"use strict"; let overlayLoaded = false; let baseLoaded = false; let running = false; let radius = 200; let softness = 25; let width; let height; let border = false; let fitScreen = true; let paintMode = false; let scale; document.addEventListener("DOMContentLoaded", e => { document.querySelector("#reset-button").addEventListener("click", reset); document.querySelector("#load-button").addEventListener("click", e => { console.log("Trying to load..."); const baseInput = document.querySelector("#base-url").value; const overlayInput = document.querySelector("#overlay-url").value; let success = true; try { let baseURL = new URL(baseInput) console.log(baseURL); } catch { document.querySelector("#base-url").value = ""; document.querySelector("#base-url").placeholder = "Invalid URL..."; success = false; } try { let overlayURL = new URL(overlayInput) console.log(overlayURL); } catch { document.querySelector("#overlay-url").value = ""; document.querySelector("#overlay-url").placeholder = "Invalid URL..."; success = false; } if (!success) { return; } const artistLink = document.querySelector("#artist"); let artistURL = document.querySelector("#artist-url").value; if (artistURL) { artistLink.href = artistURL; artistLink.style.removeProperty("display"); } else { artistLink.style.display = "none"; } const overlayImg = document.querySelector("#overlay-img"); const baseImg = document.querySelector("#base-img"); overlayImg.src = overlayInput; baseImg.src = baseInput; setURL(); load(); try { localStorage.setItem("base", baseInput); localStorage.setItem("overlay", overlayInput); } catch { console.error("Couldn't set something in local storage :(") } }); let url = new URL(window.location); const overlay = document.querySelector("#overlay"); document.addEventListener("mousedown", e => { let x = e.clientX - e.target.getBoundingClientRect().x; let y = e.clientY - e.target.getBoundingClientRect().y; updateOverlay([[x,y]], e.buttons % 2 != 0); }); document.addEventListener("mousemove", e => { let x = e.clientX - e.target.getBoundingClientRect().x; let y = e.clientY - e.target.getBoundingClientRect().y; updateOverlay([[x,y]], e.buttons % 2 != 0); }); document.addEventListener("touchstart", e => { let offsetX = e.target.getBoundingClientRect().x; let offsetY = e.target.getBoundingClientRect().y; let touches = []; for (let i=0; i < e.touches.length; i++) { let x = e.touches[i].clientX - offsetX; let y = e.touches[i].clientY - offsetY; touches.push([x,y]); } updateOverlay(touches, true); }); document.addEventListener("touchmove", e => { let offsetX = e.target.getBoundingClientRect().x; let offsetY = e.target.getBoundingClientRect().y; let touches = []; for (let i=0; i < e.touches.length; i++) { let x = e.touches[i].clientX - offsetX; let y = e.touches[i].clientY - offsetY; touches.push([x,y]); } updateOverlay(touches, true); }); document.querySelector("#radius-slider").addEventListener("input", e => { try { radius = parseInt(e.target.value); document.querySelector("#radius-input").value = radius; } catch { console.warn("That wasn't a valid radius: " + e.target.value); } }); document.querySelector("#radius-slider").addEventListener("change", e => { try { radius = parseInt(e.target.value); document.querySelector("#radius-input").value = radius; } catch { console.warn("That wasn't a valid radius: " + e.target.value); } setURL(); }); document.querySelector("#radius-input").addEventListener("input", e => { try { radius = parseInt(e.target.value); document.querySelector("#radius-slider").value = radius; } catch { console.warn("That wasn't a valid radius: " + e.target.value); } }); document.querySelector("#radius-input").addEventListener("change", e => { try { radius = parseInt(e.target.value); document.querySelector("#radius-slider").value = radius; } catch { console.warn("That wasn't a valid radius: " + e.target.value); } setURL(); }); document.querySelector("#softness-slider").addEventListener("input", e => { try { softness = parseInt(e.target.value); document.querySelector("#softness-input").value = softness; } catch { console.warn("That wasn't a valid softness: " + e.target.value); } }); document.querySelector("#softness-slider").addEventListener("change", e => { try { softness = parseInt(e.target.value); document.querySelector("#softness-input").value = softness; } catch { console.warn("That wasn't a valid softness: " + e.target.value); } setURL(); }); document.querySelector("#softness-input").addEventListener("input", e => { try { softness = parseInt(e.target.value); document.querySelector("#softness-slider").value = softness; } catch { console.warn("That wasn't a valid softness: " + e.target.value); } }); document.querySelector("#softness-input").addEventListener("change", e => { try { softness = parseInt(e.target.value); document.querySelector("#softness-slider").value = softness; } catch { console.warn("That wasn't a valid softness: " + e.target.value); } setURL(); }); // see if we have params already; if so, use them! const overlayImg = document.querySelector("#overlay-img"); const baseImg = document.querySelector("#base-img"); const baseInput = document.querySelector("#base-url"); const overlayInput = document.querySelector("#overlay-url"); const artistInput = document.querySelector("#artist-url"); const artistLink = document.querySelector("#artist"); if (url.searchParams.has("base") && url.searchParams.has("overlay")) { let baseURL = url.searchParams.get("base"); let overlayURL = url.searchParams.get("overlay"); let artistURL = null; if (url.searchParams.has("artist")) { artistURL = url.searchParams.get("artist"); } baseImg.src = baseURL; overlayImg.src = overlayURL; baseInput.value = baseURL; overlayInput.value = overlayURL; if (artistURL) { artistLink.href = artistURL; artistInput.value = artistURL; artistLink.style.removeProperty("display"); } else { artistLink.style.display = "none"; } load(); } else { try { baseInput.value = localStorage.getItem("base"); overlayInput.value = localStorage.getItem("overlay"); } catch { console.error("Couldn't get something from local storage :(") } } if (url.searchParams.has("radius")) { try { radius = parseInt(url.searchParams.get("radius")); document.querySelector("#radius-slider").value = radius; document.querySelector("#radius-input").value = radius; } catch { console.warn("That was a bogus radius..."); } } if (url.searchParams.has("softness")) { try { softness = parseInt(url.searchParams.get("softness")); document.querySelector("#softness-slider").value = softness; document.querySelector("#softness-input").value = softness; } catch { console.warn("That was a bogus softness..."); } } if (url.searchParams.has("border")) { try { border = 1 == parseInt(url.searchParams.get("border")); document.querySelector("#show-border").checked = border; } catch { console.warn("That was a bogus softness..."); } } window.addEventListener("resize", e => { if (running) { setup(); } }) document.querySelector("#fullscreen-button").addEventListener("click", function toggleFullScreen() { var doc = window.document; var docEl = doc.documentElement; var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen; var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen; if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) { requestFullScreen.call(docEl); } else { cancelFullScreen.call(doc); } }); document.querySelector("#show-border").addEventListener("change", e => { border = e.target.checked; setURL(); }); document.querySelector("#paint-mode").addEventListener("change", e => { paintMode = e.target.checked; }); document.querySelector("#fit-screen").addEventListener("change", e => { fitScreen = e.target.checked; setup(); }); }); function load() { document.querySelector("#menu").classList.remove("start"); const overlayImg = document.querySelector("#overlay-img"); const baseImg = document.querySelector("#base-img"); overlayImg.addEventListener("load", function overlayLoad() { console.log("The overlay is loaded"); overlayLoaded = true; if (overlayLoaded && baseLoaded) { setup(); } overlayImg.removeEventListener("load", overlayLoad); }) baseImg.addEventListener("load", function baseLoad() { console.log("The base is loaded"); baseLoaded = true; if (overlayLoaded && baseLoaded) { setup(); } baseImg.removeEventListener("load", baseLoad); }) } function reset() { running = false; baseLoaded = false; overlayLoaded = false; const overlay = document.querySelector("#overlay"); const base = document.querySelector("#base"); const overlayResized = document.querySelector("#overlay-resized"); const baseResized = document.querySelector("#base-resized"); document.querySelector("#menu").classList.add("start"); overlay.classList.add("hidden"); base.classList.add("hidden"); } function setup() { running = true; const overlay = document.querySelector("#overlay"); const base = document.querySelector("#base"); const overlayResized = document.querySelector("#overlay-resized"); const baseResized = document.querySelector("#base-resized"); overlay.classList.remove("hidden"); base.classList.remove("hidden"); const overlayImg = document.querySelector("#overlay-img"); const baseImg = document.querySelector("#base-img"); /** @type {CanvasRenderingContext2D} */ const overlayCtx = overlay.getContext("2d"); /** @type {CanvasRenderingContext2D} */ const baseCtx = base.getContext("2d"); /** @type {CanvasRenderingContext2D} */ const overlayCtxResized = overlayResized.getContext("2d"); /** @type {CanvasRenderingContext2D} */ const baseCtxResized = baseResized.getContext("2d"); const availableWidth = document.querySelector("#fill-div").getBoundingClientRect().width; const availableHeight = document.querySelector("#fill-div").getBoundingClientRect().height; const scaleW = availableWidth / baseImg.width; const scaleH = availableHeight / baseImg.height; scale = fitScreen ? Math.min(scaleW, scaleH) : 1; width = fitScreen ? Math.floor(availableWidth * scale / scaleW) : baseImg.width; height = fitScreen ? Math.floor(availableHeight * scale / scaleH) : baseImg.height; [baseCtx, baseCtxResized, overlayCtx, overlayCtxResized].forEach(ctx => { ctx.canvas.width = width; ctx.canvas.height = height; ctx.canvas.style.left = fitScreen ? (availableWidth - width) / 2 + "px" : 0; ctx.canvas.style.top = fitScreen ? (availableHeight - height) / 2 + "px" : 0; }); baseCtxResized.drawImage(baseImg, 0, 0, width, height); baseCtx.drawImage(baseResized, 0, 0, width, height); overlayCtxResized.drawImage(overlayImg, 0, 0, width, height); console.log("Done"); } function ease(t, k) { return 1 - Math.pow(2, -k * (1 - t)); } function updateOverlay(points, clicked) { const overlay = document.querySelector("#overlay"); const overlayResized = document.querySelector("#overlay-resized"); /** @type {CanvasRenderingContext2D} */ const overlayCtx = overlay.getContext("2d"); /** @type {CanvasRenderingContext2D} */ const overlayCtxResized = overlay.getContext("2d"); const w = overlayCtx.canvas.width; const h = overlayCtx.canvas.height; overlayCtx.save(); overlayCtx.globalCompositeOperation = "source-over"; if (!paintMode) overlayCtx.clearRect(0, 0, w, h); if (!paintMode || clicked) { points.forEach(point => { const [x,y] = point; overlayCtx.beginPath(); overlayCtx.ellipse(x, y, radius * scale, radius * scale, 0, 0, 2 * Math.PI); const gradient = overlayCtx.createRadialGradient(x, y, 0, x, y, Math.floor(radius * scale)); const maxOpacity = ease(0, 1 / (0.00001 + softness / 100)); const steps = 20; for (let t=0 ; t <= steps; t+= 1) { let eased = ease(t/steps, 1 / (0.00001 + softness / 100)) / maxOpacity; gradient.addColorStop(t/steps, `rgba(0, 0, 0, ${eased}`); } let eased = ease(0.999, 1 / (0.00001 + softness / 100)) / maxOpacity; gradient.addColorStop(0.999, `rgba(0, 0, 0, ${eased}`); overlayCtx.fillStyle = gradient; overlayCtx.fill(); }) } overlayCtx.globalCompositeOperation = "source-in"; overlayCtx.drawImage(overlayResized, 0, 0); overlayCtx.globalCompositeOperation = "source-over"; if (!paintMode && border) { points.forEach(point => { const [x, y] = point; overlayCtx.strokeStyle = "#000"; overlayCtx.lineWidth = 3; overlayCtx.beginPath(); overlayCtx.ellipse(x, y, radius * scale, radius * scale, 0, 0, 2 * Math.PI); overlayCtx.stroke(); }); } overlayCtx.restore(); } function setURL() { let shareURL = new URL(window.location); // for some reason, the parser gets confused by urlencoded urls... // so, to get rid of all parameters, we do this let keys = Array.from(shareURL.searchParams.keys()); do { keys = Array.from(shareURL.searchParams.keys()); keys.forEach(key => { shareURL.searchParams.delete(key); }); } while (keys.length > 0) const artistLink = document.querySelector("#artist"); const overlayImg = document.querySelector("#overlay-img"); const baseImg = document.querySelector("#base-img"); shareURL.searchParams.append("base", baseImg.src); shareURL.searchParams.append("overlay", overlayImg.src); if (artistLink.href) { shareURL.searchParams.append("artist", artistLink.href); } shareURL.searchParams.append("radius", radius); shareURL.searchParams.append("softness", softness); if (border) { shareURL.searchParams.append("border", 1); } window.history.replaceState(null, "X-Ray Viewer", shareURL); }