Displays a base image and an "x-ray" view of a second image where the mouse is pointing
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

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