fix: resolve Auto-Trim canvas CORS block with webSecurity, implement dynamic multi-language localization (EN, TR, DE, RU, AR) with English base

This commit is contained in:
Ümit Tunç
2026-05-23 11:01:25 +03:00
parent dba9ac7642
commit 868d7519a4
3 changed files with 406 additions and 56 deletions
+358 -19
View File
@@ -33,6 +33,7 @@ const elements = {
btnShowCredits: document.getElementById('btn-show-credits'),
creditsOverlay: document.getElementById('credits-overlay'),
btnCloseCredits: document.getElementById('btn-close-credits'),
langSelect: document.getElementById('lang-select'),
// Setup Wizard
setupOverlay: document.getElementById('setup-overlay'),
@@ -106,6 +107,7 @@ const elements = {
document.addEventListener('DOMContentLoaded', async () => {
setupTitlebarListeners();
setupToastListeners();
initLanguage(); // Initialize translations immediately on load
// Check if Python virtual environment exists
const { hasVenv } = await window.api.checkPythonSetup();
@@ -424,18 +426,25 @@ function setupToolbarListeners() {
function autoTrimTransparent() {
if (!cropper || !processedTempPath) return;
// We request canvas size matching the full uncropped image dimension
const canvas = cropper.getCroppedCanvas({
width: cropper.getImageData().naturalWidth,
height: cropper.getImageData().naturalHeight
});
// Create a temporary hidden canvas to read pixels directly from the original image element
// This bypasses any cropper canvas restrictions or CORS issues when webSecurity is false
const canvas = document.createElement('canvas');
const img = elements.imageElement;
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
if (!canvas) {
showToast("Trim Warning", "Could not analyze transparent boundaries.", "error");
try {
ctx.drawImage(img, 0, 0);
} catch (err) {
const title = getTrans('toastTrimFailed');
const msg = getTrans('toastTrimFailedMsg') + ": " + err.message;
showToast(title, msg, "error");
return;
}
const ctx = canvas.getContext('2d');
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imgData.data;
@@ -445,11 +454,11 @@ function autoTrimTransparent() {
let maxY = 0;
let foundAlpha = false;
// Loop through pixels in grid to find bounding box of non-transparent values
// Scan alpha channel (index + 3) to locate non-empty boundaries
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const alphaIndex = (y * canvas.width + x) * 4 + 3;
if (data[alphaIndex] > 8) { // Alpha transparency cut-off
if (data[alphaIndex] > 8) { // transparency threshold
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
@@ -460,16 +469,21 @@ function autoTrimTransparent() {
}
if (foundAlpha) {
// Add small padding to avoid clipping exactly
// Add small padding to avoid clipping edge pixels exactly
const pad = 10;
minX = Math.max(0, minX - pad);
minY = Math.max(0, minY - pad);
maxX = Math.min(canvas.width, maxX + pad);
maxY = Math.min(canvas.height, maxY + pad);
// Turn on Crop selection mode visual indicators
cropper.setDragMode('crop');
elements.ctrlCropDrag.classList.remove('active');
elements.ctrlCropBox.classList.add('active');
// Convert pixel coordinates to Cropper canvas boundaries
const canvasData = cropper.getCanvasData();
const scale = canvasData.width / cropper.getImageData().naturalWidth;
const scale = canvasData.width / img.naturalWidth;
// Set crop boundaries
cropper.setCropBoxData({
@@ -479,17 +493,342 @@ function autoTrimTransparent() {
height: (maxY - minY) * scale
});
// Switch to crop selection mode visual indicator
cropper.setDragMode('crop');
elements.ctrlCropDrag.classList.remove('active');
elements.ctrlCropBox.classList.add('active');
showToast("Auto-Trim Complete", "Crop boundaries adjusted securely around subject.", "success");
showToast(getTrans('toastTrimComplete'), getTrans('toastTrimCompleteMsg'), "success");
} else {
showToast("Trim Info", "No transparent border boundaries identified.", "info");
showToast(getTrans('toastTitleInfo'), getTrans('toastTrimFailedMsg'), "info");
}
}
// --- LOCALIZATION ENGINE (i18n) ---
const TRANSLATIONS = {
en: {
title: "Dekupai - Trunçgil AI Decoupage",
sourceFile: "Source File",
noImage: "No image loaded",
fileName: "Name",
fileResolution: "Resolution",
fileSize: "Size",
newImage: "Import New Image",
bgRemoval: "Background Removal",
extractSubject: "Extract Subject",
initAI: "Initializing AI Model...",
loadingWeights: "First launch downloads model (~200MB)",
bgSettings: "Background Settings",
transparent: "Transparent",
solidColor: "Solid Color",
gradient: "Gradient",
selectSolid: "Select Solid Fill",
selectGrad: "Select High-End Gradient",
exportStudio: "Export Studio",
format: "Format",
quality: "Quality",
exportMasterpiece: "Export Masterpiece",
toastTitleInfo: "Info",
toastTitleSuccess: "Success",
toastTitleError: "Error",
toastImageChanged: "Image Changed",
toastImageChangedMsg: "Workspace cleared. Ready for a new image.",
toastTrimComplete: "Auto-Trim Complete",
toastTrimCompleteMsg: "Crop boundaries adjusted securely.",
toastTrimFailed: "Trim Warning",
toastTrimFailedMsg: "Could not analyze transparent boundaries.",
toastSetupSuccess: "Setup Success",
toastSetupSuccessMsg: "Python environment is fully configured.",
toastSetupError: "Setup Error",
toastSetupErrorMsg: "Failed to configure Python environment.",
initWizardTitle: "Initializing AI Engine",
initWizardSubtitle: "Dekupai requires a local Python environment to run the BiRefNet neural network models.",
configureEngine: "Configure Engine (Automatic)",
stepVenv: "Creating isolated virtual environment (.venv)",
stepPip: "Upgrading Python package installer (pip)",
stepDeps: "Installing PyTorch AI Engine (CPU version)",
stepLibs: "Installing HuggingFace Transformers & PIL library",
creditsVision: "Developed under the vision of",
creditsExpertise: "by the engineering expertise of",
creditsDesc: "Dekupai is an advanced deep-learning desktop workspace that provides local, high-precision background removal using BiRefNet neural segmentation networks.",
closeCredits: "Close Credits",
dragDropText: "Drag & Drop Image Here",
dragDropSub: "Supports PNG, JPG, JPEG, or WebP formats",
orText: "OR",
browseFiles: "Browse Local Files",
autoTrim: "Auto-Trim"
},
tr: {
title: "Dekupai - Trunçgil AI Dekupaj",
sourceFile: "Kaynak Dosya",
noImage: "Görsel yüklenmedi",
fileName: "Dosya Adı",
fileResolution: "Çözünürlük",
fileSize: "Boyut",
newImage: "Yeni Görsel Yükle",
bgRemoval: "Arka Plan Temizleme",
extractSubject: "Özneyi Dekupe Et",
initAI: "AI Modeli Başlatılıyor...",
loadingWeights: "İlk çalıştırmada model indirilir (~200MB)",
bgSettings: "Arka Plan Ayarları",
transparent: "Şeffaf",
solidColor: "Tek Renk",
gradient: "Degrade",
selectSolid: "Düz Renk Seçin",
selectGrad: "Premium Degrade Seçin",
exportStudio: "Dışa Aktarma Stüdyosu",
format: "Format",
quality: "Kalite",
exportMasterpiece: "Çalışmayı Dışa Aktar",
toastTitleInfo: "Bilgi",
toastTitleSuccess: "Başarılı",
toastTitleError: "Hata",
toastImageChanged: "Görsel Değiştirildi",
toastImageChangedMsg: "Çalışma alanı temizlendi. Yeni görsel için hazır.",
toastTrimComplete: "Otomatik Kırpma Tamamlandı",
toastTrimCompleteMsg: "Kırpma sınırları güvenli bir şekilde ayarlandı.",
toastTrimFailed: "Kırpma Uyarısı",
toastTrimFailedMsg: "Şeffaf sınırlar analiz edilemedi.",
toastSetupSuccess: "Kurulum Başarılı",
toastSetupSuccessMsg: "Python ortamı tamamen yapılandırıldı.",
toastSetupError: "Kurulum Hatası",
toastSetupErrorMsg: "Python ortamı yapılandırılamadı.",
initWizardTitle: "AI Motoru Başlatılıyor",
initWizardSubtitle: "Dekupai, BiRefNet yapay zeka modelini çalıştırmak için yerel bir Python ortamına ihtiyaç duyar.",
configureEngine: "Motoru Yapılandır (Otomatik)",
stepVenv: "İzole sanal ortam oluşturuluyor (.venv)",
stepPip: "Python paket yöneticisi güncelleniyor (pip)",
stepDeps: "PyTorch AI motoru kuruluyor (CPU versiyonu)",
stepLibs: "HuggingFace Transformers ve PIL kuruluyor",
creditsVision: "Vizyon ve liderlik",
creditsExpertise: "yazılım geliştirme ve mühendislik",
creditsDesc: "Dekupai, BiRefNet yapay sinir ağı segmentasyon modellerini kullanarak yerel ve yüksek hassasiyetli arka plan temizleme sağlayan gelişmiş bir derin öğrenme masaüstü uygulamasıdır.",
closeCredits: "Kapat",
dragDropText: "Görseli Buraya Sürükleyip Bırakın",
dragDropSub: "PNG, JPG, JPEG veya WebP formatlarını destekler",
orText: "VEYA",
browseFiles: "Yerel Dosyalara Göz Atın",
autoTrim: "Oto-Kırp"
},
de: {
title: "Dekupai - Trunçgil AI Dekupage",
sourceFile: "Quelldatei",
noImage: "Kein Bild geladen",
fileName: "Dateiname",
fileResolution: "Auflösung",
fileSize: "Größe",
newImage: "Neues Bild Laden",
bgRemoval: "Hintergrundentfernung",
extractSubject: "Motiv Freistellen",
initAI: "AI-Modell wird initialisiert...",
loadingWeights: "Erster Start lädt Modell herunter (~200MB)",
bgSettings: "Hintergrundeinstellungen",
transparent: "Transparent",
solidColor: "Vollfarbe",
gradient: "Verlauf",
selectSolid: "Volltonfarbe wählen",
selectGrad: "Premium-Verlauf wählen",
exportStudio: "Export Studio",
format: "Format",
quality: "Qualität",
exportMasterpiece: "Meisterwerk Exportieren",
toastTitleInfo: "Info",
toastTitleSuccess: "Erfolg",
toastTitleError: "Fehler",
toastImageChanged: "Bild Geändert",
toastImageChangedMsg: "Arbeitsbereich geleert. Bereit für neues Bild.",
toastTrimComplete: "Auto-Zuschneiden Fertig",
toastTrimCompleteMsg: "Zuschnittsgrenzen sicher angepasst.",
toastTrimFailed: "Zuschnitt Warnung",
toastTrimFailedMsg: "Transparente Grenzen konnten nicht analysiert werden.",
toastSetupSuccess: "Setup erfolgreich",
toastSetupSuccessMsg: "Python-Umgebung ist vollständig konfiguriert.",
toastSetupError: "Setup-Fehler",
toastSetupErrorMsg: "Konfiguration der Python-Umgebung fehlgeschlagen.",
initWizardTitle: "AI-Engine wird initialisiert",
initWizardSubtitle: "Dekupai erfordert eine lokale Python-Umgebung, um die BiRefNet-Modelle auszuführen.",
configureEngine: "Engine konfigurieren (Automatisch)",
stepVenv: "Erstellen einer isolierten virtuellen Umgebung (.venv)",
stepPip: "Upgrade des Python-Paketinstallers (pip)",
stepDeps: "Installation der PyTorch AI-Engine (CPU-Version)",
stepLibs: "Installation der HuggingFace Transformers & PIL Bibliothek",
creditsVision: "Entwickelt unter der Vision von",
creditsExpertise: "durch die Software-Expertise von",
creditsDesc: "Dekupai ist ein fortschrittlicher Deep-Learning-Desktop-Arbeitsbereich, der eine lokale, hochpräzise Hintergrundentfernung mithilfe von BiRefNet-Segmentierungsnetzwerken bietet.",
closeCredits: "Schließen",
dragDropText: "Bild hierher ziehen und ablegen",
dragDropSub: "Unterstützt PNG-, JPG-, JPEG- oder WebP-Formate",
orText: "ODER",
browseFiles: "Lokale Dateien durchsuchen",
autoTrim: "Auto-Trim"
},
ru: {
title: "Dekupai - Trunçgil AI Декупаж",
sourceFile: "Исходный файл",
noImage: "Изображение не загружено",
fileName: "Имя",
fileResolution: "Разрешение",
fileSize: "Размер",
newImage: "Загрузить новое изображение",
bgRemoval: "Удаление фона",
extractSubject: "Вырезать объект",
initAI: "Инициализация модели ИИ...",
loadingWeights: "Первый запуск загружает модель (~200 МБ)",
bgSettings: "Настройки фона",
transparent: "Прозрачный",
solidColor: "Сплошной цвет",
gradient: "Градиент",
selectSolid: "Выберите сплошной цвет",
selectGrad: "Выберите премиум градиент",
exportStudio: "Студия экспорта",
format: "Формат",
quality: "Качество",
exportMasterpiece: "Экспортировать шедевр",
toastTitleInfo: "Информация",
toastTitleSuccess: "Успех",
toastTitleError: "Ошибка",
toastImageChanged: "Изображение изменено",
toastImageChangedMsg: "Рабочая область очищена. Готово к новому изображению.",
toastTrimComplete: "Автообрезка завершена",
toastTrimCompleteMsg: "Границы обрезки успешно скорректированы.",
toastTrimFailed: "Предупреждение об обрезке",
toastTrimFailedMsg: "Не удалось проанализировать прозрачные границы.",
toastSetupSuccess: "Установка успешна",
toastSetupSuccessMsg: "Среда Python полностью настроена.",
toastSetupError: "Ошибка установки",
toastSetupErrorMsg: "Не удалось настроить среду Python.",
initWizardTitle: "Инициализация движка ИИ",
initWizardSubtitle: "Для работы Dekupai требуется локальная среда Python для запуска нейросетевых моделей BiRefNet.",
configureEngine: "Настроить движок (Автоматически)",
stepVenv: "Создание изолированной виртуальной среды (.venv)",
stepPip: "Обновление установщика пакетов Python (pip)",
stepDeps: "Установка движка ИИ PyTorch (версия для CPU)",
stepLibs: "Установка библиотек HuggingFace Transformers и PIL",
creditsVision: "Разработано под руководством",
creditsExpertise: "программная инженерия и разработка",
creditsDesc: "Dekupai — это продвинутое настольное приложение для глубокого обучения, обеспечивающее локальное высокоточное удаление фона с использованием моделей сегментации BiRefNet.",
closeCredits: "Закрыть",
dragDropText: "Перетащите изображение сюда",
dragDropSub: "Поддерживает форматы PNG, JPG, JPEG или WebP",
orText: "ИЛИ",
browseFiles: "Обзор локальных файлов",
autoTrim: "Автообрезка"
},
ar: {
title: "Dekupai - Trunçgil AI إزالة الخلفية",
sourceFile: "الملف المصدر",
noImage: "لم يتم تحميل أي صورة",
fileName: "الاسم",
fileResolution: "الدقة",
fileSize: "الحجم",
newImage: "تحميل صورة جديدة",
bgRemoval: "إزالة الخلفية",
extractSubject: "استخراج العنصر",
initAI: "تهيئة نموذج الذكاء الاصطناعي...",
loadingWeights: "التشغيل الأول يقوم بتحميل النموذج (~200 ميجابايت)",
bgSettings: "إعدادات الخلفية",
transparent: "شفاف",
solidColor: "لون موحد",
gradient: "تدرج لوني",
selectSolid: "اختر لوناً موحداً",
selectGrad: "اختر تدرجاً لونيًا فاخرًا",
exportStudio: "استوديو التصدير",
format: "الصيغة",
quality: "الجودة",
exportMasterpiece: "تصدير العمل الفني",
toastTitleInfo: "معلومات",
toastTitleSuccess: "نجاح",
toastTitleError: "خطأ",
toastImageChanged: "تم تغيير الصورة",
toastImageChangedMsg: "تم مسح مساحة العمل. جاهز لصورة جديدة.",
toastTrimComplete: "اكتمل القص التلقائي",
toastTrimCompleteMsg: "تم ضبط حدود القص بأمان حول العنصر.",
toastTrimFailed: "تحذير القص",
toastTrimFailedMsg: "تعذر تحليل الحدود الشفافة.",
toastSetupSuccess: "نجاح التهيئة",
toastSetupSuccessMsg: "تم تكوين بيئة بايثون بنجاح.",
toastSetupError: "خطأ في التهيئة",
toastSetupErrorMsg: "فشل تكوين بيئة بايثون.",
initWizardTitle: "تهيئة محرك الذكاء الاصطناعي",
initWizardSubtitle: "يتطلب Dekupai بيئة بايثون محلية لتشغيل نماذج الشبكة العصبية BiRefNet.",
configureEngine: "تكوين المحرك (تلقائي)",
stepVenv: "إنشاء بيئة افتراضية معزولة (.venv)",
stepPip: "ترقية مثبت حزم بايثون (pip)",
stepDeps: "تثبيت محرك الذكاء الاصطناعي PyTorch (نسخة المعالج CPU)",
stepLibs: "تثبيت مكتبة HuggingFace Transformers و PIL",
creditsVision: "تم التطوير تحت رؤية",
creditsExpertise: "بواسطة الخبرة البرمجية والهندسية لـ",
creditsDesc: "Dekupai هو مساحة عمل متقدمة للتعلم العميق توفر إزالة محلية ودقيقة للغاية للخلفيات باستخدام نماذج BiRefNet.",
closeCredits: "إغلاق",
dragDropText: "اسحب وأسقط الصورة هنا",
dragDropSub: "يدعم صيغ PNG أو JPG أو JPEG أو WebP",
orText: "أو",
browseFiles: "تصفح الملفات المحلية",
autoTrim: "قص تلقائي"
}
};
let currentLang = 'en'; // default English base language
function changeLanguage(lang) {
currentLang = lang;
localStorage.setItem('dekupai_lang', lang);
if (elements.langSelect) {
elements.langSelect.value = lang;
}
// Update HTML elements marked with data-i18n
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (TRANSLATIONS[lang] && TRANSLATIONS[lang][key]) {
// If element contains an inner span for icon text
const span = el.querySelector('span');
if (span) {
span.innerText = TRANSLATIONS[lang][key];
} else if (el.tagName === 'OPTION') {
el.innerText = TRANSLATIONS[lang][key];
} else {
// Preserving SVG structure inside tags
const svg = el.querySelector('svg');
if (svg) {
// If it has SVG, clear other text and append text node safely
const textNodes = Array.from(el.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
if (textNodes.length > 0) {
textNodes[textNodes.length - 1].nodeValue = " " + TRANSLATIONS[lang][key];
} else {
el.appendChild(document.createTextNode(" " + TRANSLATIONS[lang][key]));
}
} else {
el.innerText = TRANSLATIONS[lang][key];
}
}
}
});
// Handle RTL for Arabic
if (lang === 'ar') {
document.body.dir = 'rtl';
} else {
document.body.dir = 'ltr';
}
// Update original title
document.title = TRANSLATIONS[lang].title;
}
function getTrans(key) {
return (TRANSLATIONS[currentLang] && TRANSLATIONS[currentLang][key]) || TRANSLATIONS['en'][key] || key;
}
function initLanguage() {
const savedLang = localStorage.getItem('dekupai_lang') || 'en';
if (elements.langSelect) {
elements.langSelect.value = savedLang;
elements.langSelect.addEventListener('change', (e) => {
changeLanguage(e.target.value);
});
}
changeLanguage(savedLang);
}
// --- CONTROL TOWER CONTROLLER ---
function setupControlTowerListeners() {
// Remove Background click