From 01759400d4e3850168810b08d2ceae06c18cbe13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=A9ng?= <99890979+maiminhdung@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:53:26 +0700 Subject: [PATCH] UPDATE + Add skip pixel feature (Beta) + Recode Setting UI --- Auto-Image.js | 645 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 443 insertions(+), 202 deletions(-) diff --git a/Auto-Image.js b/Auto-Image.js index 01026ca..54126fd 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -5,12 +5,12 @@ TRANSPARENCY_THRESHOLD: 100, WHITE_THRESHOLD: 250, LOG_INTERVAL: 10, - PAINTING_SPEED: { + PAINTING_SPEED: { MIN: 1, // Minimum 1 pixel per second MAX: 1000, // Maximum 1000 pixels per second DEFAULT: 5, // Default 5 pixels per second - }, - PAINTING_SPEED_ENABLED: false, + }, + SKIP_CORRECT_PIXELS: true, // Skip pixels that already have correct color // Optimized CSS Classes for reuse CSS_CLASSES: { BUTTON_PRIMARY: ` @@ -133,6 +133,17 @@ } } + const loadSkipPixelsPreference = () => { + try { + const saved = localStorage.getItem("wplace_skip_correct_pixels") + if (saved !== null) { + CONFIG.SKIP_CORRECT_PIXELS = saved === 'true' + } + } catch (e) { + console.warn("Could not load skip correct pixels preference:", e) + } + } + // BILINGUAL TEXT STRINGS const TEXT = { en: { @@ -162,6 +173,7 @@ missingRequirements: "❌ Load an image and select a position first", progress: "Progress", pixels: "Pixels", + painted: "Painted", charges: "Charges", estimatedTime: "Estimated time", initMessage: "Click 'Start Auto-BOT' to begin", @@ -193,7 +205,11 @@ language: "Language", themeSettings: "Theme Settings", themeSettingsDesc: "Choose your preferred color theme for the interface.", - languageSelectDesc: "Select your preferred language. Changes will take effect immediately.", + languageSelectDesc: "Select your preferred language. Changes will take effect immediately.", + skipCorrectPixels: "Skip Correct Pixels", + skipCorrectPixelsDesc: "Skip pixels that already have the correct color and count them as painted. Note: May not work due to canvas security restrictions.", + pixelsSkipped: "Pixels skipped: {count}", + speedSettingDesc: "Adjust the painting speed from {min} to {max} pixels per second. Higher speeds may result in longer update times on the WPlace server." }, pt: { title: "WPlace Auto-Image", @@ -222,6 +238,7 @@ missingRequirements: "❌ Carregue uma imagem e selecione uma posição primeiro", progress: "Progresso", pixels: "Pixels", + painted: "Pintados", charges: "Cargas", estimatedTime: "Tempo estimado", initMessage: "Clique em 'Iniciar Auto-BOT' para começar", @@ -253,7 +270,11 @@ language: "Idioma", themeSettings: "Configurações de Tema", themeSettingsDesc: "Escolha seu tema de cores preferido para a interface.", - languageSelectDesc: "Selecione seu idioma preferido. As alterações terão efeito imediatamente.", + languageSelectDesc: "Selecione seu idioma preferido. As alterações terão efeito imediatamente.", + skipCorrectPixels: "Pular Pixels Corretos", + skipCorrectPixelsDesc: "Pular pixels que já têm a cor correta e contá-los como pintados. Nota: Pode não funcionar devido a restrições de segurança do canvas.", + pixelsSkipped: "Pixels pulados: {count}", + speedSettingDesc: "Ajuste a velocidade de pintura de {min} a {max} pixels por segundo. Velocidades mais altas podem resultar em tempos de atualização mais longos no servidor WPlace." }, vi: { title: "WPlace Auto-Image", @@ -282,6 +303,7 @@ missingRequirements: "❌ Hãy tải lên hình ảnh và chọn vị trí trước", progress: "Tiến trình", pixels: "Pixel", + painted: "Đã vẽ", charges: "Điện tích", estimatedTime: "Thời gian ước tính", initMessage: "Nhấp 'Khởi động Auto-BOT' để bắt đầu", @@ -314,6 +336,10 @@ themeSettings: "Cài đặt Giao diện", themeSettingsDesc: "Chọn chủ đề màu sắc yêu thích cho giao diện.", languageSelectDesc: "Chọn ngôn ngữ ưa thích. Thay đổi sẽ có hiệu lực ngay lập tức.", + skipCorrectPixels: "Bỏ qua Pixel đúng màu", + skipCorrectPixelsDesc: "Bỏ qua các pixel đã có màu đúng và tính chúng là đã vẽ. Lưu ý: Có thể không hoạt động do hạn chế bảo mật canvas.", + pixelsSkipped: "Pixel đã bỏ qua: {count}", + speedSettingDesc: "Điều chỉnh tốc độ vẽ từ {min} đến {max} pixel mỗi giây. Tốc độ cao có thể làm trong wplace server update mất thời gian hơn." }, fr: { title: "WPlace Auto-Image", @@ -342,6 +368,7 @@ missingRequirements: "❌ Veuillez charger une image et sélectionner une position d'abord", progress: "Progrès", pixels: "Pixels", + painted: "Peints", charges: "Charges", estimatedTime: "Temps estimé", initMessage: "Cliquez sur 'Démarrer Auto-BOT' pour commencer", @@ -374,6 +401,10 @@ themeSettings: "Paramètres de Thème", themeSettingsDesc: "Choisissez votre thème de couleurs préféré pour l'interface.", languageSelectDesc: "Sélectionnez votre langue préférée. Les changements prendront effet immédiatement.", + skipCorrectPixels: "Ignorer les Pixels Corrects", + skipCorrectPixelsDesc: "Ignorer les pixels qui ont déjà la bonne couleur et les compter comme peints. Note: Peut ne pas fonctionner en raison de restrictions de sécurité du canvas.", + pixelsSkipped: "Pixels ignorés: {count}", + speedSettingDesc: "Ajustez la vitesse de peinture de {min} à {max} pixels par seconde. Des vitesses plus élevées peuvent entraîner des temps de mise à jour plus longs sur le serveur WPlace." }, } @@ -384,6 +415,7 @@ processing: false, totalPixels: 0, paintedPixels: 0, + skippedPixels: 0, // Track skipped pixels availableColors: [], currentCharges: 0, cooldown: CONFIG.COOLDOWN_DEFAULT, @@ -552,6 +584,154 @@ colorDistance: (a, b) => Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)), + // Debug function to test canvas access + testCanvasAccess: () => { + console.log("=== Canvas Access Debug ===") + + const canvases = document.querySelectorAll('canvas') + console.log(`Found ${canvases.length} canvas elements`) + + canvases.forEach((canvas, index) => { + console.log(`Canvas ${index}:`, { + width: canvas.width, + height: canvas.height, + id: canvas.id, + className: canvas.className, + style: canvas.style.cssText + }) + + try { + const ctx = canvas.getContext('2d') + if (ctx) { + // Try to read a pixel from center + const x = Math.floor(canvas.width / 2) + const y = Math.floor(canvas.height / 2) + const imageData = ctx.getImageData(x, y, 1, 1) + const data = imageData.data + console.log(` Pixel at (${x},${y}): RGB(${data[0]}, ${data[1]}, ${data[2]})`) + } + } catch (error) { + console.log(` Error reading canvas ${index}:`, error.message) + } + }) + + console.log("=== End Debug ===") + }, + + // Simplified canvas pixel reading for WPlace + getWPlacePixelColor: (x, y) => { + try { + console.log(`Attempting to read pixel at (${x},${y})...`) + + // Find the main WPlace canvas - try different approaches + let canvas = null + + // Method 1: Look for canvas in main container + const containers = ['#app', '[data-testid="canvas-container"]', '.canvas-container', 'main', 'body'] + for (const selector of containers) { + const container = document.querySelector(selector) + if (container) { + const foundCanvas = container.querySelector('canvas') + if (foundCanvas && foundCanvas.width > 100 && foundCanvas.height > 100) { + canvas = foundCanvas + console.log(`Found canvas via container ${selector}: ${canvas.width}x${canvas.height}`) + break + } + } + } + + // Method 2: Direct canvas selectors + if (!canvas) { + const selectors = [ + 'canvas[width][height]', + 'canvas.pixelcanvas', + 'canvas.canvas', + 'canvas[data-testid="canvas"]', + 'canvas' + ] + + for (const selector of selectors) { + const canvases = document.querySelectorAll(selector) + for (const c of canvases) { + // Check if canvas has reasonable dimensions and is visible + if (c.width > 100 && c.height > 100 && + c.offsetWidth > 0 && c.offsetHeight > 0) { + canvas = c + console.log(`Found canvas via selector ${selector}: ${canvas.width}x${canvas.height}`) + break + } + } + if (canvas) break + } + } + + // Method 3: Find the largest canvas + if (!canvas) { + const allCanvases = document.querySelectorAll('canvas') + let largest = null + let maxSize = 0 + + for (const c of allCanvases) { + const size = c.width * c.height + if (size > maxSize && c.offsetWidth > 0 && c.offsetHeight > 0) { + maxSize = size + largest = c + } + } + + if (largest) { + canvas = largest + console.log(`Found largest canvas: ${canvas.width}x${canvas.height}`) + } + } + + if (!canvas) { + console.warn("No suitable canvas found") + return null + } + + // Check if coordinates are within canvas bounds + if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) { + console.warn(`Coordinates (${x},${y}) outside canvas bounds ${canvas.width}x${canvas.height}`) + return null + } + + const ctx = canvas.getContext('2d') + if (!ctx) { + console.warn("Could not get canvas context") + return null + } + + // Get pixel data + const imageData = ctx.getImageData(x, y, 1, 1) + const data = imageData.data + + const color = [data[0], data[1], data[2]] + console.log(`Successfully read pixel at (${x},${y}): RGB(${color.join(',')})`) + return color + + } catch (error) { + console.warn(`Failed to read pixel at (${x},${y}):`, error.message) + return null + } + }, + + // Check if current pixel matches target color + pixelMatches: (currentColor, targetColor, tolerance = 10) => { + if (!currentColor || !targetColor) return false + + const [cr, cg, cb] = currentColor + const [tr, tg, tb] = targetColor + + const distance = Math.sqrt( + Math.pow(cr - tr, 2) + + Math.pow(cg - tg, 2) + + Math.pow(cb - tb, 2) + ) + + return distance <= tolerance + }, + isWhitePixel: (r, g, b) => r >= CONFIG.WHITE_THRESHOLD && g >= CONFIG.WHITE_THRESHOLD && b >= CONFIG.WHITE_THRESHOLD, @@ -967,6 +1147,7 @@ loadThemePreference() loadLanguagePreference() + loadSkipPixelsPreference() const theme = getCurrentTheme() @@ -2142,8 +2323,9 @@ padding: 0; z-index: 10002; display: none; - min-width: 380px; - max-width: 420px; + width: 720px; + max-width: 90vw; + max-height: 85vh; color: white; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; box-shadow: 0 20px 40px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.1); @@ -2177,206 +2359,205 @@ -
- -
- -
-
- + +
+ + +
+ +
+ +
+
+ + ${CONFIG.PAINTING_SPEED.DEFAULT} +
+
+ ${Utils.t("speedSettingDesc", {min: CONFIG.PAINTING_SPEED.MIN, max: CONFIG.PAINTING_SPEED.MAX})} +
+
+
+ + +
+ +
+ +
+ ${Utils.t("languageSelectDesc")} +
+
-
- - -
- -
- - -
- -
- -
-
- - -
- -
- + + +
+ +
+ +
+ +
+ ${Utils.t("themeSettingsDesc")} +
+
+
+ + +
+ +
+
+ + +
+
+ ${Utils.t("skipCorrectPixelsDesc")} +
+
+
- - ` + // Add responsive CSS styles for settings + const settingsStyles = document.createElement('style') + settingsStyles.textContent = ` + @keyframes settingsSlideIn { + from { + opacity: 0; + transform: translate(-50%, -50%) scale(0.9); + } + to { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + } + + @keyframes settingsFadeOut { + from { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + to { + opacity: 0; + transform: translate(-50%, -50%) scale(0.9); + } + } + + /* Responsive settings */ + @media (max-width: 768px) { + #wplace-settings-container { + width: 95vw !important; + max-width: none !important; + } + + #wplace-settings-container .grid-container { + grid-template-columns: 1fr !important; + gap: 15px !important; + } + } + + /* Settings hover effects */ + #wplace-settings-container input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: white; + box-shadow: 0 2px 4px rgba(0,0,0,0.3), 0 0 0 2px #4facfe; + cursor: pointer; + transition: all 0.2s ease; + } + + #wplace-settings-container input[type="range"]::-webkit-slider-thumb:hover { + transform: scale(1.1); + box-shadow: 0 3px 6px rgba(0,0,0,0.4), 0 0 0 3px #4facfe; + } + + #wplace-settings-container select:hover { + border-color: rgba(255,255,255,0.4); + background: rgba(255,255,255,0.2); + transform: translateY(-1px); + box-shadow: 0 3px 8px rgba(0,0,0,0.15); + } + + #wplace-settings-container select:focus { + border-color: #4facfe; + box-shadow: 0 0 0 2px rgba(79, 172, 254, 0.3); + outline: none; + } + + .wplace-settings-header:hover { + background: rgba(255,255,255,0.15) !important; + } + ` + document.head.appendChild(settingsStyles) + + // Resize Dialog Container const resizeContainer = document.createElement("div") resizeContainer.className = "resize-container" resizeContainer.innerHTML = ` @@ -2620,6 +2801,28 @@ // Make settings window draggable makeDraggable(settingsContainer) + // Skip Correct Pixels toggle event listener + const skipToggle = settingsContainer.querySelector("#skipCorrectPixelsToggle") + if (skipToggle) { + skipToggle.addEventListener("change", (e) => { + CONFIG.SKIP_CORRECT_PIXELS = e.target.checked + // Save preference to localStorage + try { + localStorage.setItem('wplace_skip_correct_pixels', CONFIG.SKIP_CORRECT_PIXELS.toString()) + } catch (error) { + console.warn("Could not save skip correct pixels preference:", error) + } + }) + } + + // Test Canvas button event listener + const testCanvasBtn = settingsContainer.querySelector("#testCanvasBtn") + if (testCanvasBtn) { + testCanvasBtn.addEventListener("click", () => { + Utils.testCanvasAccess() + }) + } + // Language selector event listener const languageSelect = settingsContainer.querySelector("#languageSelect") if (languageSelect) { @@ -2916,8 +3119,8 @@ state.currentCharges = Math.floor(charges) state.cooldown = cooldown - const progress = state.totalPixels > 0 ? Math.round((state.paintedPixels / state.totalPixels) * 100) : 0 - const remainingPixels = state.totalPixels - state.paintedPixels + const progress = state.totalPixels > 0 ? Math.round(((state.paintedPixels + state.skippedPixels) / state.totalPixels) * 100) : 0 + const remainingPixels = state.totalPixels - state.paintedPixels - state.skippedPixels state.estimatedTime = Utils.calculateEstimatedTime(remainingPixels, state.currentCharges, state.cooldown) @@ -2930,8 +3133,18 @@
${Utils.t("pixels")}
-
${state.paintedPixels}/${state.totalPixels}
+
${state.paintedPixels + state.skippedPixels}/${state.totalPixels}
+
+
${Utils.t("painted")}
+
${state.paintedPixels}
+
+ ${state.skippedPixels > 0 ? ` +
+
${Utils.t("pixelsSkipped", {count: state.skippedPixels})}
+
${state.skippedPixels}
+
+ ` : ''}
${Utils.t("charges")}
${Math.floor(state.currentCharges)}
@@ -3403,6 +3616,34 @@ const pixelX = startX + x const pixelY = startY + y + // Skip pixel if it already has the correct color + if (CONFIG.SKIP_CORRECT_PIXELS) { + console.log(`Checking pixel at (${pixelX},${pixelY}) for skip possibility...`) + + // Get current pixel color from canvas + const currentPixelColor = Utils.getWPlacePixelColor(pixelX, pixelY) + + if (currentPixelColor) { + // Get target color RGB + const targetColor = state.availableColors[colorId] + if (targetColor) { + const matches = Utils.pixelMatches(currentPixelColor, targetColor.rgb) + console.log(`Pixel (${pixelX},${pixelY}): Current RGB(${currentPixelColor.join(',')}) vs Target RGB(${targetColor.rgb.join(',')}) - Matches: ${matches}`) + + if (matches) { + console.log(`✓ Skipping pixel at (${pixelX},${pixelY}) - already correct color`) + state.paintedMap[y][x] = true + state.skippedPixels++ + continue + } else { + console.log(`✗ Pixel (${pixelX},${pixelY}) needs painting`) + } + } + } else { + console.log(`⚠ Could not read pixel color at (${pixelX},${pixelY})`) + } + } + pixelBatch.push({ x: pixelX, y: pixelY,