Displays a base image and an "x-ray" view of a second image where the mouse is pointing
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

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