Merge pull request #44 from jane885511/main

Correct Color Pixel Skip、Traditional Chinese
This commit is contained in:
jamz-uk authored and GitHub committed 2025-08-23 12:00:58 +01:00
commit 0e78846197
1 file changed
+184
+184
View File
@@ -797,6 +797,82 @@
automation: "自动化",
noChargesThreshold: "⌛ 等待次数达到 {threshold}。当前 {current}。下次在 {time}...",
},
"zh-tw": {
title: "WPlace 自動圖像",
toggleOverlay: "切換覆蓋層",
scanColors: "掃描顏色",
uploadImage: "上傳圖像",
resizeImage: "調整大小",
selectPosition: "選擇位置",
startPainting: "開始繪製",
stopPainting: "停止繪製",
checkingColors: "🔍 正在檢查可用顏色...",
noColorsFound: "❌ 請在網站上打開調色板後再試!",
colorsFound: "✅ 找到 {count} 個可用顏色,準備上傳。",
loadingImage: "🖼️ 正在載入圖像...",
imageLoaded: "✅ 圖像已載入,包含 {count} 個有效像素",
imageError: "❌ 載入圖像時出錯",
selectPositionAlert: "請在你想讓作品開始的位置繪製第一個像素!",
waitingPosition: "👆 正在等待你繪製參考像素...",
positionSet: "✅ 位置設定成功!",
positionTimeout: "❌ 選擇位置逾時",
startPaintingMsg: "🎨 開始繪製...",
paintingProgress: "🧱 進度: {painted}/{total} 像素...",
noCharges: "⌛ 無可用次數,等待 {time}...",
paintingStopped: "⏹️ 已被使用者停止",
paintingComplete: "✅ 繪製完成!共繪製 {count} 個像素。",
paintingError: "❌ 繪製過程中出錯",
missingRequirements: "❌ 請先載入圖像並選擇位置",
progress: "進度",
pixels: "像素",
charges: "次數",
estimatedTime: "預計時間",
initMessage: "點擊「上傳圖像」開始",
waitingInit: "正在等待初始化...",
initializingToken: "🔧 正在初始化 Turnstile 令牌產生器...",
tokenReady: "✅ 令牌產生器已就緒 - 可以開始繪製!",
tokenRetryLater: "⚠️ 令牌產生器稍後將重試",
resizeSuccess: "✅ 圖像已調整為 {width}x{height}",
paintingPaused: "⏸️ 在位置 X: {x}, Y: {y} 暫停",
captchaNeeded: "❗ 令牌產生失敗,請稍後再試。",
saveData: "儲存進度",
loadData: "載入進度",
saveToFile: "儲存至檔案",
loadFromFile: "從檔案載入",
dataManager: "資料管理",
autoSaved: "✅ 進度已自動儲存",
dataLoaded: "✅ 進度載入成功",
fileSaved: "✅ 已成功儲存至檔案",
fileLoaded: "✅ 已成功從檔案載入",
noSavedData: "❌ 未找到已儲存進度",
savedDataFound: "✅ 找到已儲存進度!是否載入以繼續?",
savedDate: "儲存時間: {date}",
clickLoadToContinue: "點擊「載入進度」繼續。",
fileError: "❌ 處理檔案時出錯",
invalidFileFormat: "❌ 檔案格式無效",
paintingSpeed: "繪製速度",
pixelsPerSecond: "像素/秒",
speedSetting: "速度: {speed} 像素/秒",
settings: "設定",
botSettings: "機器人設定",
close: "關閉",
language: "語言",
themeSettings: "主題設定",
themeSettingsDesc: "為介面選擇你喜歡的配色主題。",
languageSelectDesc: "選擇你偏好的語言,變更立即生效。",
autoCaptcha: "自動 CAPTCHA 解決",
autoCaptchaDesc: "使用整合的產生器自動產生 Turnstile 令牌,必要時回退到瀏覽器自動化。",
applySettings: "套用設定",
settingsSaved: "✅ 設定儲存成功!",
speedOn: "開啟",
speedOff: "關閉",
cooldownSettings: "冷卻設定",
waitCharges: "等待次數達到",
captchaSolving: "🔑 正在產生 Turnstile 令牌...",
captchaFailed: "❌ 令牌產生失敗。嘗試回退方法...",
automation: "自動化",
noChargesThreshold: "⌛ 等待次數達到 {threshold}。目前 {current}。下次在 {time}...",
},
ja: {
title: "WPlace 自動画像",
toggleOverlay: "オーバーレイ切替",
@@ -1000,6 +1076,8 @@
this.startCoords = null; // { region: {x, y}, pixel: {x, y} }
this.imageBitmap = null;
this.chunkedTiles = new Map(); // Map<"tileX,tileY", ImageBitmap>
this.originalTiles = new Map(); // Map<"tileX,tileY", ImageBitmap> store latest original tile bitmaps
this.originalTilesData = new Map(); // Map<"tileX,tileY", {w,h,data:Uint8ClampedArray}> cache full ImageData for fast pixel reads
this.tileSize = 1000;
this.processPromise = null; // Track ongoing processing
this.lastProcessedHash = null; // Cache invalidation
@@ -1018,6 +1096,8 @@
this.disable();
this.imageBitmap = null;
this.chunkedTiles.clear();
this.originalTiles.clear();
this.originalTilesData.clear();
this.lastProcessedHash = null;
if (this.processPromise) {
this.processPromise = null;
@@ -1193,6 +1273,34 @@
const tileKey = `${tileX},${tileY}`;
const chunkBitmap = this.chunkedTiles.get(tileKey);
// Also store the original tile bitmap for later pixel color checks
try {
const originalBitmap = await createImageBitmap(blobData);
this.originalTiles.set(tileKey, originalBitmap);
// Cache full ImageData for fast pixel access (avoid repeated drawImage/getImageData)
try {
let canvas, ctx;
if (typeof OffscreenCanvas !== 'undefined') {
canvas = new OffscreenCanvas(originalBitmap.width, originalBitmap.height);
ctx = canvas.getContext('2d');
} else {
canvas = document.createElement('canvas');
canvas.width = originalBitmap.width;
canvas.height = originalBitmap.height;
ctx = canvas.getContext('2d');
}
ctx.imageSmoothingEnabled = false;
ctx.drawImage(originalBitmap, 0, 0);
const imgData = ctx.getImageData(0, 0, originalBitmap.width, originalBitmap.height);
// Store typed array copy to avoid retaining large canvas
this.originalTilesData.set(tileKey, { w: originalBitmap.width, h: originalBitmap.height, data: new Uint8ClampedArray(imgData.data) });
} catch (e) {
// If ImageData extraction fails, still keep the bitmap as fallback
console.warn('OverlayManager: could not cache ImageData for', tileKey, e);
}
} catch (e) {
console.warn('OverlayManager: could not create original bitmap for', tileKey, e);
}
if (chunkBitmap) {
try {
// Use faster compositing for better performance
@@ -1214,6 +1322,61 @@
}, '*');
}
// Returns [r,g,b,a] for a pixel inside a region tile (tileX, tileY are region coords)
async getTilePixelColor(tileX, tileY, pixelX, pixelY) {
const tileKey = `${tileX},${tileY}`;
// Prefer cached ImageData if available
const cached = this.originalTilesData.get(tileKey);
if (cached && cached.data && cached.w > 0 && cached.h > 0) {
const x = Math.max(0, Math.min(cached.w - 1, pixelX));
const y = Math.max(0, Math.min(cached.h - 1, pixelY));
const idx = (y * cached.w + x) * 4;
const d = cached.data;
const a = d[idx + 3];
const alphaThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
if (a < alphaThresh) {
// Treat as transparent / unavailable
// Lightweight debug: show when transparency causes skip (only if verbose enabled)
if (window._overlayDebug) console.debug('getTilePixelColor: transparent pixel, skipping', tileKey, x, y, a);
return null;
}
return [d[idx], d[idx + 1], d[idx + 2], a];
}
// Fallback: draw stored bitmap to canvas and read single pixel
const bitmap = this.originalTiles.get(tileKey);
if (!bitmap) return null;
try {
let canvas, ctx;
if (typeof OffscreenCanvas !== 'undefined') {
canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
ctx = canvas.getContext('2d');
} else {
canvas = document.createElement('canvas');
canvas.width = bitmap.width;
canvas.height = bitmap.height;
ctx = canvas.getContext('2d');
}
ctx.imageSmoothingEnabled = false;
ctx.drawImage(bitmap, 0, 0);
const x = Math.max(0, Math.min(bitmap.width - 1, pixelX));
const y = Math.max(0, Math.min(bitmap.height - 1, pixelY));
const data = ctx.getImageData(x, y, 1, 1).data;
const a = data[3];
const alphaThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
if (a < alphaThresh) {
if (window._overlayDebug) console.debug('getTilePixelColor: transparent pixel (fallback), skipping', tileKey, x, y, a);
return null;
}
return [data[0], data[1], data[2], a];
} catch (e) {
console.warn('OverlayManager.getTilePixelColor failed for', tileKey, pixelX, pixelY, e);
return null;
}
}
async _compositeTileOptimized(originalBlob, overlayBitmap) {
const originalBitmap = await createImageBitmap(originalBlob);
const canvas = new OffscreenCanvas(originalBitmap.width, originalBitmap.height);
@@ -4262,6 +4425,7 @@
<option value="fr" ${state.language === 'fr' ? 'selected' : ''} style="background: #2d3748; color: white;">🇫🇷 Français</option>
<option value="tr" ${state.language === 'tr' ? 'selected' : ''} style="background: #2d3748; color: white;">🇹🇷 Türkçe</option>
<option value="zh" ${state.language === 'zh' ? 'selected' : ''} style="background: #2d3748; color: white;">🇨🇳 简体中文</option>
<option value="zh-tw" ${state.language === 'zh-tw' ? 'selected' : ''} style="background: #2d3748; color: white;">🇹🇼 繁體中文</option>
<option value="ja" ${state.language === 'ja' ? 'selected' : ''} style="background: #2d3748; color: white;">🇯🇵 日本語</option>
<option value="ko" ${state.language === 'ko' ? 'selected' : ''} style="background: #2d3748; color: white;">🇰🇷 한국어</option>
</select>
@@ -6193,6 +6357,26 @@
};
}
try {
const tileRegionX = pixelBatch ? (pixelBatch.regionX) : (regionX + adderX);
const tileRegionY = pixelBatch ? (pixelBatch.regionY) : (regionY + adderY);
const tileKeyParts = [(regionX + adderX), (regionY + adderY)];
const existingColorRGBA = await overlayManager.getTilePixelColor(tileKeyParts[0], tileKeyParts[1], pixelX, pixelY).catch(() => null);
if (existingColorRGBA && Array.isArray(existingColorRGBA)) {
const [er, eg, eb] = existingColorRGBA;
const existingColorId = findClosestColor([er, eg, eb], state.availableColors);
// console.log(`pixel at (${pixelX}, ${pixelY}) has color ${existingColorId} it should be ${colorId}`);
if (existingColorId === colorId) {
skippedPixels.alreadyPainted++;
console.log(`Skipped already painted pixel at (${pixelX}, ${pixelY})`);
continue; // Skip
}
}
} catch (e) {
/* ignore */
}
pixelBatch.pixels.push({
x: pixelX,
y: pixelY,