Spaces:
Running
Running
File size: 5,533 Bytes
774fe36 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | /* ===== 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 = `
<div class="reference-preview-container">
<img src="${this.image.dataUrl}" class="reference-preview-img" />
</div>`;
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());
},
}; |