diff --git a/Auto-Image.js b/Auto-Image.js index f34a371..78d33d8 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -4392,8 +4392,24 @@
- - Click or drag on the preview to toggle pixels to NOT paint. +
+
+ + + 1 +
+
+ +
+ + + +
+
+ + + Shift = Row • Alt = Column +
@@ -5076,11 +5092,13 @@ } baseCtx.clearRect(0,0,newWidth,newHeight); baseCtx.drawImage(baseProcessor.img, 0, 0, newWidth, newHeight); + // Draw mask on overlay canvas only + maskCtx.clearRect(0,0,maskCanvas.width,maskCanvas.height); if (state.resizeIgnoreMask) { - const imgData = baseCtx.getImageData(0,0,newWidth,newHeight); - const m = state.resizeIgnoreMask; - for (let i=0;i { onWidthInput(); schedulePreview(); }); - heightSlider.addEventListener("input", () => { onHeightInput(); schedulePreview(); }); + widthSlider.addEventListener("input", () => { onWidthInput(); schedulePreview(); }); + heightSlider.addEventListener("input", () => { onHeightInput(); schedulePreview(); }); - // Click/drag to toggle ignored pixels + // Mask painting UX: brush size, modes, row/column fills, and precise coords let draggingMask = false; - const pointToggle = (clientX, clientY) => { + let lastPaintX = -1, lastPaintY = -1; + let brushSize = 1; + let maskMode = 'toggle'; // 'ignore' | 'unignore' | 'toggle' + const brushEl = resizeContainer.querySelector('#maskBrushSize'); + const brushValEl = resizeContainer.querySelector('#maskBrushSizeValue'); + const btnIgnore = resizeContainer.querySelector('#maskModeIgnore'); + const btnUnignore = resizeContainer.querySelector('#maskModeUnignore'); + const btnToggle = resizeContainer.querySelector('#maskModeToggle'); + const clearIgnoredBtnEl = resizeContainer.querySelector('#clearIgnoredBtn'); + const invertMaskBtn = resizeContainer.querySelector('#invertMaskBtn'); + + const setMode = (mode) => { + maskMode = mode; + btnIgnore.classList.toggle('wplace-btn-primary', mode === 'ignore'); + btnUnignore.classList.toggle('wplace-btn-primary', mode === 'unignore'); + btnToggle.classList.toggle('wplace-btn-primary', mode === 'toggle'); + }; + if (brushEl && brushValEl) { + brushEl.addEventListener('input', () => { brushSize = parseInt(brushEl.value, 10) || 1; brushValEl.textContent = brushSize; }); + brushValEl.textContent = brushEl.value; + brushSize = parseInt(brushEl.value, 10) || 1; + } + if (btnIgnore) btnIgnore.addEventListener('click', () => setMode('ignore')); + if (btnUnignore) btnUnignore.addEventListener('click', () => setMode('unignore')); + if (btnToggle) btnToggle.addEventListener('click', () => setMode('toggle')); + + const mapClientToPixel = (clientX, clientY) => { + // Compute without rounding until final step to avoid drift at higher zoom const rect = baseCanvas.getBoundingClientRect(); - const x = Math.floor((clientX - rect.left) / parseFloat(zoomSlider.value)); - const y = Math.floor((clientY - rect.top) / parseFloat(zoomSlider.value)); - const w = baseCanvas.width, h = baseCanvas.height; - if (x < 0 || y < 0 || x >= w || y >= h) return; - const idx = y * w + x; + const scaleX = rect.width / baseCanvas.width; + const scaleY = rect.height / baseCanvas.height; + const dx = (clientX - rect.left) / scaleX; + const dy = (clientY - rect.top) / scaleY; + const x = Math.floor(dx); + const y = Math.floor(dy); + return { x, y }; + }; + + const ensureMask = (w, h) => { if (!state.resizeIgnoreMask || state.resizeIgnoreMask.length !== w * h) { state.resizeIgnoreMask = new Uint8Array(w * h); } - state.resizeIgnoreMask[idx] = state.resizeIgnoreMask[idx] ? 0 : 1; - // quick visual hint by updating a pixel in mask canvas - const color = state.resizeIgnoreMask[idx] ? 'rgba(255,0,0,0.6)' : 'rgba(0,0,0,0)'; - if (state.resizeIgnoreMask[idx]) { - maskCtx.fillStyle = color; maskCtx.fillRect(x, y, 1, 1); - } else { - maskCtx.clearRect(x, y, 1, 1); + }; + + const paintCircle = (cx, cy, radius, value) => { + const w = baseCanvas.width, h = baseCanvas.height; + ensureMask(w, h); + const r2 = radius * radius; + for (let yy = cy - radius; yy <= cy + radius; yy++) { + if (yy < 0 || yy >= h) continue; + for (let xx = cx - radius; xx <= cx + radius; xx++) { + if (xx < 0 || xx >= w) continue; + const dx = xx - cx, dy = yy - cy; + if (dx * dx + dy * dy <= r2) { + const idx = yy * w + xx; + if (maskMode === 'toggle') { + state.resizeIgnoreMask[idx] = state.resizeIgnoreMask[idx] ? 0 : 1; + } else if (maskMode === 'ignore') { + state.resizeIgnoreMask[idx] = 1; + } else { + state.resizeIgnoreMask[idx] = 0; + } + } + } } }; - maskCanvas.addEventListener('mousedown', (e) => { draggingMask = true; pointToggle(e.clientX, e.clientY); _updateResizePreview(); }); - window.addEventListener('mousemove', (e) => { if (draggingMask) { pointToggle(e.clientX, e.clientY); } }); - window.addEventListener('mouseup', () => { if (draggingMask) { draggingMask = false; saveBotSettings(); } }); - const clearIgnoredBtnEl = resizeContainer.querySelector('#clearIgnoredBtn'); - if (clearIgnoredBtnEl) clearIgnoredBtnEl.addEventListener('click', () => { if (state.resizeIgnoreMask) state.resizeIgnoreMask.fill(0); maskCtx.clearRect(0,0,maskCanvas.width,maskCanvas.height); _updateResizePreview(); saveBotSettings(); }); + + const paintRow = (y, value) => { + const w = baseCanvas.width, h = baseCanvas.height; + ensureMask(w, h); + if (y < 0 || y >= h) return; + for (let x = 0; x < w; x++) { + const idx = y * w + x; + if (maskMode === 'toggle') { + state.resizeIgnoreMask[idx] = state.resizeIgnoreMask[idx] ? 0 : 1; + } else if (maskMode === 'ignore') { + state.resizeIgnoreMask[idx] = 1; + } else { + state.resizeIgnoreMask[idx] = 0; + } + } + }; + + const paintColumn = (x, value) => { + const w = baseCanvas.width, h = baseCanvas.height; + ensureMask(w, h); + if (x < 0 || x >= w) return; + for (let y = 0; y < h; y++) { + const idx = y * w + x; + if (maskMode === 'toggle') { + state.resizeIgnoreMask[idx] = state.resizeIgnoreMask[idx] ? 0 : 1; + } else if (maskMode === 'ignore') { + state.resizeIgnoreMask[idx] = 1; + } else { + state.resizeIgnoreMask[idx] = 0; + } + } + }; + + const redrawMaskOverlay = () => { + const w = baseCanvas.width, h = baseCanvas.height; + maskCanvas.width = w; maskCanvas.height = h; + maskCtx.clearRect(0, 0, w, h); + if (!state.resizeIgnoreMask) return; + // Draw ignored pixels as semi-transparent red squares + const img = maskCtx.createImageData(w, h); + const md = img.data; + for (let i = 0; i < state.resizeIgnoreMask.length; i++) { + if (state.resizeIgnoreMask[i]) { + const p = i * 4; + md[p] = 255; md[p + 1] = 0; md[p + 2] = 0; md[p + 3] = 150; + } + } + maskCtx.putImageData(img, 0, 0); + }; + + const handlePaint = (e) => { + const { x, y } = mapClientToPixel(e.clientX, e.clientY); + const w = baseCanvas.width, h = baseCanvas.height; + if (x < 0 || y < 0 || x >= w || y >= h) return; + const radius = Math.max(1, Math.floor(brushSize / 2)); + if (e.shiftKey) { + paintRow(y); + } else if (e.altKey) { + paintColumn(x); + } else { + paintCircle(x, y, radius); + } + lastPaintX = x; lastPaintY = y; + redrawMaskOverlay(); + }; + + maskCanvas.addEventListener('mousedown', (e) => { draggingMask = true; handlePaint(e); }); + window.addEventListener('mousemove', (e) => { if (draggingMask) handlePaint(e); }); + window.addEventListener('mouseup', () => { if (draggingMask) { draggingMask = false; saveBotSettings(); }}); + + if (clearIgnoredBtnEl) clearIgnoredBtnEl.addEventListener('click', () => { + if (state.resizeIgnoreMask) state.resizeIgnoreMask.fill(0); + redrawMaskOverlay(); + _updateResizePreview(); + saveBotSettings(); + }); + + if (invertMaskBtn) invertMaskBtn.addEventListener('click', () => { + if (!state.resizeIgnoreMask) return; + for (let i = 0; i < state.resizeIgnoreMask.length; i++) state.resizeIgnoreMask[i] = state.resizeIgnoreMask[i] ? 0 : 1; + redrawMaskOverlay(); + _updateResizePreview(); + saveBotSettings(); + }); confirmResize.onclick = async () => { const newWidth = parseInt(widthSlider.value, 10); @@ -5391,9 +5537,16 @@ downloadPreviewBtn.onclick = () => { try { + const w = baseCanvas.width, h = baseCanvas.height; + const out = document.createElement('canvas'); + out.width = w; out.height = h; + const octx = out.getContext('2d'); + octx.imageSmoothingEnabled = false; + octx.drawImage(baseCanvas, 0, 0); + octx.drawImage(maskCanvas, 0, 0); const link = document.createElement('a'); link.download = 'wplace-preview.png'; - link.href = baseCanvas.toDataURL(); + link.href = out.toDataURL(); link.click(); } catch (e) { console.warn('Failed to download preview:', e); } };