diff --git a/Auto-Image.js b/Auto-Image.js index 83bd5b0..066a444 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -1775,6 +1775,111 @@ return r >= wt && g >= wt && b >= wt; }, + resolveColor(targetRgb, palette, exactMatch = false) { + if (!palette || palette.length === 0) return { id: null, rgb: targetRgb }; + + const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}|${ + state.colorMatchingAlgorithm + }|${state.enableChromaPenalty ? 'c' : 'nc'}|${state.chromaPenaltyWeight}|${exactMatch ? 'exact' : 'closest'}`; + + if (colorCache.has(cacheKey)) return colorCache.get(cacheKey); + + // точное совпадение + if (exactMatch) { + const match = palette.find( + (c) => c.rgb[0] === targetRgb[0] && c.rgb[1] === targetRgb[1] && c.rgb[2] === targetRgb[2] + ); + const result = match ? { id: match.id, rgb: [...match.rgb] } : { id: null, rgb: targetRgb }; + colorCache.set(cacheKey, result); + return result; + } + + // проверка на белый через порог + const whiteThreshold = state.customWhiteThreshold || CONFIG.WHITE_THRESHOLD; + if ( + targetRgb[0] >= whiteThreshold && + targetRgb[1] >= whiteThreshold && + targetRgb[2] >= whiteThreshold + ) { + const whiteEntry = palette.find( + (c) => + c.rgb[0] >= whiteThreshold && c.rgb[1] >= whiteThreshold && c.rgb[2] >= whiteThreshold + ); + if (whiteEntry) { + const result = { id: whiteEntry.id, rgb: [...whiteEntry.rgb] }; + colorCache.set(cacheKey, result); + return result; + } + } + + // поиск ближайшего цвета + let bestId = palette[0].id; + let bestRgb = [...palette[0].rgb]; + let bestScore = Infinity; + + if (state.colorMatchingAlgorithm === 'legacy') { + for (let i = 0; i < palette.length; i++) { + const c = palette[i]; + const [r, g, b] = c.rgb; + const rmean = (r + targetRgb[0]) / 2; + const rdiff = r - targetRgb[0]; + const gdiff = g - targetRgb[1]; + const bdiff = b - targetRgb[2]; + const dist = Math.sqrt( + (((512 + rmean) * rdiff * rdiff) >> 8) + + 4 * gdiff * gdiff + + (((767 - rmean) * bdiff * bdiff) >> 8) + ); + if (dist < bestScore) { + bestScore = dist; + bestId = c.id; + bestRgb = [...c.rgb]; + if (dist === 0) break; + } + } + } else { + const [Lt, at, bt] = Utils._lab(targetRgb[0], targetRgb[1], targetRgb[2]); + const targetChroma = Math.sqrt(at * at + bt * bt); + const penaltyWeight = state.enableChromaPenalty ? state.chromaPenaltyWeight || 0.15 : 0; + + for (let i = 0; i < palette.length; i++) { + const c = palette[i]; + const [r, g, b] = c.rgb; + const [L2, a2, b2] = Utils._lab(r, g, b); + const dL = Lt - L2, + da = at - a2, + db = bt - b2; + let dist = dL * dL + da * da + db * db; + + if (penaltyWeight > 0 && targetChroma > 20) { + const candChroma = Math.sqrt(a2 * a2 + b2 * b2); + if (candChroma < targetChroma) { + const cd = targetChroma - candChroma; + dist += cd * cd * penaltyWeight; + } + } + + if (dist < bestScore) { + bestScore = dist; + bestId = c.id; + bestRgb = [...c.rgb]; + if (dist === 0) break; + } + } + } + + const result = { id: bestId, rgb: bestRgb }; + colorCache.set(cacheKey, result); + + // ограничение кеша + if (colorCache.size > 15000) { + const firstKey = colorCache.keys().next().value; + colorCache.delete(firstKey); + } + + return result; + }, + createImageUploader: () => new Promise((resolve) => { const input = document.createElement('input'); @@ -1827,7 +1932,7 @@ }), extractAvailableColors: () => { - const colorElements = document.querySelectorAll('[id^="color-"]'); + const colorElements = document.querySelectorAll('.tooltip button[id^="color-"]'); // Separate available and unavailable colors const availableColors = []; @@ -1838,7 +1943,11 @@ if (id === 0) return; // Skip transparent color const rgbStr = el.style.backgroundColor.match(/\d+/g); - const rgb = rgbStr ? rgbStr.map(Number) : [0, 0, 0]; + if (!rgbStr || rgbStr.length < 3) { + console.warn(`Skipping color element ${el.id} — cannot parse RGB`); + return; + } + const rgb = rgbStr.map(Number); // Find color name from COLOR_MAP const colorInfo = Object.values(CONFIG.COLOR_MAP).find((color) => color.id === id); @@ -2114,54 +2223,83 @@ } }, - saveProgress: () => { - try { - // Pack painted map if available - let paintedMapPacked = null; - if (state.paintedMap && state.imageData) { - const data = Utils.packPaintedMapToBase64( - state.paintedMap, - state.imageData.width, - state.imageData.height - ); - if (data) { - paintedMapPacked = { + buildPaintedMapPacked() { + if (state.paintedMap && state.imageData) { + const data = Utils.packPaintedMapToBase64( + state.paintedMap, + state.imageData.width, + state.imageData.height + ); + if (data) { + return { + width: state.imageData.width, + height: state.imageData.height, + data: data, + }; + } + } + return null; + }, + + buildProgressData() { + return { + timestamp: Date.now(), + version: '2.2', + state: { + totalPixels: state.totalPixels, + paintedPixels: state.paintedPixels, + lastPosition: state.lastPosition, + startPosition: state.startPosition, + region: state.region, + imageLoaded: state.imageLoaded, + colorsChecked: state.colorsChecked, + coordinateMode: state.coordinateMode, + coordinateDirection: state.coordinateDirection, + coordinateSnake: state.coordinateSnake, + blockWidth: state.blockWidth, + blockHeight: state.blockHeight, + availableColors: state.availableColors, + }, + imageData: state.imageData + ? { width: state.imageData.width, height: state.imageData.height, - data: data, - }; - } - } + pixels: Array.from(state.imageData.pixels), + totalPixels: state.imageData.totalPixels, + } + : null, + paintedMapPacked: Utils.buildPaintedMapPacked(), + }; + }, - const progressData = { - timestamp: Date.now(), - version: '2.2', - state: { - totalPixels: state.totalPixels, - paintedPixels: state.paintedPixels, - lastPosition: state.lastPosition, - startPosition: state.startPosition, - region: state.region, - imageLoaded: state.imageLoaded, - colorsChecked: state.colorsChecked, - availableColors: state.availableColors, - // Добавляем настройки генерации координат - coordinateMode: state.coordinateMode, - coordinateDirection: state.coordinateDirection, - coordinateSnake: state.coordinateSnake, - blockWidth: state.blockWidth, - blockHeight: state.blockHeight, - }, - imageData: state.imageData - ? { - width: state.imageData.width, - height: state.imageData.height, - pixels: Array.from(state.imageData.pixels), - totalPixels: state.imageData.totalPixels, - } - : null, - paintedMapPacked: paintedMapPacked, - }; + migrateProgress(saved) { + if (!saved) return null; + + let data = saved; + const ver = data.version; + + // если версия не указана или <= 1.x → сначала в v2 + if (!ver || ver === '1' || ver === '1.0' || ver === '1.1') { + data = Utils.migrateProgressToV2(data); + } + + // если всё ещё старее v2.1 → мигрируем в 2.1 + if (data.version === '2' || data.version === '2.0') { + data = Utils.migrateProgressToV21(data); + } + + // если всё ещё старее v2.2 → мигрируем в 2.2 + if (data.version === '2.1') { + data = Utils.migrateProgressToV22(data); + } + + // теперь точно последняя версия + return data; + }, + + saveProgress: () => { + try { + const progressData = Utils.buildProgressData(state); localStorage.setItem('wplace-bot-progress', JSON.stringify(progressData)); return true; @@ -2176,27 +2314,14 @@ const saved = localStorage.getItem('wplace-bot-progress'); if (!saved) return null; let data = JSON.parse(saved); - const ver = data.version; - let migrated = data; - - // Миграция для новых версий - if (ver === '2.2') { - // already latest - } else if (ver === '2.1') { - migrated = Utils.migrateProgressToV22(data); - } else if (ver === '2' || ver === '2.0') { - migrated = Utils.migrateProgressToV22(Utils.migrateProgressToV21(data)); - } else { - migrated = Utils.migrateProgressToV22(Utils.migrateProgressToV21(data)); - } + const migrated = Utils.migrateProgress(data); if (migrated && migrated !== data) { try { localStorage.setItem('wplace-bot-progress', JSON.stringify(migrated)); } catch {} - data = migrated; } - return data; + return migrated; } catch (error) { console.error('Error loading progress:', error); return null; @@ -2289,47 +2414,7 @@ saveProgressToFile: () => { try { - // Pack painted map if available - let paintedMapPacked = null; - if (state.paintedMap && state.imageData) { - const data = Utils.packPaintedMapToBase64( - state.paintedMap, - state.imageData.width, - state.imageData.height - ); - if (data) { - paintedMapPacked = { - width: state.imageData.width, - height: state.imageData.height, - data: data, - }; - } - } - - const progressData = { - timestamp: Date.now(), - version: '2.1', - state: { - totalPixels: state.totalPixels, - paintedPixels: state.paintedPixels, - lastPosition: state.lastPosition, - startPosition: state.startPosition, - region: state.region, - imageLoaded: state.imageLoaded, - colorsChecked: state.colorsChecked, - availableColors: state.availableColors, - }, - imageData: state.imageData - ? { - width: state.imageData.width, - height: state.imageData.height, - pixels: Array.from(state.imageData.pixels), - totalPixels: state.imageData.totalPixels, - } - : null, - paintedMapPacked: paintedMapPacked, - }; - + const progressData = Utils.buildProgressData(); const filename = `wplace-bot-progress-${new Date() .toISOString() .slice(0, 19) @@ -2348,14 +2433,8 @@ if (!data || !data.state) { throw new Error('Invalid file format'); } - const ver = data.version; - let migrated = data; - if (ver === '2.1') { - } else if (ver === '2' || ver === '2.0') { - migrated = Utils.migrateProgressToV21(data) || data; - } else { - migrated = Utils.migrateProgressToV21(data) || data; - } + const migrated = Utils.migrateProgress(data); + const success = Utils.restoreProgress(migrated); return success; } catch (error) { @@ -2642,90 +2721,9 @@ // COLOR MATCHING FUNCTION - Optimized with caching const colorCache = new Map(); - function findClosestColor(targetRgb, availableColors) { - if (!availableColors || availableColors.length === 0) return 1; - const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}|${ - state.colorMatchingAlgorithm - }|${state.enableChromaPenalty ? 'c' : 'nc'}|${state.chromaPenaltyWeight}`; - if (colorCache.has(cacheKey)) return colorCache.get(cacheKey); - - const whiteThreshold = state.customWhiteThreshold || CONFIG.WHITE_THRESHOLD; - if ( - targetRgb[0] >= whiteThreshold && - targetRgb[1] >= whiteThreshold && - targetRgb[2] >= whiteThreshold - ) { - const whiteEntry = availableColors.find( - (c) => - c.rgb[0] >= whiteThreshold && c.rgb[1] >= whiteThreshold && c.rgb[2] >= whiteThreshold - ); - if (whiteEntry) { - colorCache.set(cacheKey, whiteEntry.id); - return whiteEntry.id; - } - } - - let bestId = availableColors[0].id; - let bestScore = Infinity; - - if (state.colorMatchingAlgorithm === 'legacy') { - for (let i = 0; i < availableColors.length; i++) { - const c = availableColors[i]; - const [r, g, b] = c.rgb; - const rmean = (r + targetRgb[0]) / 2; - const rdiff = r - targetRgb[0]; - const gdiff = g - targetRgb[1]; - const bdiff = b - targetRgb[2]; - const dist = Math.sqrt( - (((512 + rmean) * rdiff * rdiff) >> 8) + - 4 * gdiff * gdiff + - (((767 - rmean) * bdiff * bdiff) >> 8) - ); - if (dist < bestScore) { - bestScore = dist; - bestId = c.id; - if (dist === 0) break; - } - } - } else { - // lab - const [Lt, at, bt] = Utils._lab(targetRgb[0], targetRgb[1], targetRgb[2]); - const targetChroma = Math.sqrt(at * at + bt * bt); - const penaltyWeight = state.enableChromaPenalty ? state.chromaPenaltyWeight || 0.15 : 0; - for (let i = 0; i < availableColors.length; i++) { - const c = availableColors[i]; - const [r, g, b] = c.rgb; - const [L2, a2, b2] = Utils._lab(r, g, b); - const dL = Lt - L2, - da = at - a2, - db = bt - b2; - let dist = dL * dL + da * da + db * db; - if (penaltyWeight > 0 && targetChroma > 20) { - const candChroma = Math.sqrt(a2 * a2 + b2 * b2); - if (candChroma < targetChroma) { - const cd = targetChroma - candChroma; - dist += cd * cd * penaltyWeight; - } - } - if (dist < bestScore) { - bestScore = dist; - bestId = c.id; - if (dist === 0) break; - } - } - } - - colorCache.set(cacheKey, bestId); - if (colorCache.size > 15000) { - const firstKey = colorCache.keys().next().value; - colorCache.delete(firstKey); - } - return bestId; - } - // UI UPDATE FUNCTIONS (declared early to avoid reference errors) let updateUI = () => {}; - let updateStats = () => {}; + let updateStats = (isManualRefresh) => {}; let updateDataButtons = () => {}; function updateActiveColorPalette() { @@ -3271,6 +3269,7 @@ } `; + // noinspection CssInvalidFunction settingsContainer.innerHTML = `
@@ -3556,7 +3555,7 @@

- +