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.
 
 
 
 

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