From f4fdc1bbfe213afd5ef7e87acc788477d0599c13 Mon Sep 17 00:00:00 2001 From: jamz-uk <222520099+jamz-uk@users.noreply.github.com> Date: Mon, 18 Aug 2025 22:09:14 +0100 Subject: [PATCH] Biggest optimization yet --- Auto-Image.js | 182 ++++++++++++++++++++++++++++---------------------- 1 file changed, 103 insertions(+), 79 deletions(-) diff --git a/Auto-Image.js b/Auto-Image.js index 3ac1409..4674a60 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -134,7 +134,9 @@ }, "Neon Retro": { primary: "#1a1a2e", - secondary: "#16213e", + availableColors: [], // Default to empty array + activeColorPalette: [], // User-selected colors for conversion + paintWhitePixels: true, // Default to ON accent: "#0f3460", text: "#00ff41", highlight: "#ff6b35", @@ -1239,7 +1241,7 @@ totalPixels: state.imageData.totalPixels, } : null, - paintedMap: state.paintedMap ? state.paintedMap.map((row) => Array.from(row)) : null, + paintedMap: state.paintedMap ? Array.from(state.paintedMap) : null, } localStorage.setItem("wplace-bot-progress", JSON.stringify(progressData)) @@ -1273,6 +1275,9 @@ restoreProgress: (savedData) => { try { Object.assign(state, savedData.state) + if (state.availableColors && state.availableColors.length) { + primeColorHelpers(); + } if (savedData.imageData) { state.imageData = { @@ -1282,7 +1287,7 @@ } if (savedData.paintedMap) { - state.paintedMap = savedData.paintedMap.map((row) => Array.from(row)) + state.paintedMap = new Uint8Array(savedData.paintedMap); } return true @@ -1315,7 +1320,7 @@ totalPixels: state.imageData.totalPixels, } : null, - paintedMap: state.paintedMap ? state.paintedMap.map((row) => Array.from(row)) : null, + paintedMap: state.paintedMap ? Array.from(state.paintedMap) : null, } const filename = `wplace-bot-progress-${new Date().toISOString().slice(0, 19).replace(/:/g, "-")}.json` @@ -1502,45 +1507,59 @@ } // COLOR MATCHING FUNCTION - Optimized with caching - const colorCache = new Map() + // Improved color cache using integer keys & squared distance (avoid sqrt) + const colorCache = new Map(); // key: 24-bit int -> color id + let cachedWhiteId = null; + + function primeColorHelpers() { + cachedWhiteId = null; + for (let i = 0; i < state.availableColors.length; i++) { + const c = state.availableColors[i]; + if (c.rgb[0] >= 250 && c.rgb[1] >= 250 && c.rgb[2] >= 250) { + cachedWhiteId = c.id; + break; + } + } + } function findClosestColor(targetRgb, availableColors) { - const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}` + const key = (targetRgb[0] << 16) | (targetRgb[1] << 8) | targetRgb[2]; + const cached = colorCache.get(key); + if (cached !== undefined) return cached; - if (colorCache.has(cacheKey)) { - return colorCache.get(cacheKey) + // Fast path for white-ish + if (cachedWhiteId !== null && targetRgb[0] >= 250 && targetRgb[1] >= 250 && targetRgb[2] >= 250) { + colorCache.set(key, cachedWhiteId); + return cachedWhiteId; } - const isNearWhite = targetRgb[0] >= 250 && targetRgb[1] >= 250 && targetRgb[2] >= 250 - if (isNearWhite) { - const whiteEntry = availableColors.find(c => c.rgb[0] >= 250 && c.rgb[1] >= 250 && c.rgb[2] >= 250) - if (whiteEntry) { - colorCache.set(cacheKey, whiteEntry.id) - return whiteEntry.id - } - } - - let minDistance = Number.POSITIVE_INFINITY - let closestColorId = availableColors[0]?.id || 1 - + let bestId = availableColors[0]?.id || 1; + let bestDist = 1e12; + const tr = targetRgb[0]; + const tg = targetRgb[1]; + const tb = targetRgb[2]; for (let i = 0; i < availableColors.length; i++) { - const color = availableColors[i] - const distance = Utils.colorDistance(targetRgb, color.rgb) - if (distance < minDistance) { - minDistance = distance - closestColorId = color.id - if (distance === 0) break + const c = availableColors[i]; + const cr = c.rgb[0] - tr; + const cg = c.rgb[1] - tg; + const cb = c.rgb[2] - tb; + const dist = cr * cr + cg * cg + cb * cb; // squared distance + if (dist < bestDist) { + bestDist = dist; + bestId = c.id; + if (dist === 0) break; } } - - colorCache.set(cacheKey, closestColorId) - - if (colorCache.size > 10000) { - const firstKey = colorCache.keys().next().value - colorCache.delete(firstKey) + colorCache.set(key, bestId); + // Simple LRU trim + if (colorCache.size > 20000) { + let n = 0; + for (const k of colorCache.keys()) { // trim first 1000 entries + colorCache.delete(k); + if (++n > 1000) break; + } } - - return closestColorId + return bestId; } // UI UPDATE FUNCTIONS (declared early to avoid reference errors) @@ -4084,6 +4103,7 @@ if (!state.colorsChecked) { state.availableColors = availableColors; + primeColorHelpers(); state.colorsChecked = true; updateUI("colorsFound", "success", { count: availableColors.length }); updateStats(); @@ -4339,27 +4359,28 @@ const startRow = state.lastPosition.y || 0 const startCol = state.lastPosition.x || 0 + // Use a flat Uint8Array as bitset for painted map for memory & speed if (!state.paintedMap) { - state.paintedMap = Array(height) - .fill() - .map(() => Array(width).fill(false)) + state.paintedMap = new Uint8Array(width * height); } let pixelBatch = null; try { + let lastStatsUpdate = 0; + const statsUpdateInterval = 1500; // ms outerLoop: for (let y = startRow; y < height; y++) { + const rowOffset = y * width; for (let x = y === startRow ? startCol : 0; x < width; x++) { if (state.stopFlag) { - if (pixelBatch > 0 && pixelBatch.pixels.length > 0) { + if (pixelBatch && pixelBatch.pixels.length > 0) { await sendPixelBatch(pixelBatch.pixels, pixelBatch.regionX, pixelBatch.regionY); } state.lastPosition = { x, y } updateUI("paintingPaused", "warning", { x, y }) break outerLoop } - - if (state.paintedMap[y][x]) continue + if (state.paintedMap[rowOffset + x]) continue const idx = (y * width + x) * 4 const r = pixels[idx] @@ -4388,19 +4409,22 @@ let pixelX = absX % 1000; let pixelY = absY % 1000; - if (!pixelBatch || - pixelBatch.regionX !== regionX + adderX || - pixelBatch.regionY !== regionY + adderY) { + if (!pixelBatch || pixelBatch.regionX !== regionX + adderX || pixelBatch.regionY !== regionY + adderY) { if (pixelBatch && pixelBatch.pixels.length > 0) { let success = await sendPixelBatch(pixelBatch.pixels, pixelBatch.regionX, pixelBatch.regionY); if (success) { - pixelBatch.pixels.forEach((p) => { - state.paintedMap[p.localY][p.localX] = true; + for (let i = 0; i < pixelBatch.pixels.length; i++) { + const p = pixelBatch.pixels[i]; + state.paintedMap[p.localY * width + p.localX] = 1; state.paintedPixels++; - }); + } state.currentCharges -= pixelBatch.pixels.length; - updateStats(); + const now = performance.now(); + if (now - lastStatsUpdate > statsUpdateInterval) { + updateStats(); + lastStatsUpdate = now; + } } } @@ -4446,26 +4470,25 @@ } if (success) { - pixelBatch.pixels.forEach((pixel) => { - state.paintedMap[pixel.localY][pixel.localX] = true; + for (let i = 0; i < pixelBatch.pixels.length; i++) { + const pix = pixelBatch.pixels[i]; + state.paintedMap[pix.localY * width + pix.localX] = 1; state.paintedPixels++; - }) - - state.currentCharges -= pixelBatch.pixels.length; - updateStats() - updateUI("paintingProgress", "default", { - painted: state.paintedPixels, - total: state.totalPixels, - }) - - if (state.paintedPixels % 50 === 0) { - Utils.saveProgress() } - - if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0 && pixelBatch.length > 0) { - const delayPerPixel = 1000 / state.paintingSpeed // ms per pixel - const totalDelay = Math.max(100, delayPerPixel * pixelBatch.length) // minimum 100ms - await Utils.sleep(totalDelay) + state.currentCharges -= pixelBatch.pixels.length; + const now = performance.now(); + if (now - lastStatsUpdate > statsUpdateInterval) { + updateStats(); + updateUI("paintingProgress", "default", { painted: state.paintedPixels, total: state.totalPixels }); + lastStatsUpdate = now; + } + if ((state.paintedPixels & 0x3F) === 0) { // every 64 + Utils.saveProgress(); + } + if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0) { + const delayPerPixel = 1000 / state.paintingSpeed; + const totalDelay = Math.max(50, delayPerPixel * pixelBatch.pixels.length); + await Utils.sleep(totalDelay); } } @@ -4482,12 +4505,12 @@ break; } - updateUI("noChargesThreshold", "warning", { - time: Utils.formatTime(state.cooldown), - threshold: state.cooldownChargeThreshold, - current: state.currentCharges - }); - await updateStats(); + const now = performance.now(); + if (now - lastStatsUpdate > statsUpdateInterval) { + updateUI("noChargesThreshold", "warning", { time: Utils.formatTime(state.cooldown), threshold: state.cooldownChargeThreshold, current: state.currentCharges }); + updateStats(); + lastStatsUpdate = now; + } await Utils.sleep(state.cooldown); } if (state.stopFlag) break outerLoop; @@ -4498,15 +4521,16 @@ if (pixelBatch && pixelBatch.pixels.length > 0 && !state.stopFlag) { const success = await sendPixelBatch(pixelBatch.pixels, pixelBatch.regionX, pixelBatch.regionY); if (success) { - pixelBatch.pixels.forEach((pixel) => { - state.paintedMap[pixel.localY][pixel.localX] = true - state.paintedPixels++ - }) + for (let i = 0; i < pixelBatch.pixels.length; i++) { + const p = pixelBatch.pixels[i]; + state.paintedMap[p.localY * width + p.localX] = 1; + state.paintedPixels++; + } state.currentCharges -= pixelBatch.pixels.length; - if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0 && pixelBatch.length > 0) { - const delayPerPixel = 1000 / state.paintingSpeed // ms per pixel - const totalDelay = Math.max(100, delayPerPixel * pixelBatch.length) // minimum 100ms - await Utils.sleep(totalDelay) + if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0) { + const delayPerPixel = 1000 / state.paintingSpeed; + const totalDelay = Math.max(50, delayPerPixel * pixelBatch.pixels.length); + await Utils.sleep(totalDelay); } } }