Displays a base image and an "x-ray" view of a second image where the mouse is pointing
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

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