Displays a base image and an "x-ray" view of a second image where the mouse is pointing
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

526 lignes
17 KiB

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