/* ===== Character Reference (角色参考) Module ===== */ window.NAIReference = { image: null, // {dataUrl, b64Processed} — b64Processed is after resize_and_pad getInfoExtract() { const el = $('#reference-info-extract'); return el ? parseFloat(el.value) : 1; }, getStrength() { const el = $('#reference-strength'); return el ? parseFloat(el.value) : 1; }, getStyleMode() { const el = $('#reference-style-select'); return el ? el.value : 'character&style'; }, async handleUpload(file) { if (!file || !file.type.startsWith('image/')) return; const dataUrl = await NAIUtils.fileToDataUrl(file); const processed = await this.resizeAndPad(dataUrl); this.image = { dataUrl, b64Processed: processed }; this.renderPreview(); }, useLastGenerated() { const imgEl = document.querySelector('#result-image-area img'); if (!imgEl || !imgEl.src) return; const dataUrl = imgEl.src; // Process async this.resizeAndPad(dataUrl).then(processed => { this.image = { dataUrl, b64Processed: processed }; this.renderPreview(); }); }, clear() { this.image = null; this.renderPreview(); }, renderPreview() { const previewArea = $('#reference-preview'); const slidersArea = $('#reference-sliders'); const clearBtn = $('#btn-reference-clear'); if (!this.image) { previewArea.innerHTML = ''; previewArea.style.display = 'none'; slidersArea.innerHTML = ''; clearBtn.style.display = 'none'; return; } clearBtn.style.display = ''; previewArea.style.display = 'block'; previewArea.innerHTML = `
`; slidersArea.innerHTML = ''; slidersArea.appendChild(NAIUtils.createDynSlider('参考度', 'reference-info-extract', 0, 1, 0.05, 1)); slidersArea.appendChild(NAIUtils.createDynSlider('强度', 'reference-strength', 0, 1, 0.05, 1)); }, /** * Resize and pad image to nearest target size (1024x1536, 1472x1472, 1536x1024), * centered on black background. Returns base64 PNG string (no prefix). */ resizeAndPad(dataUrl) { return new Promise(async (resolve) => { const img = await NAIUtils.loadImageFromDataUrl(dataUrl); const ow = img.naturalWidth; const oh = img.naturalHeight; const ratio = ow / oh; // Find closest target size by aspect ratio const targets = [[1024, 1536], [1472, 1472], [1536, 1024]]; let best = targets[0]; let minDiff = Infinity; for (const [tw, th] of targets) { const diff = Math.abs(ratio - tw / th); if (diff < minDiff) { minDiff = diff; best = [tw, th]; } } const [tw, th] = best; // Scale to fit const scale = Math.min(tw / ow, th / oh); const nw = Math.round(ow * scale); const nh = Math.round(oh * scale); // Draw on canvas const canvas = document.createElement('canvas'); canvas.width = tw; canvas.height = th; const ctx = canvas.getContext('2d'); // Black background ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, tw, th); // Centered image const xOff = Math.floor((tw - nw) / 2); const yOff = Math.floor((th - nh) / 2); ctx.drawImage(img, xOff, yOff, nw, nh); // Export as PNG base64 const result = canvas.toDataURL('image/png'); resolve(NAIUtils.dataUrlToBase64(result)); }); }, collectParams() { if (!this.image) return null; return { image: this.image.b64Processed, info_extract: this.getInfoExtract(), strength: this.getStrength(), style_mode: this.getStyleMode(), }; }, init() { // Toggle $('#reference-toggle').addEventListener('click', () => { const c = $('#reference-content'); const a = $('#reference-arrow'); const open = c.classList.toggle('open'); a.textContent = open ? '▼' : '▶'; }); // Upload const input = $('#reference-input-file'); const drop = $('#reference-dropzone'); drop.addEventListener('click', () => input.click()); drop.addEventListener('dragover', e => { e.preventDefault(); drop.classList.add('dragover'); }); drop.addEventListener('dragleave', () => drop.classList.remove('dragover')); drop.addEventListener('drop', e => { e.preventDefault(); drop.classList.remove('dragover'); if (e.dataTransfer.files.length > 0) this.handleUpload(e.dataTransfer.files[0]); }); input.addEventListener('change', () => { if (input.files.length > 0) this.handleUpload(input.files[0]); input.value = ''; }); // Use last generated $('#btn-reference-use-last').addEventListener('click', () => this.useLastGenerated()); // Clear $('#btn-reference-clear').addEventListener('click', () => this.clear()); }, };