From 88788ebaf42668c0bfd0e3443fe08474d786c511 Mon Sep 17 00:00:00 2001 From: MiraclePatrickLumowa Date: Mon, 18 Aug 2025 15:46:45 +0800 Subject: [PATCH] Update Auto-Image.js --- Auto-Image.js | 615 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 463 insertions(+), 152 deletions(-) diff --git a/Auto-Image.js b/Auto-Image.js index 2e870e5..3acb396 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -10,13 +10,14 @@ MAX: 1000, // Maximum 1000 pixels per second DEFAULT: 5, // Default 5 pixels per second }, + // --- NEW/MODIFIED: Overlay Configuration Defaults --- + OVERLAY_OPACITY_DEFAULT: 0.6, + OVERLAY_SHREAD_EFFECT_DEFAULT: false, // Default OFF as requested + OVERLAY_FOLLOW_PALETTE_DEFAULT: true, // Default ON as requested + // --- END NEW/MODIFIED --- PAINTING_SPEED_ENABLED: false, AUTO_CAPTCHA_ENABLED: false, // Disabled by default COOLDOWN_CHARGE_THRESHOLD: 1, // Default wait threshold - OVERLAY: { - OPACITY_DEFAULT: 0.6, - BLUE_MARBLE_DEFAULT: false, - }, // --- START: Color data from colour-converter.js --- COLOR_PALETTE: [ [0,0,0],[60,60,60],[120,120,120],[170,170,170],[210,210,210],[255,255,255], @@ -178,9 +179,8 @@ // BILINGUAL TEXT STRINGS const TEXT = { - en: { + en: { title: "WPlace Auto-Image", - toggleOverlay: "Toggle Overlay", scanColors: "Scan Colors", uploadImage: "Upload Image", resizeImage: "Resize Image", @@ -248,6 +248,12 @@ captchaFailed: "❌ Auto-CAPTCHA failed. Paint a pixel manually.", automation: "Automation", noChargesThreshold: "⌛ Waiting for charges to reach {threshold}. Currently {current}. Next in {time}...", + overlayConfiguration: "Overlay Configuration", + shreadEffect: "Shread Effect (Blue Marble Style)", + shreadEffectDesc: "Renders the overlay as a pixel grid for better alignment.", + followPalette: "Follow Enabled Color Palette", + followPaletteDesc: "Overlay will only show colors currently enabled in the resize dialog.", + overlayOpacity: "Overlay Opacity", }, ru: { title: "WPlace Авто-Изображение", @@ -318,17 +324,318 @@ captchaFailed: "❌ Не удалось решить CAPTCHA. Нарисуйте пиксель вручную.", automation: "Автоматизация", noChargesThreshold: "⌛ Ожидание зарядов до {threshold}. Сейчас {current}. Следующий через {time}...", -}, + overlayConfiguration: "Настройка наложения", + shreadEffect: "Эффект сетки (стиль Blue Marble)", + shreadEffectDesc: "Отображает наложение в виде сетки пикселей для лучшего выравнивания.", + followPalette: "Следовать включенной палитре цветов", + followPaletteDesc: "Наложение будет показывать только цвета, включенные в диалоге изменения размера.", + overlayOpacity: "Прозрачность наложения", + }, pt: { - // ... other languages - }, + title: "WPlace Auto-Image", + scanColors: "Escanear Cores", + uploadImage: "Upload da Imagem", + resizeImage: "Redimensionar Imagem", + selectPosition: "Selecionar Posição", + startPainting: "Iniciar Pintura", + stopPainting: "Parar Pintura", + checkingColors: "🔍 Verificando cores disponíveis...", + noColorsFound: "❌ Abra a paleta de cores no site e tente novamente!", + colorsFound: "✅ {count} cores encontradas. Pronto para upload.", + loadingImage: "🖼️ Carregando imagem...", + imageLoaded: "✅ Imagem carregada com {count} pixels válidos", + imageError: "❌ Erro ao carregar imagem", + selectPositionAlert: "Pinte o primeiro pixel na localização onde deseja que a arte comece!", + waitingPosition: "👆 Aguardando você pintar o pixel de referência...", + positionSet: "✅ Posição definida com sucesso!", + positionTimeout: "❌ Tempo esgotado para selecionar posição", + startPaintingMsg: "🎨 Iniciando pintura...", + paintingProgress: "🧱 Progresso: {painted}/{total} pixels...", + noCharges: "⌛ Sem cargas. Aguardando {time}...", + paintingStopped: "⏹️ Pintura interrompida pelo usuário", + paintingComplete: "✅ Pintura concluída! {count} pixels pintados.", + paintingError: "❌ Erro durante a pintura", + missingRequirements: "❌ Carregue uma imagem e selecione uma posição primeiro", + progress: "Progresso", + pixels: "Pixels", + charges: "Cargas", + estimatedTime: "Tempo estimado", + initMessage: "Clique em 'Upload da Imagem' para começar", + waitingInit: "Aguardando inicialização...", + resizeSuccess: "✅ Imagem redimensionada para {width}x{height}", + paintingPaused: "⏸️ Pintura pausada na posição X: {x}, Y: {y}", + captchaNeeded: "❗ Token CAPTCHA necessário. Pinte um pixel manualmente para continuar.", + saveData: "Salvar Progresso", + loadData: "Carregar Progresso", + saveToFile: "Salvar em Arquivo", + loadFromFile: "Carregar de Arquivo", + dataManager: "Dados", + autoSaved: "✅ Progresso salvo automaticamente", + dataLoaded: "✅ Progresso carregado com sucesso", + fileSaved: "✅ Salvo em arquivo com sucesso", + fileLoaded: "✅ Carregado de arquivo com sucesso", + noSavedData: "❌ Nenhum progresso salvo encontrado", + savedDataFound: "✅ Progresso salvo encontrado! Carregar para continuar?", + savedDate: "Salvo em: {date}", + clickLoadToContinue: "Clique em 'Carregar Progresso' para continuar.", + fileError: "❌ Erro ao processar arquivo", + invalidFileFormat: "❌ Formato de arquivo inválido", + paintingSpeed: "Velocidade de Pintura", + pixelsPerSecond: "pixels/segundo", + speedSetting: "Velocidade: {speed} pixels/seg", + settings: "Configurações", + botSettings: "Configurações do Bot", + close: "Fechar", + 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.", + autoCaptcha: "Resolvedor de CAPTCHA Automático", + autoCaptchaDesc: "Tenta resolver o CAPTCHA automaticamente simulando a colocação manual de um pixel quando o token expira.", + applySettings: "Aplicar Configurações", + settingsSaved: "✅ Configurações salvas com sucesso!", + cooldownSettings: "Configurações de Cooldown", + waitCharges: "Aguardar até as cargas atingirem", + captchaSolving: "🤖 Tentando resolver o CAPTCHA...", + captchaFailed: "❌ Falha ao resolver CAPTCHA. Pinte um pixel manualmente.", + automation: "Automação", + noChargesThreshold: "⌛ Aguardando cargas atingirem {threshold}. Atual: {current}. Próxima em {time}...", + overlayConfiguration: "Configuração da Sobreposição", + shreadEffect: "Efeito de Grade (Estilo Blue Marble)", + shreadEffectDesc: "Renderiza a sobreposição como uma grade de pixels para melhor alinhamento.", + followPalette: "Seguir Paleta de Cores Ativada", + followPaletteDesc: "A sobreposição mostrará apenas as cores atualmente ativadas na caixa de diálogo de redimensionamento.", + overlayOpacity: "Opacidade da Sobreposição", + }, vi: { - // ... other languages - }, + title: "WPlace Auto-Image", + scanColors: "Quét màu", + uploadImage: "Tải lên hình ảnh", + resizeImage: "Thay đổi kích thước", + selectPosition: "Chọn vị trí", + startPainting: "Bắt đầu vẽ", + stopPainting: "Dừng vẽ", + checkingColors: "🔍 Đang kiểm tra màu sắc có sẵn...", + noColorsFound: "❌ Hãy mở bảng màu trên trang web và thử lại!", + colorsFound: "✅ Tìm thấy {count} màu. Sẵn sàng để tải lên.", + loadingImage: "🖼️ Đang tải hình ảnh...", + imageLoaded: "✅ Đã tải hình ảnh với {count} pixel hợp lệ", + imageError: "❌ Lỗi khi tải hình ảnh", + selectPositionAlert: "Vẽ pixel đầu tiên tại vị trí bạn muốn tác phẩm nghệ thuật bắt đầu!", + waitingPosition: "👆 Đang chờ bạn vẽ pixel tham chiếu...", + positionSet: "✅ Đã đặt vị trí thành công!", + positionTimeout: "❌ Hết thời gian chọn vị trí", + startPaintingMsg: "🎨 Bắt đầu vẽ...", + paintingProgress: "🧱 Tiến trình: {painted}/{total} pixel...", + noCharges: "⌛ Không có điện tích. Đang chờ {time}...", + paintingStopped: "⏹️ Người dùng đã dừng vẽ", + paintingComplete: "✅ Hoàn thành vẽ! Đã vẽ {count} pixel.", + paintingError: "❌ Lỗi trong quá trình vẽ", + missingRequirements: "❌ Hãy tải lên hình ảnh và chọn vị trí trước", + progress: "Tiến trình", + pixels: "Pixel", + charges: "Điện tích", + estimatedTime: "Thời gian ước tính", + initMessage: "Nhấp 'Tải lên hình ảnh' để bắt đầu", + waitingInit: "Đang chờ khởi tạo...", + resizeSuccess: "✅ Đã thay đổi kích thước hình ảnh thành {width}x{height}", + paintingPaused: "⏸️ Tạm dừng vẽ tại vị trí X: {x}, Y: {y}", + captchaNeeded: "❗ Cần token CAPTCHA. Vẽ một pixel thủ công để tiếp tục.", + saveData: "Lưu tiến trình", + loadData: "Tải tiến trình", + saveToFile: "Lưu vào tệp", + loadFromFile: "Tải từ tệp", + dataManager: "Dữ liệu", + autoSaved: "✅ Đã tự động lưu tiến trình", + dataLoaded: "✅ Đã tải tiến trình thành công", + fileSaved: "✅ Đã lưu vào tệp thành công", + fileLoaded: "✅ Đã tải từ tệp thành công", + noSavedData: "❌ Không tìm thấy tiến trình đã lưu", + savedDataFound: "✅ Tìm thấy tiến trình đã lưu! Tải để tiếp tục?", + savedDate: "Đã lưu vào: {date}", + clickLoadToContinue: "Nhấp 'Tải tiến trình' để tiếp tục.", + fileError: "❌ Lỗi khi xử lý tệp", + invalidFileFormat: "❌ Định dạng tệp không hợp lệ", + paintingSpeed: "Tốc độ vẽ", + pixelsPerSecond: "pixel/giây", + speedSetting: "Tốc độ: {speed} pixel/giây", + settings: "Cài đặt", + botSettings: "Cài đặt Bot", + close: "Đóng", + language: "Ngôn ngữ", + 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.", + autoCaptcha: "Tự động giải CAPTCHA", + autoCaptchaDesc: "Tự động cố gắng giải CAPTCHA bằng cách mô phỏng việc đặt pixel thủ công khi token hết hạn.", + applySettings: "Áp dụng cài đặt", + settingsSaved: "✅ Đã lưu cài đặt thành công!", + cooldownSettings: "Cài đặt thời gian chờ", + waitCharges: "Chờ cho đến khi số lần sạc đạt", + captchaSolving: "🤖 Đang cố gắng giải CAPTCHA...", + captchaFailed: "❌ Giải CAPTCHA tự động thất bại. Vui lòng vẽ một pixel thủ công.", + automation: "Tự động hóa", + noChargesThreshold: "⌛ Đang chờ số lần sạc đạt {threshold}. Hiện tại {current}. Lần tiếp theo trong {time}...", + overlayConfiguration: "Cấu hình Lớp phủ", + shreadEffect: "Hiệu ứng Lưới (Kiểu Blue Marble)", + shreadEffectDesc: "Hiển thị lớp phủ dưới dạng lưới pixel để căn chỉnh tốt hơn.", + followPalette: "Theo Bảng màu được Bật", + followPaletteDesc: "Lớp phủ sẽ chỉ hiển thị các màu hiện đang được bật trong hộp thoại thay đổi kích thước.", + overlayOpacity: "Độ mờ của Lớp phủ", + }, fr: { - // ... other languages - }, - } + title: "WPlace Auto-Image", + scanColors: "Scanner les couleurs", + uploadImage: "Télécharger l'image", + resizeImage: "Redimensionner l'image", + selectPosition: "Sélectionner la position", + startPainting: "Commencer à peindre", + stopPainting: "Arrêter de peindre", + checkingColors: "🔍 Vérification des couleurs disponibles...", + noColorsFound: "❌ Ouvrez la palette de couleurs sur le site et réessayez!", + colorsFound: "✅ {count} couleurs trouvées. Prêt à télécharger.", + loadingImage: "🖼️ Chargement de l'image...", + imageLoaded: "✅ Image chargée avec {count} pixels valides", + imageError: "❌ Erreur lors du chargement de l'image", + selectPositionAlert: "Peignez le premier pixel à l'endroit où vous voulez que l'art commence!", + waitingPosition: "👆 En attente que vous peigniez le pixel de référence...", + positionSet: "✅ Position définie avec succès!", + positionTimeout: "❌ Délai d'attente pour la sélection de position", + startPaintingMsg: "🎨 Début de la peinture...", + paintingProgress: "🧱 Progrès: {painted}/{total} pixels...", + noCharges: "⌛ Aucune charge. En attente {time}...", + paintingStopped: "⏹️ Peinture arrêtée par l'utilisateur", + paintingComplete: "✅ Peinture terminée! {count} pixels peints.", + paintingError: "❌ Erreur pendant la peinture", + missingRequirements: "❌ Veuillez charger une image et sélectionner une position d'abord", + progress: "Progrès", + pixels: "Pixels", + charges: "Charges", + estimatedTime: "Temps estimé", + initMessage: "Cliquez sur 'Télécharger l'image' pour commencer", + waitingInit: "En attente d'initialisation...", + resizeSuccess: "✅ Image redimensionnée en {width}x{height}", + paintingPaused: "⏸️ Peinture en pause à la position X: {x}, Y: {y}", + captchaNeeded: "❗ Token CAPTCHA nécessaire. Peignez un pixel manuellement pour continuer.", + saveData: "Sauvegarder le progrès", + loadData: "Charger le progrès", + saveToFile: "Sauvegarder dans un fichier", + loadFromFile: "Charger depuis un fichier", + dataManager: "Données", + autoSaved: "✅ Progrès sauvegardé automatiquement", + dataLoaded: "✅ Progrès chargé avec succès", + fileSaved: "✅ Sauvegardé dans un fichier avec succès", + fileLoaded: "✅ Chargé depuis un fichier avec succès", + noSavedData: "❌ Aucun progrès sauvegardé trouvé", + savedDataFound: "✅ Progrès sauvegardé trouvé! Charger pour continuer?", + savedDate: "Sauvegardé le: {date}", + clickLoadToContinue: "Cliquez sur 'Charger le progrès' pour continuer.", + fileError: "❌ Erreur lors du traitement du fichier", + invalidFileFormat: "❌ Format de fichier invalide", + paintingSpeed: "Vitesse de peinture", + pixelsPerSecond: "pixels/seconde", + speedSetting: "Vitesse: {speed} pixels/sec", + settings: "Paramètres", + botSettings: "Paramètres du Bot", + close: "Fermer", + language: "Langue", + 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.", + autoCaptcha: "Résolveur de CAPTCHA automatique", + autoCaptchaDesc: "Tente automatiquement de résoudre le CAPTCHA en simulant un placement manuel de pixel lorsque le jeton expire.", + applySettings: "Appliquer les paramètres", + settingsSaved: "✅ Paramètres enregistrés avec succès !", + cooldownSettings: "Paramètres de recharge", + waitCharges: "Attendre que les charges atteignent", + captchaSolving: "🤖 Tentative de résolution du CAPTCHA...", + captchaFailed: "❌ Échec de l'Auto-CAPTCHA. Peignez un pixel manuellement.", + automation: "Automatisation", + noChargesThreshold: "⌛ En attente que les charges atteignent {threshold}. Actuel: {current}. Prochaine dans {time}...", + overlayConfiguration: "Configuration de la superposition", + shreadEffect: "Effet de grille (Style Blue Marble)", + shreadEffectDesc: "Affiche la superposition sous forme de grille de pixels pour un meilleur alignement.", + followPalette: "Suivre la palette de couleurs activée", + followPaletteDesc: "La superposition n'affichera que les couleurs actuellement activées dans la boîte de dialogue de redimensionnement.", + overlayOpacity: "Opacité de la superposition", + }, + id: { + title: "WPlace Auto-Image", + scanColors: "Pindai Warna", + uploadImage: "Unggah Gambar", + resizeImage: "Ubah Ukuran Gambar", + selectPosition: "Pilih Posisi", + startPainting: "Mulai Menggambar", + stopPainting: "Hentikan Menggambar", + checkingColors: "🔍 Memeriksa warna yang tersedia...", + noColorsFound: "❌ Buka palet warna di situs dan coba lagi!", + colorsFound: "✅ {count} warna tersedia ditemukan. Siap untuk diunggah.", + loadingImage: "🖼️ Memuat gambar...", + imageLoaded: "✅ Gambar dimuat dengan {count} piksel yang valid", + imageError: "❌ Terjadi kesalahan saat memuat gambar", + selectPositionAlert: "Gambar piksel pertama di lokasi tempat Anda ingin memulai karya seni!", + waitingPosition: "👆 Menunggu Anda menggambar piksel referensi...", + positionSet: "✅ Posisi berhasil diatur!", + positionTimeout: "❌ Waktu untuk memilih posisi telah habis", + startPaintingMsg: "🎨 Mulai menggambar...", + paintingProgress: "🧱 Progres: {painted}/{total} piksel...", + noCharges: "⌛ Tidak ada daya. Menunggu {time}...", + paintingStopped: "⏹️ Penggambaran dihentikan oleh pengguna", + paintingComplete: "✅ Penggambaran selesai! {count} piksel digambar.", + paintingError: "❌ Terjadi kesalahan saat menggambar", + missingRequirements: "❌ Muat gambar dan pilih posisi terlebih dahulu", + progress: "Progres", + pixels: "Piksel", + charges: "Daya", + estimatedTime: "Perkiraan Waktu", + initMessage: "Klik 'Unggah Gambar' untuk memulai", + waitingInit: "Menunggu inisialisasi...", + resizeSuccess: "✅ Ukuran gambar diubah menjadi {width}x{height}", + paintingPaused: "⏸️ Penggambaran dijeda pada posisi X: {x}, Y: {y}", + captchaNeeded: "❗ Token CAPTCHA diperlukan. Gambar satu piksel secara manual untuk melanjutkan.", + saveData: "Simpan Progres", + loadData: "Muat Progres", + saveToFile: "Simpan ke File", + loadFromFile: "Muat dari File", + dataManager: "Manajer Data", + autoSaved: "✅ Progres disimpan secara otomatis", + dataLoaded: "✅ Progres berhasil dimuat", + fileSaved: "✅ Progres berhasil disimpan ke file", + fileLoaded: "✅ Progres berhasil dimuat dari file", + noSavedData: "❌ Tidak ada progres tersimpan yang ditemukan", + savedDataFound: "✅ Progres tersimpan ditemukan! Muat untuk melanjutkan?", + savedDate: "Disimpan pada: {date}", + clickLoadToContinue: "Klik 'Muat Progres' untuk melanjutkan.", + fileError: "❌ Terjadi kesalahan saat memproses file", + invalidFileFormat: "❌ Format file tidak valid", + paintingSpeed: "Kecepatan Menggambar", + pixelsPerSecond: "piksel/detik", + speedSetting: "Kecepatan: {speed} piksel/detik", + settings: "Pengaturan", + botSettings: "Pengaturan Bot", + close: "Tutup", + language: "Bahasa", + themeSettings: "Pengaturan Tema", + themeSettingsDesc: "Pilih tema warna pilihan Anda untuk antarmuka.", + languageSelectDesc: "Pilih bahasa pilihan Anda. Perubahan akan segera berlaku.", + autoCaptcha: "Penyelesai CAPTCHA Otomatis", + autoCaptchaDesc: "Secara otomatis mencoba menyelesaikan CAPTCHA dengan mensimulasikan penempatan piksel manual saat token kedaluwarsa.", + applySettings: "Terapkan Pengaturan", + settingsSaved: "✅ Pengaturan berhasil disimpan!", + cooldownSettings: "Pengaturan Cooldown", + waitCharges: "Tunggu hingga daya mencapai", + captchaSolving: "🤖 Mencoba menyelesaikan CAPTCHA...", + captchaFailed: "❌ Auto-CAPTCHA gagal. Gambar satu piksel secara manual.", + automation: "Otomatisasi", + noChargesThreshold: "⌛ Menunggu daya mencapai {threshold}. Saat ini {current}. Berikutnya dalam {time}...", + overlayConfiguration: "Konfigurasi Hamparan", + shreadEffect: "Efek Kisi (Gaya Blue Marble)", + shreadEffectDesc: "Merender hamparan sebagai kisi piksel untuk penyelarasan yang lebih baik.", + followPalette: "Ikuti Palet Warna yang Diaktifkan", + followPaletteDesc: "Hamparan hanya akan menampilkan warna yang saat ini diaktifkan di dialog ubah ukuran.", + overlayOpacity: "Opasitas Hamparan", + }, +} // GLOBAL STATE const state = { @@ -355,8 +662,11 @@ language: "en", paintingSpeed: CONFIG.PAINTING_SPEED.DEFAULT, // pixels per second cooldownChargeThreshold: CONFIG.COOLDOWN_CHARGE_THRESHOLD, - overlayOpacity: CONFIG.OVERLAY.OPACITY_DEFAULT, - blueMarbleEnabled: CONFIG.OVERLAY.BLUE_MARBLE_DEFAULT, + // --- NEW/MODIFIED: Overlay State --- + overlayOpacity: CONFIG.OVERLAY_OPACITY_DEFAULT, + overlayShreadEffect: CONFIG.OVERLAY_SHREAD_EFFECT_DEFAULT, + overlayFollowPalette: CONFIG.OVERLAY_FOLLOW_PALETTE_DEFAULT, + // --- END NEW/MODIFIED --- } // Placeholder for the resize preview update function @@ -404,8 +714,8 @@ await this.processImageIntoChunks(); } } - - // --- OVERLAY UPDATE: Simplified chunking logic for solid, semi-transparent overlay --- + + // --- NEW/MODIFIED: Complete rewrite of chunking logic to support new features --- async processImageIntoChunks() { if (!this.imageBitmap || !this.startCoords) return; @@ -422,15 +732,17 @@ const endTileX = startRegionX + Math.floor(endPixelX / this.tileSize); const endTileY = startRegionY + Math.floor(endPixelY / this.tileSize); + // For palette check, create a quick lookup Set + const activeColorSet = new Set(state.activeColorPalette.map(rgb => rgb.join(','))); + const shreadSize = 3; + for (let ty = startTileY; ty <= endTileY; ty++) { for (let tx = startTileX; tx <= endTileX; tx++) { const tileKey = `${tx},${ty}`; - // Calculate the portion of the image that overlaps with this tile const imgStartX = (tx - startRegionX) * this.tileSize - startPixelX; const imgStartY = (ty - startRegionY) * this.tileSize - startPixelY; - // Crop coordinates within the source image const sX = Math.max(0, imgStartX); const sY = Math.max(0, imgStartY); const sW = Math.min(imageWidth - sX, this.tileSize - (sX - imgStartX)); @@ -438,47 +750,54 @@ if (sW <= 0 || sH <= 0) continue; - // Destination coordinates on the new chunk canvas const dX = Math.max(0, -imgStartX); const dY = Math.max(0, -imgStartY); + + // Create a temporary canvas to get pixel data of the source image part + const sourcePartCanvas = new OffscreenCanvas(sW, sH); + const sourcePartCtx = sourcePartCanvas.getContext('2d', { willReadFrequently: true }); + sourcePartCtx.imageSmoothingEnabled = false; + sourcePartCtx.drawImage(this.imageBitmap, sX, sY, sW, sH, 0, 0, sW, sH); + const sourcePixelData = sourcePartCtx.getImageData(0, 0, sW, sH).data; const chunkCanvas = new OffscreenCanvas(this.tileSize, this.tileSize); const chunkCtx = chunkCanvas.getContext('2d'); chunkCtx.imageSmoothingEnabled = false; - chunkCtx.drawImage(this.imageBitmap, sX, sY, sW, sH, dX, dY, sW, sH); - - // --- NEW: BLUE MARBLE EFFECT --- - if (state.blueMarbleEnabled) { - const imageData = chunkCtx.getImageData(0, 0, this.tileSize, this.tileSize); - const data = imageData.data; - for (let pixelY = 0; pixelY < this.tileSize; pixelY++) { - for (let pixelX = 0; pixelX < this.tileSize; pixelX++) { - const canvasX = pixelX; - const canvasY = pixelY; - const imageX = canvasX - dX; - const imageY = canvasY - dY; + for (let y = 0; y < sH; y++) { + for (let x = 0; x < sW; x++) { + const idx = (y * sW + x) * 4; + const r = sourcePixelData[idx]; + const g = sourcePixelData[idx + 1]; + const b = sourcePixelData[idx + 2]; + const a = sourcePixelData[idx + 3]; - if ((imageX + imageY) % 2 === 0) { - const index = (canvasY * this.tileSize + canvasX) * 4; - if (data[index + 3] > 0) { - data[index + 3] = 0; - } - } + if (a < CONFIG.TRANSPARENCY_THRESHOLD) continue; + + // Follow Palette Logic + if (state.overlayFollowPalette && !activeColorSet.has(`${r},${g},${b}`)) { + continue; + } + + chunkCtx.fillStyle = `rgba(${r},${g},${b},${a / 255})`; + + // Shread Effect Logic + if (state.overlayShreadEffect) { + chunkCtx.fillRect(dX + x * shreadSize + 1, dY + y * shreadSize + 1, 1, 1); + } else { + chunkCtx.fillRect(dX + x, dY + y, 1, 1); } } - chunkCtx.putImageData(imageData, 0, 0); } - + const chunkBitmap = await chunkCanvas.transferToImageBitmap(); this.chunkedTiles.set(tileKey, chunkBitmap); } } - console.log(`Overlay processed into ${this.chunkedTiles.size} chunks.`); + console.log(`Overlay processed into ${this.chunkedTiles.size} chunks with current settings.`); } - // --- OVERLAY UPDATE: Simplified compositing logic for solid, semi-transparent overlay --- async processAndRespondToTileRequest(eventData) { const { endpoint, blobID, blobData } = eventData; @@ -495,16 +814,18 @@ if (chunkBitmap) { try { const originalTileBitmap = await createImageBitmap(blobData); - const canvas = new OffscreenCanvas(originalTileBitmap.width, originalTileBitmap.height); + const shreadSize = state.overlayShreadEffect ? 3 : 1; + const canvas = new OffscreenCanvas(originalTileBitmap.width * shreadSize, originalTileBitmap.height * shreadSize); const ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; - // Draw original tile first - ctx.drawImage(originalTileBitmap, 0, 0); + // Draw original tile first, scaled up if shread effect is on + ctx.drawImage(originalTileBitmap, 0, 0, canvas.width, canvas.height); - // Set opacity and draw our solid overlay chunk on top + // --- NEW/MODIFIED: Use state for opacity --- ctx.globalAlpha = state.overlayOpacity; - ctx.drawImage(chunkBitmap, 0, 0); + ctx.drawImage(chunkBitmap, 0, 0, chunkBitmap.width * shreadSize, chunkBitmap.height * shreadSize); + // --- END NEW/MODIFIED --- finalBlob = await canvas.convertToBlob({ type: 'image/png' }); } catch (e) { @@ -2650,6 +2971,7 @@ window.addEventListener('message', (event) => { animation: settingsSlideIn 0.4s ease-out; ` + // --- NEW/MODIFIED: Added Overlay Configuration to Settings HTML --- settingsContainer.innerHTML = `
@@ -2677,6 +2999,39 @@ window.addEventListener('message', (event) => {
+ +
+ +
+ +
+ +
+
+ +
+ +
${Math.round(state.overlayOpacity * 100)}%
+
+
+
+
+
- -
- -
- -
-
- Overlay Opacity -
${Math.round(state.overlayOpacity * 100)}%
-
- -
- - -
-
-