Displays a base image and an "x-ray" view of a second image where the mouse is pointing
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

423 linhas
14 KiB

  1. "use strict";
  2. let overlayLoaded = false;
  3. let baseLoaded = false;
  4. let running = false;
  5. let radius = 200;
  6. let softness = 25;
  7. let width;
  8. let height;
  9. let fitScreen = true;
  10. let paintMode = false;
  11. let scale;
  12. document.addEventListener("DOMContentLoaded", e => {
  13. document.querySelector("#reset-button").addEventListener("click", reset);
  14. document.querySelector("#load-button").addEventListener("click", e => {
  15. console.log("Trying to load...");
  16. const baseInput = document.querySelector("#base-url").value;
  17. const overlayInput = document.querySelector("#overlay-url").value;
  18. let success = true;
  19. try {
  20. let baseURL = new URL(baseInput)
  21. console.log(baseURL);
  22. } catch {
  23. document.querySelector("#base-url").value = "";
  24. document.querySelector("#base-url").placeholder = "Invalid URL...";
  25. success = false;
  26. }
  27. try {
  28. let overlayURL = new URL(overlayInput)
  29. console.log(overlayURL);
  30. } catch {
  31. document.querySelector("#overlay-url").value = "";
  32. document.querySelector("#overlay-url").placeholder = "Invalid URL...";
  33. success = false;
  34. }
  35. if (!success) {
  36. return;
  37. }
  38. const overlayImg = document.querySelector("#overlay-img");
  39. const baseImg = document.querySelector("#base-img");
  40. overlayImg.src = overlayInput;
  41. baseImg.src = baseInput;
  42. setURL();
  43. load();
  44. try {
  45. localStorage.setItem("base", baseInput);
  46. localStorage.setItem("overlay", overlayInput);
  47. } catch {
  48. console.error("Couldn't set something in local storage :(")
  49. }
  50. });
  51. let url = new URL(window.location);
  52. const overlay = document.querySelector("#overlay");
  53. document.addEventListener("mousedown", e => {
  54. let x = e.clientX - e.target.getBoundingClientRect().x;
  55. let y = e.clientY - e.target.getBoundingClientRect().y;
  56. updateOverlay([[x,y]], e.buttons % 2 != 0);
  57. });
  58. document.addEventListener("mousemove", e => {
  59. let x = e.clientX - e.target.getBoundingClientRect().x;
  60. let y = e.clientY - e.target.getBoundingClientRect().y;
  61. updateOverlay([[x,y]], e.buttons % 2 != 0);
  62. });
  63. document.addEventListener("touchstart", e => {
  64. let offsetX = e.target.getBoundingClientRect().x;
  65. let offsetY = e.target.getBoundingClientRect().y;
  66. let touches = [];
  67. for (let i=0; i < e.touches.length; i++) {
  68. let x = e.touches[i].clientX - offsetX;
  69. let y = e.touches[i].clientY - offsetY;
  70. touches.push([x,y]);
  71. }
  72. updateOverlay(touches, true);
  73. });
  74. document.addEventListener("touchmove", e => {
  75. let offsetX = e.target.getBoundingClientRect().x;
  76. let offsetY = e.target.getBoundingClientRect().y;
  77. let touches = [];
  78. for (let i=0; i < e.touches.length; i++) {
  79. let x = e.touches[i].clientX - offsetX;
  80. let y = e.touches[i].clientY - offsetY;
  81. touches.push([x,y]);
  82. }
  83. updateOverlay(touches, true);
  84. });
  85. document.querySelector("#radius-slider").addEventListener("input", e => {
  86. try {
  87. radius = parseInt(e.target.value);
  88. document.querySelector("#radius-input").value = radius;
  89. } catch {
  90. console.warn("That wasn't a valid radius: " + e.target.value);
  91. }
  92. });
  93. document.querySelector("#radius-slider").addEventListener("change", e => {
  94. try {
  95. radius = parseInt(e.target.value);
  96. document.querySelector("#radius-input").value = radius;
  97. } catch {
  98. console.warn("That wasn't a valid radius: " + e.target.value);
  99. }
  100. setURL();
  101. });
  102. document.querySelector("#radius-input").addEventListener("input", e => {
  103. try {
  104. radius = parseInt(e.target.value);
  105. document.querySelector("#radius-slider").value = radius;
  106. } catch {
  107. console.warn("That wasn't a valid radius: " + e.target.value);
  108. }
  109. });
  110. document.querySelector("#radius-input").addEventListener("change", e => {
  111. try {
  112. radius = parseInt(e.target.value);
  113. document.querySelector("#radius-slider").value = radius;
  114. } catch {
  115. console.warn("That wasn't a valid radius: " + e.target.value);
  116. }
  117. setURL();
  118. });
  119. document.querySelector("#softness-slider").addEventListener("input", e => {
  120. try {
  121. softness = parseInt(e.target.value);
  122. document.querySelector("#softness-input").value = softness;
  123. } catch {
  124. console.warn("That wasn't a valid softness: " + e.target.value);
  125. }
  126. });
  127. document.querySelector("#softness-slider").addEventListener("change", e => {
  128. try {
  129. softness = parseInt(e.target.value);
  130. document.querySelector("#softness-input").value = softness;
  131. } catch {
  132. console.warn("That wasn't a valid softness: " + e.target.value);
  133. }
  134. setURL();
  135. });
  136. document.querySelector("#softness-input").addEventListener("input", e => {
  137. try {
  138. softness = parseInt(e.target.value);
  139. document.querySelector("#softness-slider").value = softness;
  140. } catch {
  141. console.warn("That wasn't a valid softness: " + e.target.value);
  142. }
  143. });
  144. document.querySelector("#softness-input").addEventListener("change", e => {
  145. try {
  146. softness = parseInt(e.target.value);
  147. document.querySelector("#softness-slider").value = softness;
  148. } catch {
  149. console.warn("That wasn't a valid softness: " + e.target.value);
  150. }
  151. setURL();
  152. });
  153. // see if we have params already; if so, use them!
  154. const overlayImg = document.querySelector("#overlay-img");
  155. const baseImg = document.querySelector("#base-img");
  156. const baseInput = document.querySelector("#base-url");
  157. const overlayInput = document.querySelector("#overlay-url");
  158. if (url.searchParams.has("base") && url.searchParams.has("overlay")) {
  159. let baseURL = url.searchParams.get("base");
  160. let overlayURL = url.searchParams.get("overlay");
  161. baseImg.src = baseURL;
  162. overlayImg.src = overlayURL;
  163. baseInput.value = baseURL;
  164. overlayInput.value = overlayURL;
  165. load();
  166. } else {
  167. try {
  168. baseInput.value = localStorage.getItem("base");
  169. overlayInput.value = localStorage.getItem("overlay");
  170. } catch {
  171. console.error("Couldn't get something from local storage :(")
  172. }
  173. }
  174. if (url.searchParams.has("radius")) {
  175. try {
  176. radius = parseInt(url.searchParams.get("radius"));
  177. document.querySelector("#radius-slider").value = radius;
  178. document.querySelector("#radius-input").value = radius;
  179. } catch {
  180. console.warn("That was a bogus radius...");
  181. }
  182. }
  183. if (url.searchParams.has("softness")) {
  184. try {
  185. softness = parseInt(url.searchParams.get("softness"));
  186. document.querySelector("#softness-slider").value = softness;
  187. document.querySelector("#softness-input").value = softness;
  188. } catch {
  189. console.warn("That was a bogus softness...");
  190. }
  191. }
  192. window.addEventListener("resize", e => {
  193. if (running) {
  194. setup();
  195. }
  196. })
  197. document.querySelector("#fullscreen-button").addEventListener("click", function toggleFullScreen() {
  198. var doc = window.document;
  199. var docEl = doc.documentElement;
  200. var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
  201. var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
  202. if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
  203. requestFullScreen.call(docEl);
  204. }
  205. else {
  206. cancelFullScreen.call(doc);
  207. }
  208. });
  209. document.querySelector("#paint-mode").addEventListener("change", e => {
  210. paintMode = e.target.checked;
  211. });
  212. document.querySelector("#fit-screen").addEventListener("change", e => {
  213. fitScreen = e.target.checked;
  214. setup();
  215. });
  216. });
  217. function load() {
  218. document.querySelector("#menu").classList.remove("start");
  219. const overlayImg = document.querySelector("#overlay-img");
  220. const baseImg = document.querySelector("#base-img");
  221. overlayImg.addEventListener("load", function overlayLoad() {
  222. console.log("The overlay is loaded");
  223. overlayLoaded = true;
  224. if (overlayLoaded && baseLoaded) {
  225. setup();
  226. }
  227. overlayImg.removeEventListener("load", overlayLoad);
  228. })
  229. baseImg.addEventListener("load", function baseLoad() {
  230. console.log("The base is loaded");
  231. baseLoaded = true;
  232. if (overlayLoaded && baseLoaded) {
  233. setup();
  234. }
  235. baseImg.removeEventListener("load", baseLoad);
  236. })
  237. }
  238. function reset() {
  239. running = false;
  240. const overlay = document.querySelector("#overlay");
  241. const base = document.querySelector("#base");
  242. const overlayResized = document.querySelector("#overlay-resized");
  243. const baseResized = document.querySelector("#base-resized");
  244. document.querySelector("#menu").classList.add("start");
  245. overlay.classList.add("hidden");
  246. base.classList.add("hidden");
  247. }
  248. function setup() {
  249. running = true;
  250. const overlay = document.querySelector("#overlay");
  251. const base = document.querySelector("#base");
  252. const overlayResized = document.querySelector("#overlay-resized");
  253. const baseResized = document.querySelector("#base-resized");
  254. overlay.classList.remove("hidden");
  255. base.classList.remove("hidden");
  256. const overlayImg = document.querySelector("#overlay-img");
  257. const baseImg = document.querySelector("#base-img");
  258. /** @type {CanvasRenderingContext2D} */
  259. const overlayCtx = overlay.getContext("2d");
  260. /** @type {CanvasRenderingContext2D} */
  261. const baseCtx = base.getContext("2d");
  262. /** @type {CanvasRenderingContext2D} */
  263. const overlayCtxResized = overlayResized.getContext("2d");
  264. /** @type {CanvasRenderingContext2D} */
  265. const baseCtxResized = baseResized.getContext("2d");
  266. const availableWidth = document.querySelector("#fill-div").getBoundingClientRect().width;
  267. const availableHeight = document.querySelector("#fill-div").getBoundingClientRect().height;
  268. const scaleW = availableWidth / baseImg.width;
  269. const scaleH = availableHeight / baseImg.height;
  270. scale = fitScreen ? Math.min(scaleW, scaleH) : 1;
  271. width = fitScreen ? Math.floor(availableWidth * scale / scaleW) : baseImg.width;
  272. height = fitScreen ? Math.floor(availableHeight * scale / scaleH) : baseImg.height;
  273. [baseCtx, baseCtxResized, overlayCtx, overlayCtxResized].forEach(ctx => {
  274. ctx.canvas.width = width;
  275. ctx.canvas.height = height;
  276. ctx.canvas.style.left = fitScreen ? (availableWidth - width) / 2 + "px" : 0;
  277. ctx.canvas.style.top = fitScreen ? (availableHeight - height) / 2 + "px" : 0;
  278. });
  279. baseCtxResized.drawImage(baseImg, 0, 0, width, height);
  280. baseCtx.drawImage(baseResized, 0, 0, width, height);
  281. overlayCtxResized.drawImage(overlayImg, 0, 0, width, height);
  282. console.log("Done");
  283. }
  284. function updateOverlay(points, clicked) {
  285. const overlay = document.querySelector("#overlay");
  286. const overlayResized = document.querySelector("#overlay-resized");
  287. /** @type {CanvasRenderingContext2D} */
  288. const overlayCtx = overlay.getContext("2d");
  289. /** @type {CanvasRenderingContext2D} */
  290. const overlayCtxResized = overlay.getContext("2d");
  291. const w = overlayCtx.canvas.width;
  292. const h = overlayCtx.canvas.height;
  293. overlayCtx.save();
  294. overlayCtx.globalCompositeOperation = "source-over";
  295. if (!paintMode)
  296. overlayCtx.clearRect(0, 0, w, h);
  297. if (!paintMode || clicked) {
  298. points.forEach(point => {
  299. const [x,y] = point;
  300. overlayCtx.beginPath();
  301. overlayCtx.ellipse(x, y, radius * scale, radius * scale, 0, 0, 2 * Math.PI);
  302. const gradient = overlayCtx.createRadialGradient(x, y, 0, x, y, Math.floor(radius * scale));
  303. gradient.addColorStop((100-softness)/100, '#000000FF');
  304. gradient.addColorStop(1, '#00000000');
  305. overlayCtx.fillStyle = gradient;
  306. overlayCtx.fill();
  307. })
  308. }
  309. overlayCtx.globalCompositeOperation = "source-in";
  310. overlayCtx.drawImage(overlayResized, 0, 0);
  311. overlayCtx.restore();
  312. }
  313. function setURL() {
  314. let shareURL = new URL(window.location);
  315. // for some reason, the parser gets confused by urlencoded urls...
  316. // so, to get rid of all parameters, we do this
  317. let keys = Array.from(shareURL.searchParams.keys());
  318. do {
  319. keys = Array.from(shareURL.searchParams.keys());
  320. keys.forEach(key => {
  321. shareURL.searchParams.delete(key);
  322. });
  323. } while (keys.length > 0)
  324. const overlayImg = document.querySelector("#overlay-img");
  325. const baseImg = document.querySelector("#base-img");
  326. shareURL.searchParams.append("base", baseImg.src);
  327. shareURL.searchParams.append("overlay", overlayImg.src);
  328. shareURL.searchParams.append("radius", radius);
  329. shareURL.searchParams.append("softness", softness);
  330. window.history.replaceState(null, "X-Ray Viewer", shareURL);
  331. }