/* ===== 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());
},
};