Displays a base image and an "x-ray" view of a second image where the mouse is pointing
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

526 行
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. }