// Copyright by anb030.de - 15.09.2025 const fileInput = document.getElementById("fileInput"); const uploadBtn = document.getElementById("uploadBtn"); const saveBtn = document.getElementById("saveBtn"); const canvasBase = document.getElementById("canvasBase"); const ctxBase = canvasBase.getContext("2d"); const canvasOverlay = document.getElementById("canvasOverlay"); const ctxOverlay = canvasOverlay.getContext("2d"); let img = new Image(); let words = []; let scale = 1; const DPR = window.devicePixelRatio || 1; const hint = document.getElementById("hintOverlay"); const popup = document.getElementById("customPopup"); const confirmSaveBtn = document.getElementById("confirmSaveBtn"); uploadBtn.addEventListener("click", () => fileInput.click()); fileInput.addEventListener("change", (e) => { const file = e.target.files && e.target.files[0]; if (!file) return; if (hint) hint.style.display = "none"; const reader = new FileReader(); reader.onload = (ev) => { img = new Image(); img.onload = async () => { scale = window.innerWidth / img.width; const w = img.width * scale; const h = img.height * scale; [canvasBase, canvasOverlay].forEach((c) => { c.width = w * DPR; c.height = h * DPR; c.style.width = w + "px"; c.style.height = h + "px"; }); ctxBase.drawImage(img, 0, 0, w * DPR, h * DPR); const result = await Tesseract.recognize(img, "deu", { logger: (m) => console.log(m), }); words = result.data.words.map((w) => ({ text: w.text, bbox: { x0: w.bbox.x0, y0: w.bbox.y0, x1: w.bbox.x1, y1: w.bbox.y1 }, })); }; img.src = ev.target.result; }; reader.readAsDataURL(file); }); function transformBox(bbox) { return { x0: bbox.x0 * scale, y0: bbox.y0 * scale, x1: bbox.x1 * scale, y1: bbox.y1 * scale, }; } function pixelateWord(word) { const b = transformBox(word.bbox); const blockSize = 4; const palette = [0, 32, 64, 96, 128, 160, 192, 224, 255]; const width = b.x1 - b.x0; const height = b.y1 - b.y0; for (let by = 0; by < height; by += blockSize) { for (let bx = 0; bx < width; bx += blockSize) { const v = palette[Math.floor(Math.random() * palette.length)]; ctxBase.fillStyle = `rgb(${v},${v},${v})`; ctxBase.fillRect( (b.x0 + bx) * DPR, (b.y0 + by) * DPR, blockSize * DPR, blockSize * DPR ); } } } let touchStartX = 0, touchStartY = 0; canvasOverlay.addEventListener("touchstart", (e) => { if (e.touches.length === 1) { touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; } }, { passive: true }); canvasOverlay.addEventListener("touchend", (e) => { if (e.changedTouches.length === 1) { const dx = Math.abs(e.changedTouches[0].clientX - touchStartX); const dy = Math.abs(e.changedTouches[0].clientY - touchStartY); if (dx < 5 && dy < 5) { handleTap(e.changedTouches[0].clientX, e.changedTouches[0].clientY); } } }, { passive: true }); canvasOverlay.addEventListener("click", (e) => { handleTap(e.clientX, e.clientY); }); function handleTap(clientX, clientY) { const rect = canvasOverlay.getBoundingClientRect(); const x = clientX - rect.left; const y = clientY - rect.top; const hit = words.find((w) => { const b = transformBox(w.bbox); return x >= b.x0 && x <= b.x1 && y >= b.y0 && y <= b.y1; }); if (hit) { pixelateWord(hit); } } saveBtn.addEventListener("click", () => { popup.style.display = "flex"; }); confirmSaveBtn.addEventListener("click", () => { popup.style.display = "none"; const finalCanvas = document.createElement("canvas"); finalCanvas.width = canvasBase.width; finalCanvas.height = canvasBase.height; const ctx = finalCanvas.getContext("2d"); ctx.drawImage(canvasBase, 0, 0); ctx.drawImage(canvasOverlay, 0, 0); finalCanvas.toBlob((blob) => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "verpixelt.png"; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000); }, "image/png"); });