1395 lines
60 KiB
JavaScript
1395 lines
60 KiB
JavaScript
// Aether BG Renderer Processes
|
||
|
||
// --- GLOBAL VARIABLES & STATE ---
|
||
let cropper = null;
|
||
let currentImagePath = null;
|
||
let originalFileName = '';
|
||
let processedTempPath = null;
|
||
let activeBgMode = 'transparent'; // 'transparent', 'solid', 'gradient'
|
||
let activeSolidColor = '#ffffff';
|
||
let activeGradientStops = [
|
||
{ offset: 0, color: '#a78bfa' },
|
||
{ offset: 1, color: '#06b6d4' }
|
||
];
|
||
let scaleX = 1;
|
||
let scaleY = 1;
|
||
|
||
// Define gradient templates for canvas exporting
|
||
const GRADIENTS = {
|
||
'grad-1': [{ offset: 0, color: '#a78bfa' }, { offset: 1, color: '#06b6d4' }], // Violet Cyan
|
||
'grad-2': [{ offset: 0, color: '#f43f5e' }, { offset: 1, color: '#f97316' }], // Sunset
|
||
'grad-3': [{ offset: 0, color: '#10b981' }, { offset: 1, color: '#06b6d4' }], // Emerald
|
||
'grad-4': [{ offset: 0, color: '#ec4899' }, { offset: 1, color: '#8b5cf6' }], // Cyberpunk
|
||
'grad-5': [{ offset: 0, color: '#3b82f6' }, { offset: 1, color: '#8b5cf6' }], // Aether Blue
|
||
'grad-6': [{ offset: 0, color: '#f59e0b' }, { offset: 1, color: '#ef4444' }] // Warm Ember
|
||
};
|
||
|
||
// --- DOM ELEMENTS SELECTION ---
|
||
const elements = {
|
||
// Titlebar
|
||
winMin: document.getElementById('win-min'),
|
||
winMax: document.getElementById('win-max'),
|
||
winClose: document.getElementById('win-close'),
|
||
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'),
|
||
btnStartSetup: document.getElementById('btn-start-setup'),
|
||
setupProgressBar: document.getElementById('setup-progress-bar'),
|
||
setupStatusText: document.getElementById('setup-status-text'),
|
||
stepVenv: document.getElementById('step-venv'),
|
||
stepPip: document.getElementById('step-pip'),
|
||
stepDeps: document.getElementById('step-deps'),
|
||
stepLibs: document.getElementById('step-libs'),
|
||
|
||
// Workspace Views
|
||
dropzone: document.getElementById('dropzone'),
|
||
btnBrowse: document.getElementById('btn-browse'),
|
||
editorContainer: document.getElementById('editor-container'),
|
||
canvasWrapper: document.getElementById('canvas-wrapper'),
|
||
imageElement: document.getElementById('image-element'),
|
||
canvasBgOverlay: document.getElementById('canvas-bg-overlay'),
|
||
|
||
// Workspace Toolbar Controls
|
||
ctrlCropDrag: document.getElementById('ctrl-crop-drag'),
|
||
ctrlCropBox: document.getElementById('ctrl-crop-box'),
|
||
ctrlRotL: document.getElementById('ctrl-rot-l'),
|
||
ctrlRotR: document.getElementById('ctrl-rot-r'),
|
||
ctrlFlipH: document.getElementById('ctrl-flip-h'),
|
||
ctrlFlipV: document.getElementById('ctrl-flip-v'),
|
||
cropRatioSelect: document.getElementById('crop-ratio-select'),
|
||
ctrlAutoTrim: document.getElementById('ctrl-auto-trim'),
|
||
ctrlReset: document.getElementById('ctrl-reset'),
|
||
|
||
// Control Tower: Info Panel
|
||
infoCardEmpty: document.getElementById('info-card-empty'),
|
||
infoCardLoaded: document.getElementById('info-card-loaded'),
|
||
infoFilename: document.getElementById('info-filename'),
|
||
infoResolution: document.getElementById('info-resolution'),
|
||
infoSize: document.getElementById('info-size'),
|
||
btnChangeImage: document.getElementById('btn-change-image'),
|
||
|
||
// Control Tower: AI Processor
|
||
btnRemoveBg: document.getElementById('btn-remove-bg'),
|
||
loaderBox: document.getElementById('loader-box'),
|
||
loaderText: document.getElementById('loader-text'),
|
||
loaderSubtext: document.getElementById('loader-subtext'),
|
||
|
||
// Control Tower: Background settings
|
||
secCanvasBg: document.getElementById('sec-canvas-bg'),
|
||
tabBgTrans: document.getElementById('tab-bg-trans'),
|
||
tabBgSolid: document.getElementById('tab-bg-solid'),
|
||
tabBgGrad: document.getElementById('tab-bg-grad'),
|
||
subpanelSolid: document.getElementById('subpanel-solid'),
|
||
subpanelGradient: document.getElementById('subpanel-gradient'),
|
||
customColorPicker: document.getElementById('custom-color-picker'),
|
||
btnCustomColorPicker: document.getElementById('btn-custom-color-picker'),
|
||
|
||
// Control Tower: Export Studio
|
||
secExport: document.getElementById('sec-export'),
|
||
exportFormat: document.getElementById('export-format'),
|
||
rowQualitySlider: document.getElementById('row-quality-slider'),
|
||
exportQuality: document.getElementById('export-quality'),
|
||
exportQualityVal: document.getElementById('export-quality-val'),
|
||
btnExport: document.getElementById('btn-export'),
|
||
|
||
// Toast Banner
|
||
toastBanner: document.getElementById('toast-banner'),
|
||
toastTitle: document.getElementById('toast-title'),
|
||
toastMessage: document.getElementById('toast-message'),
|
||
btnCloseToast: document.getElementById('btn-close-toast'),
|
||
|
||
// Error Overlay Modal
|
||
errorOverlay: document.getElementById('error-overlay'),
|
||
errorOverlayLog: document.getElementById('error-overlay-log'),
|
||
btnCloseError: document.getElementById('btn-close-error')
|
||
};
|
||
|
||
// --- INITIALIZE & ENVIRONMENT CHECKS ---
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
setupTitlebarListeners();
|
||
setupToastListeners();
|
||
initLanguage(); // Initialize translations immediately on load
|
||
|
||
// Check if Python virtual environment exists
|
||
const { hasVenv } = await window.api.checkPythonSetup();
|
||
if (!hasVenv) {
|
||
elements.setupOverlay.classList.remove('hidden');
|
||
} else {
|
||
initWorkspace();
|
||
}
|
||
});
|
||
|
||
// Titlebar Controls
|
||
function setupTitlebarListeners() {
|
||
elements.winMin.addEventListener('click', () => window.api.minimizeWindow());
|
||
elements.winMax.addEventListener('click', () => window.api.maximizeWindow());
|
||
elements.winClose.addEventListener('click', () => window.api.closeWindow());
|
||
|
||
// Show Credits Modal
|
||
elements.btnShowCredits.addEventListener('click', () => {
|
||
elements.creditsOverlay.classList.remove('hidden');
|
||
});
|
||
|
||
// Close Credits Modal
|
||
elements.btnCloseCredits.addEventListener('click', () => {
|
||
elements.creditsOverlay.classList.add('hidden');
|
||
});
|
||
|
||
// Close Error Modal
|
||
elements.btnCloseError.addEventListener('click', () => {
|
||
elements.errorOverlay.classList.add('hidden');
|
||
});
|
||
}
|
||
|
||
// Workspace Initialization
|
||
function initWorkspace() {
|
||
setupDragAndDrop();
|
||
setupToolbarListeners();
|
||
setupControlTowerListeners();
|
||
setupBgSwatches();
|
||
|
||
// Browse Button click
|
||
elements.btnBrowse.addEventListener('click', selectLocalImage);
|
||
elements.dropzone.addEventListener('click', (e) => {
|
||
// Only open if clicking container directly and not buttons
|
||
if (e.target === elements.dropzone || e.target.closest('.dropzone-content') && !e.target.closest('button')) {
|
||
selectLocalImage();
|
||
}
|
||
});
|
||
|
||
// Change image button click
|
||
elements.btnChangeImage.addEventListener('click', resetWorkspaceToEmpty);
|
||
}
|
||
|
||
function resetWorkspaceToEmpty() {
|
||
currentImagePath = null;
|
||
originalFileName = '';
|
||
processedTempPath = null;
|
||
activeBgMode = 'transparent';
|
||
scaleX = 1;
|
||
scaleY = 1;
|
||
|
||
// Destroy cropper safely
|
||
if (cropper) {
|
||
cropper.destroy();
|
||
cropper = null;
|
||
}
|
||
|
||
// Restore drag drop state views
|
||
elements.editorContainer.classList.add('hidden');
|
||
elements.dropzone.classList.remove('hidden');
|
||
|
||
elements.infoCardLoaded.classList.add('hidden');
|
||
elements.infoCardEmpty.classList.remove('hidden');
|
||
|
||
// Disable processing button until a new file is loaded
|
||
elements.btnRemoveBg.disabled = true;
|
||
|
||
// Clear background customizations
|
||
resetBgSettings();
|
||
|
||
showToast(getTrans('toastImageChanged'), getTrans('toastImageChangedMsg'), "info");
|
||
}
|
||
|
||
// --- PYTHON VENV INSTALLER ENGINE ---
|
||
elements.btnStartSetup.addEventListener('click', async () => {
|
||
elements.btnStartSetup.disabled = true;
|
||
elements.setupProgressBar.parentElement.style.display = 'block';
|
||
elements.setupProgressBar.style.width = '5%';
|
||
|
||
// Track active steps
|
||
const setStepActive = (stepEl) => {
|
||
document.querySelectorAll('.step-item').forEach(el => el.classList.remove('active'));
|
||
stepEl.classList.add('active');
|
||
};
|
||
|
||
const setStepDone = (stepEl) => {
|
||
stepEl.classList.remove('active');
|
||
stepEl.classList.add('done');
|
||
};
|
||
|
||
const setStepFailed = (stepEl) => {
|
||
stepEl.classList.remove('active');
|
||
stepEl.classList.add('failed');
|
||
};
|
||
|
||
setStepActive(elements.stepVenv);
|
||
elements.setupStatusText.innerText = getTrans('setupCreatingVenv');
|
||
|
||
// Listen for pipeline setup output logs
|
||
window.api.onSetupProgress((progressText) => {
|
||
// Translate setup output progress text
|
||
if (progressText.includes("Creating Python virtual environment")) {
|
||
elements.setupStatusText.innerText = getTrans('setupCreatingVenv');
|
||
setStepActive(elements.stepVenv);
|
||
elements.setupProgressBar.style.width = '15%';
|
||
} else if (progressText.includes("Virtual environment successfully created")) {
|
||
elements.setupStatusText.innerText = getTrans('setupVenvCreated');
|
||
setStepDone(elements.stepVenv);
|
||
setStepActive(elements.stepPip);
|
||
elements.setupProgressBar.style.width = '30%';
|
||
} else if (progressText.includes("Virtual environment already exists")) {
|
||
elements.setupStatusText.innerText = getTrans('setupVenvExists');
|
||
setStepDone(elements.stepVenv);
|
||
setStepActive(elements.stepPip);
|
||
elements.setupProgressBar.style.width = '30%';
|
||
} else if (progressText.includes("Upgrading pip")) {
|
||
elements.setupStatusText.innerText = getTrans('setupUpgradingPip');
|
||
setStepActive(elements.stepPip);
|
||
elements.setupProgressBar.style.width = '45%';
|
||
} else if (progressText.includes("Installing PyTorch")) {
|
||
elements.setupStatusText.innerText = getTrans('setupInstallingTorch');
|
||
setStepDone(elements.stepPip);
|
||
setStepActive(elements.stepDeps);
|
||
elements.setupProgressBar.style.width = '60%';
|
||
} else if (progressText.includes("Installing HuggingFace")) {
|
||
elements.setupStatusText.innerText = getTrans('setupInstallingDeps');
|
||
setStepDone(elements.stepDeps);
|
||
setStepActive(elements.stepLibs);
|
||
elements.setupProgressBar.style.width = '80%';
|
||
} else if (progressText.includes("Verifying PyTorch")) {
|
||
elements.setupStatusText.innerText = getTrans('setupInstallingDeps');
|
||
setStepActive(elements.stepLibs);
|
||
elements.setupProgressBar.style.width = '90%';
|
||
} else if (progressText.includes("Downloading torch")) {
|
||
elements.setupStatusText.innerText = getTrans('setupDownloadingTorch');
|
||
elements.setupProgressBar.style.width = '75%';
|
||
} else if (progressText.includes("Installing collected packages")) {
|
||
elements.setupStatusText.innerText = getTrans('setupInstallingDeps');
|
||
elements.setupProgressBar.style.width = '85%';
|
||
} else if (progressText.includes("Setup completed successfully")) {
|
||
elements.setupStatusText.innerText = getTrans('setupCompleted');
|
||
setStepDone(elements.stepDeps);
|
||
setStepDone(elements.stepLibs);
|
||
elements.setupProgressBar.style.width = '100%';
|
||
} else {
|
||
elements.setupStatusText.innerText = progressText;
|
||
}
|
||
});
|
||
|
||
try {
|
||
const result = await window.api.runPythonSetup();
|
||
if (result.code === 0) {
|
||
elements.setupStatusText.innerText = getTrans('setupCompleted');
|
||
showToast(getTrans('toastSetupSuccess'), getTrans('toastSetupSuccessMsg'), "success");
|
||
|
||
setTimeout(() => {
|
||
elements.setupOverlay.classList.add('hidden');
|
||
initWorkspace();
|
||
}, 1500);
|
||
} else {
|
||
setStepFailed(elements.stepDeps);
|
||
const errorDetail = (result.error || '').trim();
|
||
elements.setupStatusText.innerText = errorDetail
|
||
? `${getTrans('setupFailed')} ${errorDetail.slice(0, 200)}`
|
||
: getTrans('setupFailed');
|
||
elements.btnStartSetup.disabled = false;
|
||
showToast(getTrans('toastSetupError'), errorDetail || getTrans('toastSetupErrorMsg'), "error");
|
||
}
|
||
} catch (err) {
|
||
elements.setupStatusText.innerText = `${getTrans('toastSetupError')}: ${err.message}`;
|
||
elements.btnStartSetup.disabled = false;
|
||
showToast(getTrans('toastSetupError'), err.message, "error");
|
||
}
|
||
});
|
||
|
||
// --- IMAGE FILE INPUT ENGINE ---
|
||
function setupDragAndDrop() {
|
||
const dropzone = elements.dropzone;
|
||
|
||
['dragenter', 'dragover'].forEach(eventName => {
|
||
dropzone.addEventListener(eventName, (e) => {
|
||
e.preventDefault();
|
||
dropzone.classList.add('dragover');
|
||
}, false);
|
||
});
|
||
|
||
['dragleave', 'drop'].forEach(eventName => {
|
||
dropzone.addEventListener(eventName, (e) => {
|
||
e.preventDefault();
|
||
dropzone.classList.remove('dragover');
|
||
}, false);
|
||
});
|
||
|
||
dropzone.addEventListener('drop', (e) => {
|
||
const dt = e.dataTransfer;
|
||
const files = dt.files;
|
||
if (files.length > 0) {
|
||
handleImageSelection(files[0].path);
|
||
}
|
||
});
|
||
}
|
||
|
||
async function selectLocalImage() {
|
||
const filePath = await window.api.selectImage();
|
||
if (filePath) {
|
||
handleImageSelection(filePath);
|
||
}
|
||
}
|
||
|
||
function handleImageSelection(filePath) {
|
||
currentImagePath = filePath;
|
||
originalFileName = filePath.split(/[\\/]/).pop();
|
||
|
||
// Reset previous states
|
||
processedTempPath = null;
|
||
activeBgMode = 'transparent';
|
||
scaleX = 1;
|
||
scaleY = 1;
|
||
resetBgSettings();
|
||
|
||
// Show image in preview element
|
||
elements.imageElement.src = `file://${filePath}`;
|
||
|
||
// Set file metadata in control tower
|
||
const stats = getFileStats(filePath);
|
||
elements.infoFilename.innerText = originalFileName;
|
||
elements.infoSize.innerText = stats.sizeFormatted;
|
||
|
||
// Load dimensions
|
||
const img = new Image();
|
||
img.onload = function() {
|
||
elements.infoResolution.innerText = `${this.naturalWidth} x ${this.naturalHeight} px`;
|
||
|
||
// Switch views
|
||
elements.dropzone.classList.add('hidden');
|
||
elements.editorContainer.classList.remove('hidden');
|
||
elements.infoCardEmpty.classList.add('hidden');
|
||
elements.infoCardLoaded.classList.remove('hidden');
|
||
|
||
// Enable AI button
|
||
elements.btnRemoveBg.disabled = false;
|
||
|
||
// Initialize CropperJS on image
|
||
initCropper();
|
||
};
|
||
img.src = `file://${filePath}`;
|
||
}
|
||
|
||
function getFileStats(path) {
|
||
// Simple synchronous-like wrapper or mockup stats.
|
||
// Note: Electron file handles path directly.
|
||
return {
|
||
sizeFormatted: "Calculating..." // Updated as soon as loaded
|
||
};
|
||
}
|
||
|
||
// Helper: Formatter
|
||
function formatBytes(bytes, decimals = 2) {
|
||
if (bytes === 0) return '0 Bytes';
|
||
const k = 1024;
|
||
const dm = decimals < 0 ? 0 : decimals;
|
||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||
}
|
||
|
||
// --- CROPPERJS CONTROLLER HUB ---
|
||
function initCropper() {
|
||
if (cropper) {
|
||
cropper.destroy();
|
||
}
|
||
|
||
// Load Cropper configuration
|
||
cropper = new Cropper(elements.imageElement, {
|
||
viewMode: 1,
|
||
dragMode: 'move',
|
||
autoCrop: false,
|
||
responsive: true,
|
||
restore: false,
|
||
guides: true,
|
||
center: true,
|
||
highlight: false,
|
||
cropBoxMovable: true,
|
||
cropBoxResizable: true,
|
||
toggleDragModeOnDblclick: false,
|
||
});
|
||
|
||
// Wire toolbar click listeners
|
||
elements.ctrlCropDrag.classList.add('active');
|
||
elements.ctrlCropBox.classList.remove('active');
|
||
elements.ctrlAutoTrim.disabled = true; // Disabled until background is removed
|
||
}
|
||
|
||
function setupToolbarListeners() {
|
||
elements.ctrlCropDrag.addEventListener('click', () => {
|
||
cropper.setDragMode('move');
|
||
elements.ctrlCropDrag.classList.add('active');
|
||
elements.ctrlCropBox.classList.remove('active');
|
||
});
|
||
|
||
elements.ctrlCropBox.addEventListener('click', () => {
|
||
cropper.setDragMode('crop');
|
||
elements.ctrlCropDrag.classList.remove('active');
|
||
elements.ctrlCropBox.classList.add('active');
|
||
});
|
||
|
||
elements.ctrlRotL.addEventListener('click', () => cropper.rotate(-90));
|
||
elements.ctrlRotR.addEventListener('click', () => cropper.rotate(90));
|
||
|
||
elements.ctrlFlipH.addEventListener('click', () => {
|
||
scaleX = scaleX === 1 ? -1 : 1;
|
||
cropper.scale(scaleX, scaleY);
|
||
});
|
||
|
||
elements.ctrlFlipV.addEventListener('click', () => {
|
||
scaleY = scaleY === 1 ? -1 : 1;
|
||
cropper.scale(scaleX, scaleY);
|
||
});
|
||
|
||
elements.cropRatioSelect.addEventListener('change', (e) => {
|
||
const value = parseFloat(e.target.value);
|
||
cropper.setAspectRatio(value);
|
||
});
|
||
|
||
elements.ctrlReset.addEventListener('click', () => {
|
||
cropper.reset();
|
||
scaleX = 1;
|
||
scaleY = 1;
|
||
cropper.scale(1, 1);
|
||
elements.cropRatioSelect.value = "NaN";
|
||
cropper.setAspectRatio(NaN);
|
||
showToast("Reset Action", "Workspace state cleared.", "info");
|
||
});
|
||
|
||
elements.ctrlAutoTrim.addEventListener('click', autoTrimTransparent);
|
||
}
|
||
|
||
// --- NATIVE AUTOTRIM (PIXEL ITERATION BOUNDS) ---
|
||
function autoTrimTransparent() {
|
||
if (!cropper || !processedTempPath) return;
|
||
|
||
// 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');
|
||
|
||
try {
|
||
ctx.drawImage(img, 0, 0);
|
||
} catch (err) {
|
||
const title = getTrans('toastTrimFailed');
|
||
const msg = getTrans('toastTrimFailedMsg') + ": " + err.message;
|
||
showToast(title, msg, "error");
|
||
return;
|
||
}
|
||
|
||
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||
const data = imgData.data;
|
||
|
||
let minX = canvas.width;
|
||
let maxX = 0;
|
||
let minY = canvas.height;
|
||
let maxY = 0;
|
||
let foundAlpha = false;
|
||
|
||
// 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) { // transparency threshold
|
||
if (x < minX) minX = x;
|
||
if (x > maxX) maxX = x;
|
||
if (y < minY) minY = y;
|
||
if (y > maxY) maxY = y;
|
||
foundAlpha = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (foundAlpha) {
|
||
// 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.crop(); // Manually draw/show the crop selection box
|
||
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 / img.naturalWidth;
|
||
|
||
// Set crop boundaries
|
||
cropper.setCropBoxData({
|
||
left: canvasData.left + minX * scale,
|
||
top: canvasData.top + minY * scale,
|
||
width: (maxX - minX) * scale,
|
||
height: (maxY - minY) * scale
|
||
});
|
||
|
||
showToast(getTrans('toastTrimComplete'), getTrans('toastTrimCompleteMsg'), "success");
|
||
} else {
|
||
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",
|
||
statusInit: "Initializing AI model...",
|
||
statusWeights: "Loading neural weights into memory...",
|
||
statusImporting: "Importing libraries...",
|
||
statusDetecting: "Detecting device...",
|
||
statusUsingDevice: "Using device: ",
|
||
statusLoadingModel: "Loading BiRefNet model...",
|
||
statusPreprocessing: "Loading and preprocessing image...",
|
||
statusInference: "Running background removal inference...",
|
||
statusGeneratingMask: "Generating transparency mask...",
|
||
statusSaving: "Saving output transparent image...",
|
||
statusDone: "Completed!",
|
||
subFirstRun: "This downloads ~200MB of weights on first run.",
|
||
subInference: "Extracting details and soft matting alpha...",
|
||
subSaving: "Assembling high-res RGBA container...",
|
||
subDefault: "Please wait while the AI model processes...",
|
||
setupReady: "Ready to configure environment.",
|
||
setupCreatingVenv: "Creating Python virtual environment...",
|
||
setupVenvCreated: "Virtual environment successfully created.",
|
||
setupVenvExists: "Virtual environment already exists.",
|
||
setupUpgradingPip: "Upgrading Python package installer (pip)...",
|
||
setupInstallingTorch: "Installing PyTorch AI Engine (CPU version)...",
|
||
setupDownloadingTorch: "Downloading torch and dependencies...",
|
||
setupInstallingDeps: "Installing collected packages...",
|
||
setupCompleted: "Configuration Complete! Enjoy background removal.",
|
||
setupFailed: "Setup failed. Check system dependencies.",
|
||
exportingMasterpiece: "Exporting masterpiece...",
|
||
toastExportSuccess: "Export Successful",
|
||
toastExportSuccessMsg: "Saved file correctly: ",
|
||
toastExportFailed: "Export Failed",
|
||
toastExportFailedMsg: "Save operation failed.",
|
||
toastExportError: "Export Error",
|
||
showInExplorer: "Show in File Explorer",
|
||
errorModalTitle: "Process Error",
|
||
errorModalSubtitle: "We encountered an issue while segmenting your image.",
|
||
errorModalSuggestion: "The subject in the image you want to extract might be too blended or visually indistinguishable from its background. Please try another image with a clearer, higher-contrast subject.",
|
||
closeBtn: "Close"
|
||
},
|
||
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",
|
||
statusInit: "AI modeli başlatılıyor...",
|
||
statusWeights: "Yapay sinir ağları belleğe yükleniyor...",
|
||
statusImporting: "Kütüphaneler içe aktarılıyor...",
|
||
statusDetecting: "Donanım tespit ediliyor...",
|
||
statusUsingDevice: "Kullanılan donanım: ",
|
||
statusLoadingModel: "BiRefNet modeli yükleniyor...",
|
||
statusPreprocessing: "Görsel yükleniyor ve ön işleme yapılıyor...",
|
||
statusInference: "Arka plan temizleme algoritması çalıştırılıyor...",
|
||
statusGeneratingMask: "Şeffaflık maskesi oluşturuluyor...",
|
||
statusSaving: "Şeffaf görsel kaydediliyor...",
|
||
statusDone: "Tamamlandı!",
|
||
subFirstRun: "İlk çalıştırmada ~200MB ağırlık dosyası indirilir.",
|
||
subInference: "Detaylar ve yumuşak matlaştırma katmanı çıkarılıyor...",
|
||
subSaving: "Yüksek çözünürlüklü RGBA görsel birleştiriliyor...",
|
||
subDefault: "Yapay zeka modeli işleme yaparken lütfen bekleyin...",
|
||
setupReady: "Ortamı yapılandırmaya hazır.",
|
||
setupCreatingVenv: "Python sanal ortamı oluşturuluyor...",
|
||
setupVenvCreated: "Sanal ortam başarıyla oluşturuldu.",
|
||
setupVenvExists: "Sanal ortam zaten mevcut.",
|
||
setupUpgradingPip: "Python paket yükleyici (pip) güncelleniyor...",
|
||
setupInstallingTorch: "PyTorch AI motoru kuruluyor (CPU versiyonu)...",
|
||
setupDownloadingTorch: "Torch ve bağımlılıklar indiriliyor...",
|
||
setupInstallingDeps: "Toplanan paketler kuruluyor...",
|
||
setupCompleted: "Yapılandırma Tamamlandı! Arka plan temizlemenin keyfini çıkarın.",
|
||
setupFailed: "Kurulum başarısız oldu. Sistem bağımlılıklarını kontrol edin.",
|
||
exportingMasterpiece: "Çalışma dışa aktarılıyor...",
|
||
toastExportSuccess: "Dışa Aktarma Başarılı",
|
||
toastExportSuccessMsg: "Dosya başarıyla kaydedildi: ",
|
||
toastExportFailed: "Dışa Aktarma Başarısız",
|
||
toastExportFailedMsg: "Kaydetme işlemi başarısız oldu.",
|
||
toastExportError: "Dışa Aktarma Hatası",
|
||
showInExplorer: "Dosya Gezgininde Göster",
|
||
errorModalTitle: "İşlem Hatası",
|
||
errorModalSubtitle: "Görsel dekupe edilirken yapay zeka işleminde bir sorunla karşılaşıldı.",
|
||
errorModalSuggestion: "Dekupe etmek istediğiniz görsel, arka planıyla ayırt edilemeyecek kadar benzer veya karmaşık olabilir. Lütfen öznenin arka plandan daha net ayırt edilebildiği, daha yüksek kontrastlı başka bir görsel deneyin.",
|
||
closeBtn: "Kapat"
|
||
},
|
||
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 is 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",
|
||
statusInit: "AI-Modell wird initialisiert...",
|
||
statusWeights: "Neuronale Gewichte werden geladen...",
|
||
statusImporting: "Bibliotheken werden importiert...",
|
||
statusDetecting: "Hardware wird erkannt...",
|
||
statusUsingDevice: "Verwendetes Gerät: ",
|
||
statusLoadingModel: "BiRefNet-Modell wird geladen...",
|
||
statusPreprocessing: "Bild wird geladen und vorverarbeitet...",
|
||
statusInference: "Hintergrundentfernungs-Inferenz wird ausgeführt...",
|
||
statusGeneratingMask: "Transparenzmaske wird generiert...",
|
||
statusSaving: "Transparentes Ausgabebild wird gespeichert...",
|
||
statusDone: "Abgeschlossen!",
|
||
subFirstRun: "Erster Start lädt Modell herunter (~200MB)",
|
||
subInference: "Details und weiche Maske werden extrahiert...",
|
||
subSaving: "Hochauflösender RGBA-Container wird zusammengestellt...",
|
||
subDefault: "Bitte warten Sie, während das AI-Modell verarbeitet...",
|
||
setupReady: "Bereit zum Konfigurieren der Umgebung.",
|
||
setupCreatingVenv: "Isolierte virtuelle Umgebung (.venv) wird erstellt...",
|
||
setupVenvCreated: "Virtuelle Umgebung erfolgreich erstellt.",
|
||
setupVenvExists: "Virtuelle Umgebung existiert bereits.",
|
||
setupUpgradingPip: "Python-Paketinstallateur (pip) wird aktualisiert...",
|
||
setupInstallingTorch: "PyTorch AI-Engine (CPU-Version) wird installiert...",
|
||
setupDownloadingTorch: "Torch und Abhängigkeiten werden heruntergeladen...",
|
||
setupInstallingDeps: "Gesammelte Pakete werden installiert...",
|
||
setupCompleted: "Konfiguration abgeschlossen! Viel Spaß mit der Hintergrundentfernung.",
|
||
setupFailed: "Setup fehlgeschlagen. Systemabhängigkeiten prüfen.",
|
||
exportingMasterpiece: "Meisterwerk wird exportiert...",
|
||
toastExportSuccess: "Export erfolgreich",
|
||
toastExportSuccessMsg: "Datei erfolgreich gespeichert: ",
|
||
toastExportFailed: "Export fehlgeschlagen",
|
||
toastExportFailedMsg: "Speichervorgang fehlgeschlagen.",
|
||
toastExportError: "Exportfehler",
|
||
showInExplorer: "Im Datei-Explorer anzeigen",
|
||
errorModalTitle: "Verarbeitungsfehler",
|
||
errorModalSubtitle: "Beim Segmentieren Ihres Bildes ist ein Problem aufgetreten.",
|
||
errorModalSuggestion: "Das Motiv auf dem Bild, das Sie freistellen möchten, ist möglicherweise zu stark mit dem Hintergrund verschmolzen oder visuell nicht unterscheidbar. Bitte versuchen Sie es mit einem anderen Bild mit einem deutlicheren, kontrastreicheren Motiv.",
|
||
closeBtn: "Schließen"
|
||
},
|
||
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: "Автообрезка",
|
||
statusInit: "Инициализация модели ИИ...",
|
||
statusWeights: "Загрузка весов нейросети в память...",
|
||
statusImporting: "Импорт библиотек...",
|
||
statusDetecting: "Обнаружение устройства...",
|
||
statusUsingDevice: "Используемое устройство: ",
|
||
statusLoadingModel: "Загрузка модели BiRefNet...",
|
||
statusPreprocessing: "Загрузка и предобработка изображения...",
|
||
statusInference: "Выполнение удаления фона...",
|
||
statusGeneratingMask: "Генерация маски прозрачности...",
|
||
statusSaving: "Сохранение выходного прозрачного изображения...",
|
||
statusDone: "Готово!",
|
||
subFirstRun: "Первый запуск загружает модель (~200 МБ)",
|
||
subInference: "Извлечение деталей и мягкой маски альфа...",
|
||
subSaving: "Сборка RGBA-контейнера высокого разрешения...",
|
||
subDefault: "Пожалуйста, подождите, пока модель ИИ обрабатывает...",
|
||
setupReady: "Готов к настройке среды.",
|
||
setupCreatingVenv: "Создание изолированной виртуальной среды (.venv)...",
|
||
setupVenvCreated: "Виртуальная среда успешно создана.",
|
||
setupVenvExists: "Виртуальная среда уже существует.",
|
||
setupUpgradingPip: "Обновление установщика пакетов Python (pip)...",
|
||
setupInstallingTorch: "Установка движка ИИ PyTorch (версия CPU)...",
|
||
setupDownloadingTorch: "Скачивание torch и зависимостей...",
|
||
setupInstallingDeps: "Установка собранных пакетов...",
|
||
setupCompleted: "Настройка завершена! Наслаждайтесь удалением фона.",
|
||
setupFailed: "Ошибка настройки. Проверьте системные зависимости.",
|
||
exportingMasterpiece: "Экспорт шедевра...",
|
||
toastExportSuccess: "Экспорт успешен",
|
||
toastExportSuccessMsg: "Файл успешно сохранен: ",
|
||
toastExportFailed: "Ошибка экспорта",
|
||
toastExportFailedMsg: "Операция сохранения не удалась.",
|
||
toastExportError: "Ошибка экспорта",
|
||
showInExplorer: "Показать в проводнике",
|
||
errorModalTitle: "Ошибка обработки",
|
||
errorModalSubtitle: "При сегментации вашего изображения возникла проблема.",
|
||
errorModalSuggestion: "Объект на изображении, который вы хотите вырезать, может быть слишком сливающимся или визуально неотличимым от фона. Пожалуйста, попробуйте другое изображение с более четким и контрастным объектом.",
|
||
closeBtn: "Закрыть"
|
||
},
|
||
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: "قص تلقائي",
|
||
statusInit: "تهيئة نموذج الذكاء الاصطناعي...",
|
||
statusWeights: "تحميل أوزان الشبكة العصبية في الذاكرة...",
|
||
statusImporting: "استيراد المكتبات...",
|
||
statusDetecting: "الكشف عن الجهاز...",
|
||
statusUsingDevice: "الجهاز المستخدم: ",
|
||
statusLoadingModel: "تحميل نموذج BiRefNet...",
|
||
statusPreprocessing: "تحميل ومعالجة الصورة مسبقاً...",
|
||
statusInference: "تشغيل خوارزمية إزالة الخلفية...",
|
||
statusGeneratingMask: "إنشاء قناع الشفافية...",
|
||
statusSaving: "حفظ الصورة الشفافة الناتجة...",
|
||
statusDone: "اكتمل!",
|
||
subFirstRun: "التشغيل الأول يقوم بتنزيل النموذج (~200 ميجابايت)",
|
||
subInference: "استخراج التفاصيل والشفافية الناعمة...",
|
||
subSaving: "تجميع حاوية RGBA عالية الدقة...",
|
||
subDefault: "يرجى الانتظار أثناء معالجة نموذج الذكاء الاصطناعي...",
|
||
setupReady: "جاهز لتكوين البيئة.",
|
||
setupCreatingVenv: "إنشاء بيئة افتراضية معزولة (.venv)...",
|
||
setupVenvCreated: "تم إنشاء البيئة الافتراضية بنجاح.",
|
||
setupVenvExists: "البيئة الافتراضية موجودة بالفعل.",
|
||
setupUpgradingPip: "ترقية مثبت حزم بايثون (pip)...",
|
||
setupInstallingTorch: "تثبيت محرك الذكاء الاصطناعي PyTorch (نسخة CPU)...",
|
||
setupDownloadingTorch: "تنزيل torch والاعتمادات...",
|
||
setupInstallingDeps: "تثبيت الحزم التي تم جمعها...",
|
||
setupCompleted: "اكتمل التكوين! استمتع بإزالة الخلفية.",
|
||
setupFailed: "فشل الإعداد. تحقق من تبعيات النظام.",
|
||
exportingMasterpiece: "تصدير العمل الفني...",
|
||
toastExportSuccess: "تم التصدير بنجاح",
|
||
toastExportSuccessMsg: "تم حفظ الملف بنجاح: ",
|
||
toastExportFailed: "فشل التصدير",
|
||
toastExportFailedMsg: "فشلت عملية الحفظ.",
|
||
toastExportError: "خطأ في التصدير",
|
||
showInExplorer: "عرض في مستكشف الملفات",
|
||
errorModalTitle: "خطأ في المعالجة",
|
||
errorModalSubtitle: "واجهتنا مشكلة أثناء تقسيم صورتك باستخدام الذكاء الاصطناعي.",
|
||
errorModalSuggestion: "قد يكون العنصر في الصورة التي تريد قصها مدمجًا للغاية أو غير قابل للتمييز بصريًا عن خلفيته. يرجى محاولة استخدام صورة أخرى تحتوي على عنصر أكثر وضوحًا وتباينًا.",
|
||
closeBtn: "إغلاق"
|
||
}
|
||
};
|
||
|
||
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
|
||
elements.btnRemoveBg.addEventListener('click', executeBackgroundRemoval);
|
||
|
||
// Background Settings Tabs
|
||
elements.tabBgTrans.addEventListener('click', () => switchBgMode('transparent'));
|
||
elements.tabBgSolid.addEventListener('click', () => switchBgMode('solid'));
|
||
elements.tabBgGrad.addEventListener('click', () => switchBgMode('gradient'));
|
||
|
||
// Export Quality Slider
|
||
elements.exportFormat.addEventListener('change', (e) => {
|
||
if (e.target.value === 'png') {
|
||
elements.rowQualitySlider.classList.add('hidden');
|
||
} else {
|
||
elements.rowQualitySlider.classList.remove('hidden');
|
||
}
|
||
});
|
||
|
||
elements.exportQuality.addEventListener('input', (e) => {
|
||
elements.exportQualityVal.innerText = `${e.target.value}%`;
|
||
});
|
||
|
||
// Export MASTERPIECE Click
|
||
elements.btnExport.addEventListener('click', executeExport);
|
||
}
|
||
|
||
// --- BIREFNET EXECUTION PIPELINE ---
|
||
async function executeBackgroundRemoval() {
|
||
if (!currentImagePath) return;
|
||
|
||
elements.btnRemoveBg.classList.add('hidden');
|
||
elements.loaderBox.classList.remove('hidden');
|
||
elements.loaderText.innerText = getTrans('statusInit');
|
||
elements.loaderSubtext.innerText = getTrans('statusWeights');
|
||
|
||
// Get unique temporary output file path
|
||
const tempOut = await window.api.getTempPath('transparent_result.png');
|
||
|
||
// Listen to execution progress
|
||
window.api.onProcessProgress((progress) => {
|
||
if (progress.includes("[STATUS]")) {
|
||
const statusText = progress.replace("[STATUS] ", "").trim();
|
||
|
||
// Let's map statusText to localized keys
|
||
if (statusText.includes("Importing libraries")) {
|
||
elements.loaderText.innerText = getTrans('statusImporting');
|
||
elements.loaderSubtext.innerText = getTrans('subDefault');
|
||
} else if (statusText.includes("Detecting device")) {
|
||
elements.loaderText.innerText = getTrans('statusDetecting');
|
||
elements.loaderSubtext.innerText = getTrans('subDefault');
|
||
} else if (statusText.startsWith("Using device:")) {
|
||
const device = statusText.replace("Using device:", "").trim();
|
||
elements.loaderText.innerText = getTrans('statusUsingDevice') + device;
|
||
elements.loaderSubtext.innerText = getTrans('subDefault');
|
||
} else if (statusText.includes("Loading BiRefNet model")) {
|
||
elements.loaderText.innerText = getTrans('statusLoadingModel');
|
||
elements.loaderSubtext.innerText = getTrans('subFirstRun');
|
||
} else if (statusText.includes("Loading and preprocessing image")) {
|
||
elements.loaderText.innerText = getTrans('statusPreprocessing');
|
||
elements.loaderSubtext.innerText = getTrans('subDefault');
|
||
} else if (statusText.includes("Running background removal inference")) {
|
||
elements.loaderText.innerText = getTrans('statusInference');
|
||
elements.loaderSubtext.innerText = getTrans('subInference');
|
||
} else if (statusText.includes("Generating transparency mask")) {
|
||
elements.loaderText.innerText = getTrans('statusGeneratingMask');
|
||
elements.loaderSubtext.innerText = getTrans('subInference');
|
||
} else if (statusText.includes("Saving output transparent image")) {
|
||
elements.loaderText.innerText = getTrans('statusSaving');
|
||
elements.loaderSubtext.innerText = getTrans('subSaving');
|
||
} else if (statusText.includes("DONE")) {
|
||
elements.loaderText.innerText = getTrans('statusDone');
|
||
elements.loaderSubtext.innerText = "";
|
||
} else {
|
||
// Fallback for any other status logs
|
||
elements.loaderText.innerText = statusText;
|
||
}
|
||
}
|
||
});
|
||
|
||
try {
|
||
const result = await window.api.removeBackground(currentImagePath, tempOut);
|
||
|
||
if (result.success) {
|
||
processedTempPath = result.outputPath;
|
||
showToast(getTrans('toastTitleSuccess'), getTrans('toastTrimCompleteMsg'), "success");
|
||
|
||
// Update workspace preview image source to output transparent PNG
|
||
// Add timestamp to bypass browser cache refreshing
|
||
const freshSrc = `file://${processedTempPath}?t=${Date.now()}`;
|
||
|
||
// Re-initialize CropperJS on the new image src
|
||
elements.imageElement.src = freshSrc;
|
||
|
||
// Wait for image source update to build the cropper safely
|
||
setTimeout(() => {
|
||
initCropper();
|
||
|
||
// Unlock background customization and export
|
||
elements.secCanvasBg.classList.remove('disabled-group');
|
||
elements.secExport.classList.remove('disabled-group');
|
||
elements.ctrlAutoTrim.disabled = false; // Unlock trim feature!
|
||
}, 300);
|
||
|
||
} else {
|
||
elements.errorOverlayLog.innerText = result.error || getTrans('toastSetupErrorMsg');
|
||
elements.errorOverlay.classList.remove('hidden');
|
||
}
|
||
} catch (err) {
|
||
elements.errorOverlayLog.innerText = err.message;
|
||
elements.errorOverlay.classList.remove('hidden');
|
||
} finally {
|
||
elements.loaderBox.classList.add('hidden');
|
||
elements.btnRemoveBg.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
// --- CANVAS CUSTOM BACKGROUND SETTINGS ---
|
||
function resetBgSettings() {
|
||
elements.secCanvasBg.classList.add('disabled-group');
|
||
elements.secExport.classList.add('disabled-group');
|
||
switchBgMode('transparent');
|
||
}
|
||
|
||
function switchBgMode(mode) {
|
||
activeBgMode = mode;
|
||
|
||
// Adjust active tab css
|
||
elements.tabBgTrans.classList.remove('active');
|
||
elements.tabBgSolid.classList.remove('active');
|
||
elements.tabBgGrad.classList.remove('active');
|
||
|
||
elements.subpanelSolid.classList.add('hidden');
|
||
elements.subpanelGradient.classList.add('hidden');
|
||
|
||
if (mode === 'transparent') {
|
||
elements.tabBgTrans.classList.add('active');
|
||
elements.canvasBgOverlay.style.opacity = '0';
|
||
} else if (mode === 'solid') {
|
||
elements.tabBgSolid.classList.add('active');
|
||
elements.subpanelSolid.classList.remove('hidden');
|
||
elements.canvasBgOverlay.style.background = activeSolidColor;
|
||
elements.canvasBgOverlay.style.opacity = '1';
|
||
} else if (mode === 'gradient') {
|
||
elements.tabBgGrad.classList.add('active');
|
||
elements.subpanelGradient.classList.remove('hidden');
|
||
|
||
// Draw current active gradient stops onto the workspace preview
|
||
const cssGrad = buildCssGradientString(activeGradientStops);
|
||
elements.canvasBgOverlay.style.background = cssGrad;
|
||
elements.canvasBgOverlay.style.opacity = '1';
|
||
}
|
||
}
|
||
|
||
function buildCssGradientString(stops) {
|
||
return `linear-gradient(135deg, ${stops[0].color}, ${stops[1].color})`;
|
||
}
|
||
|
||
function setupBgSwatches() {
|
||
// Solid Swatches
|
||
const solidSwatches = document.querySelectorAll('.swatch:not(.custom)');
|
||
solidSwatches.forEach(swatch => {
|
||
swatch.addEventListener('click', (e) => {
|
||
solidSwatches.forEach(s => s.classList.remove('active'));
|
||
e.target.classList.add('active');
|
||
|
||
activeSolidColor = e.target.getAttribute('data-color');
|
||
elements.canvasBgOverlay.style.background = activeSolidColor;
|
||
});
|
||
});
|
||
|
||
// Custom Color Picker
|
||
elements.customColorPicker.addEventListener('input', (e) => {
|
||
activeSolidColor = e.target.value;
|
||
elements.canvasBgOverlay.style.background = activeSolidColor;
|
||
|
||
// Highlight custom swatch borders
|
||
elements.btnCustomColorPicker.classList.add('active');
|
||
solidSwatches.forEach(s => s.classList.remove('active'));
|
||
});
|
||
|
||
// Gradient Swatches
|
||
const gradSwatches = document.querySelectorAll('.grad-swatch');
|
||
gradSwatches.forEach(swatch => {
|
||
swatch.addEventListener('click', (e) => {
|
||
gradSwatches.forEach(s => s.classList.remove('active'));
|
||
e.target.classList.add('active');
|
||
|
||
const gradKey = e.target.className.split(' ').find(cls => cls.startsWith('grad-'));
|
||
if (GRADIENTS[gradKey]) {
|
||
activeGradientStops = GRADIENTS[gradKey];
|
||
elements.canvasBgOverlay.style.background = e.target.getAttribute('data-gradient');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// --- CANVAS EXPORT AND MERGING ENGINE ---
|
||
async function executeExport() {
|
||
if (!cropper) return;
|
||
|
||
elements.btnExport.disabled = true;
|
||
elements.btnExport.innerHTML = `
|
||
<svg class="loader-ring" viewBox="0 0 50 50" style="width:16px;height:16px;margin:0;"><circle cx="25" cy="25" r="20" fill="none" stroke-width="4" stroke="#fff"></circle></svg>
|
||
<span>${getTrans('exportingMasterpiece')}</span>
|
||
`;
|
||
|
||
setTimeout(async () => {
|
||
try {
|
||
// Get the cropped canvas at original resolution
|
||
const croppedCanvas = cropper.getCroppedCanvas();
|
||
|
||
if (!croppedCanvas) {
|
||
throw new Error("Failed to extract active cropped canvas workspace.");
|
||
}
|
||
|
||
let finalCanvas = croppedCanvas;
|
||
|
||
// If solid or gradient background mode is selected, we burn it behind the subject
|
||
if (activeBgMode !== 'transparent') {
|
||
finalCanvas = document.createElement('canvas');
|
||
finalCanvas.width = croppedCanvas.width;
|
||
finalCanvas.height = croppedCanvas.height;
|
||
const ctx = finalCanvas.getContext('2d');
|
||
|
||
if (activeBgMode === 'solid') {
|
||
ctx.fillStyle = activeSolidColor;
|
||
ctx.fillRect(0, 0, finalCanvas.width, finalCanvas.height);
|
||
} else if (activeBgMode === 'gradient') {
|
||
// Render the 135deg gradient vector on final output canvas
|
||
const grad = ctx.createLinearGradient(0, 0, finalCanvas.width, finalCanvas.height);
|
||
activeGradientStops.forEach(stop => {
|
||
grad.addColorStop(stop.offset, stop.color);
|
||
});
|
||
ctx.fillStyle = grad;
|
||
ctx.fillRect(0, 0, finalCanvas.width, finalCanvas.height);
|
||
}
|
||
|
||
// Overlay transparent subject on top of custom background
|
||
ctx.drawImage(croppedCanvas, 0, 0);
|
||
}
|
||
|
||
// Read Export Format settings
|
||
const format = elements.exportFormat.value;
|
||
let mimeType = 'image/png';
|
||
let extension = 'png';
|
||
|
||
if (format === 'jpeg') {
|
||
mimeType = 'image/jpeg';
|
||
extension = 'jpg';
|
||
} else if (format === 'webp') {
|
||
mimeType = 'image/webp';
|
||
extension = 'webp';
|
||
}
|
||
|
||
const quality = parseFloat(elements.exportQuality.value) / 100;
|
||
const base64Data = finalCanvas.toDataURL(mimeType, quality);
|
||
|
||
// Create default filename suggestion
|
||
const nameWithoutExt = originalFileName.substring(0, originalFileName.lastIndexOf('.')) || originalFileName;
|
||
const exportDefaultName = `${nameWithoutExt}_no_bg.${extension}`;
|
||
|
||
// Open Save File dialog via context bridge
|
||
const result = await window.api.exportImage(base64Data, exportDefaultName);
|
||
|
||
if (result.success) {
|
||
showToastWithAction(
|
||
getTrans('toastExportSuccess'),
|
||
`${getTrans('toastExportSuccessMsg')}${result.filePath.split(/[\\/]/).pop()}`,
|
||
"success",
|
||
result.filePath
|
||
);
|
||
} else if (!result.canceled) {
|
||
showToast(getTrans('toastExportFailed'), result.error || getTrans('toastExportFailedMsg'), "error");
|
||
}
|
||
} catch (err) {
|
||
showToast(getTrans('toastExportError'), err.message, "error");
|
||
} finally {
|
||
// Re-enable button
|
||
elements.btnExport.disabled = false;
|
||
elements.btnExport.innerHTML = `
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||
<span>${getTrans('exportMasterpiece')}</span>
|
||
`;
|
||
}
|
||
}, 100);
|
||
}
|
||
|
||
// --- GLOBAL TOAST SYSTEM IMPLEMENTATION ---
|
||
let toastTimeout = null;
|
||
|
||
function setupToastListeners() {
|
||
elements.btnCloseToast.addEventListener('click', hideToast);
|
||
}
|
||
|
||
function showToast(title, message, type = 'info') {
|
||
hideToast();
|
||
|
||
elements.toastTitle.innerText = title;
|
||
elements.toastMessage.innerText = message;
|
||
|
||
// Styling
|
||
elements.toastBanner.className = 'toast-notification glass-panel-inner';
|
||
if (type === 'success') elements.toastBanner.classList.add('success');
|
||
if (type === 'error') elements.toastBanner.classList.add('error');
|
||
|
||
elements.toastBanner.classList.remove('hidden');
|
||
|
||
// Set automatic dismissal
|
||
toastTimeout = setTimeout(hideToast, 5000);
|
||
}
|
||
|
||
function showToastWithAction(title, message, type = 'success', filePath) {
|
||
hideToast();
|
||
|
||
elements.toastTitle.innerText = title;
|
||
elements.toastMessage.innerHTML = `${message} <br><button id="btn-toast-explore" class="btn btn-secondary btn-sm" style="margin-top:6px;width:100%;">${getTrans('showInExplorer')}</button>`;
|
||
|
||
elements.toastBanner.className = 'toast-notification glass-panel-inner';
|
||
if (type === 'success') elements.toastBanner.classList.add('success');
|
||
if (type === 'error') elements.toastBanner.classList.add('error');
|
||
|
||
elements.toastBanner.classList.remove('hidden');
|
||
|
||
// Bind click logic to "Show in File Explorer" button
|
||
setTimeout(() => {
|
||
const btnExplore = document.getElementById('btn-toast-explore');
|
||
if (btnExplore) {
|
||
btnExplore.addEventListener('click', () => {
|
||
window.api.showInExplorer(filePath);
|
||
});
|
||
}
|
||
}, 50);
|
||
|
||
// Keep it active longer since it contains interaction
|
||
toastTimeout = setTimeout(hideToast, 8000);
|
||
}
|
||
|
||
function hideToast() {
|
||
if (toastTimeout) {
|
||
clearTimeout(toastTimeout);
|
||
toastTimeout = null;
|
||
}
|
||
elements.toastBanner.classList.add('hidden');
|
||
}
|