This commit is contained in:
mike
2026-06-28 00:09:26 +02:00
parent bf3773bfd4
commit 03b754112c

View File

@@ -169,10 +169,10 @@
.pad-preview-overlay {
position: absolute;
background: rgba(245,158,11,0.25);
background: rgba(245,158,11,0.4);
border: 1px dashed #f59e0b;
pointer-events: none;
z-index: 5;
z-index: 10;
}
.action-btn {
@@ -1323,6 +1323,9 @@
position: relative; height: 100%; border-radius: 6px; overflow: hidden;
}
.studio-viewer.show-checker {
background-color: transparent;
}
#studioCheckerboard {
background-color: #888;
background-image:
linear-gradient(45deg,#555 25%,transparent 25%),
@@ -1331,11 +1334,13 @@
linear-gradient(-45deg,transparent 75%,#555 75%);
background-size: 16px 16px;
background-position: 0 0, 0 8px, 8px -8px, -8px 0;
z-index: 1;
}
#lbImg {
max-width: 100%; max-height: 100%;
object-fit: contain; border-radius: 4px;
cursor: zoom-in; transition: transform 0.25s; display: block;
z-index: 2; position: relative;
}
#lbImg.zoomed { cursor: zoom-out; transform: scale(2.2); transform-origin: center; }
#lbVideo { max-width: 100%; max-height: 100%; border-radius: 4px; }
@@ -1960,6 +1965,7 @@
onblur="updateGroupName()" onkeydown="if(event.key==='Enter')this.blur()"
title="Group name (applies to all images in this group)" />
<span id="lbPoseTag" style="font-size:11px;color:#f59e0b;display:none"></span>
<span id="lbImgDims" style="font-size:11px;color:#444;margin:0 8px"></span>
<span id="lbCounter" style="font-size:12px;color:#555"></span>
</div>
<div class="studio-topbar-right">
@@ -1975,6 +1981,7 @@
<div class="studio-viewer-row">
<button class="studio-nav-btn" id="lbPrev" onclick="lbNav(-1)"></button>
<div class="studio-viewer" id="studioViewer">
<div id="studioCheckerboard" style="display:none;position:absolute;pointer-events:none;z-index:1"></div>
<img id="lbImg" src="" alt="" onclick="toggleImageZoom(this)" />
<video id="lbVideo" style="display:none;max-width:100%;max-height:100%;border-radius:4px" controls autoplay muted loop></video>
<div class="lb-hidden-badge">Hidden from preview</div>
@@ -2051,7 +2058,7 @@
<button class="sb-btn" id="lbCropBtn" style="display:none" onclick="lbAutoCrop()" title="Crop away transparent border">Crop</button>
<button class="sb-btn" id="lbDuplicateBtn" onclick="lbDuplicate()" title="Duplicate image into same group">Duplicate</button>
<button class="sb-btn" id="lbManualCropBtn" onclick="startManualCrop()" title="Drag to crop image">Crop…</button>
<button class="sb-btn" id="lbManualPadBtn" onclick="startManualPad()" title="Expand canvas by padding each side">Pad…</button>
<button class="sb-btn" id="lbManualPadBtn" onclick="startManualPad()" title="Expand canvas by padding each side">Expand…</button>
<button class="sb-btn" id="lbRotateLeftBtn" onclick="lbRotate(-90)" title="Rotate 90° counter-clockwise">⟲ 90°</button>
<button class="sb-btn" id="lbRotateRightBtn" onclick="lbRotate(90)" title="Rotate 90° clockwise">⟳ 90°</button>
<button class="sb-btn" id="lbPoseBtn" onclick="lbTogglePose()" title="Preview body pose skeleton">Pose</button>
@@ -2220,8 +2227,8 @@
// --- HYDRATION_START ---
const PRELOADED_IMAGES = [
"20260627_232843_pad_20260626_035101_pad_20260626_034830_jb.nobg.png",
"20260627_232502_pad_20260626_035101_pad_20260626_034830_jb.nobg.png",
"20260627_232843_pad_20260626_035101_pad_20260626_034830_jb.nobg.png",
"20260627_232526_pad_20260626_035101_pad_20260626_034830_jb.nobg.png",
"20260627_232302_pad_20260627_232133_image.png",
"20260627_232238_pad_20260627_232133_image.png",
@@ -6914,19 +6921,61 @@
updatePadPreview();
}
function getImageDisplayRect(img) {
if (!img || !img.naturalWidth) return null;
const rect = img.getBoundingClientRect();
const container = img.parentElement;
const crect = container.getBoundingClientRect();
const iw = img.naturalWidth;
const ih = img.naturalHeight;
const cw = rect.width;
const ch = rect.height;
const aspect = iw / ih;
const caspect = cw / ch;
let dw, dh, dl, dt;
if (aspect > caspect) {
dw = cw;
dh = cw / aspect;
dl = 0;
dt = (ch - dh) / 2;
} else {
dh = ch;
dw = ch * aspect;
dt = 0;
dl = (cw - dw) / 2;
}
// Adjust relative to container if needed, but since img is a flex item
// centered in .studio-viewer, its rect.left/top might already be offset.
// Actually, dl and dt above are relative to the img element's box.
// We want coordinates relative to .studio-viewer.
const finalLeft = rect.left - crect.left + dl;
const finalTop = rect.top - crect.top + dt;
return { left: finalLeft, top: finalTop, width: dw, height: dh };
}
function updatePadPreview() {
const viewer = document.getElementById('studioViewer');
if (!viewer) return;
const img = document.getElementById('lbImg');
if (!viewer || !img || img.style.display === 'none') return;
// Clean up existing preview overlays
viewer.querySelectorAll('.pad-preview-overlay').forEach(el => el.remove());
const rect = getImageDisplayRect(img);
if (!rect) return;
const isManual = !!document.getElementById('padBar');
const getV = (side) => {
const el = document.getElementById(isManual ? 'pad' + side : 'sbPad' + side);
return el ? el.value : '0';
};
const sides = ['Left', 'Right'];
const sides = ['Left', 'Right', 'Top', 'Bottom'];
sides.forEach(side => {
const vStr = getV(side);
if (!vStr || vStr === '0' || vStr === '0.00') return;
@@ -6934,19 +6983,24 @@
const val = parseFloat(vStr);
if (isNaN(val) || val <= 0) return;
const isPercent = vStr.includes('%') || vStr.includes('.');
const width = isPercent ? (val + '%') : (val + 'px');
const isPercent = vStr.includes('%') || (vStr.indexOf('.') !== -1 && !isNaN(vStr));
const overlay = document.createElement('div');
overlay.className = 'pad-preview-overlay';
overlay.style.top = '0';
overlay.style.bottom = '0';
if (side === 'Left') {
overlay.style.left = '0';
overlay.style.width = width;
if (side === 'Left' || side === 'Right') {
const w = isPercent ? (rect.width * val / 100) : val;
overlay.style.width = w + 'px';
overlay.style.top = rect.top + 'px';
overlay.style.height = rect.height + 'px';
if (side === 'Left') overlay.style.left = (rect.left - w) + 'px';
else overlay.style.left = (rect.left + rect.width) + 'px';
} else {
overlay.style.right = '0';
overlay.style.width = width;
const h = isPercent ? (rect.height * val / 100) : val;
overlay.style.height = h + 'px';
overlay.style.left = rect.left + 'px';
overlay.style.width = rect.width + 'px';
if (side === 'Top') overlay.style.top = (rect.top - h) + 'px';
else overlay.style.top = (rect.top + rect.height) + 'px';
}
viewer.appendChild(overlay);
});
@@ -7075,6 +7129,29 @@
const isVid = isVideo(fname) || fileContentType[fname] === 'video';
const lbImgEl = document.getElementById('lbImg');
const lbVideoEl = document.getElementById('lbVideo');
lbImgEl.onload = () => {
const dimEl = document.getElementById('lbImgDims');
if (dimEl) dimEl.textContent = `${lbImgEl.naturalWidth} x ${lbImgEl.naturalHeight}`;
// Use requestAnimationFrame to ensure layout has updated before measuring for checkerboard/previews
requestAnimationFrame(() => {
updatePadPreview();
const cb = document.getElementById('sbCheckerboard');
if (cb && cb.checked) toggleCheckerboard(true);
});
};
// Show image dimensions
const dimEl = document.getElementById('lbImgDims');
if (dimEl) {
if (!isVid && lbImgEl.naturalWidth) {
dimEl.textContent = `${lbImgEl.naturalWidth} x ${lbImgEl.naturalHeight}`;
} else {
dimEl.textContent = '';
}
}
if (isVid) {
lbImgEl.style.display = 'none';
lbVideoEl.style.display = '';
@@ -8201,7 +8278,7 @@
return `<label style="display:flex;align-items:center;gap:3px;font-size:11px;color:#ccc">${label}<input type="text" id="${id}" value="0.00" oninput="${onInput}" onblur="${onBlur}" style="width:52px;background:#111;border:1px solid #333;color:#ccc;border-radius:4px;padding:2px 4px;font-size:11px;text-align:right" placeholder="px or %"></label>`;
};
bar.innerHTML =
`<span style="flex:1;font-size:11px;color:#aaa">Pad canvas (px or %):</span>`
`<span style="flex:1;font-size:11px;color:#aaa">Expand canvas (px or %):</span>`
+ numInput('padTop','↑')
+ numInput('padRight','→')
+ numInput('padBottom','↓')
@@ -8216,7 +8293,7 @@
+ `<label style="display:flex;align-items:center;gap:4px;font-size:11px;color:#ccc;cursor:pointer" title="Instruct the model to fill in the padded area">`
+ `<input type="checkbox" id="padOutpaint" style="cursor:pointer">Outpaint</label>`
+ `<button class="sb-btn" onclick="cancelManualPad()">Cancel</button>`
+ `<button class="sb-btn primary" id="padConfirmBtn" onclick="confirmManualPad()">Pad</button>`;
+ `<button class="sb-btn primary" id="padConfirmBtn" onclick="confirmManualPad()">Expand</button>`;
viewer.appendChild(bar);
updatePadPreview();
}
@@ -8279,12 +8356,11 @@
refreshNow(true);
} else {
showToast('Padded', 'success');
lbUrls[lbIdx] = IMAGE_FOLDER + fname + '?t=' + Date.now();
updateStudio();
_bustStudioImage(fname);
}
} else {
showToast('Pad failed: ' + await r.text(), 'error');
if (btn) { btn.disabled = false; btn.textContent = 'Pad'; }
if (btn) { btn.disabled = false; btn.textContent = 'Expand'; }
}
} catch (e) {
showToast('Pad error: ' + e, 'error');
@@ -9802,6 +9878,15 @@
// Initialize
document.addEventListener('DOMContentLoaded', () => {
window.addEventListener('resize', () => {
if (_isStudioOpen()) {
requestAnimationFrame(() => {
updatePadPreview();
const cb = document.getElementById('sbCheckerboard');
if (cb && cb.checked) toggleCheckerboard(true);
});
}
});
loadPromptHistory();
loadImages();
autoRefreshTimer = setInterval(_timedRefresh, REFRESH_INTERVAL * 1000);
@@ -11648,7 +11733,28 @@
function toggleCheckerboard(on) {
const viewer = document.getElementById('studioViewer');
const cbLayer = document.getElementById('studioCheckerboard');
const img = document.getElementById('lbImg');
if (viewer) viewer.classList.toggle('show-checker', on);
if (cbLayer) {
if (on && img && img.style.display !== 'none') {
const rect = getImageDisplayRect(img);
if (rect) {
cbLayer.style.display = 'block';
cbLayer.style.left = rect.left + 'px';
cbLayer.style.top = rect.top + 'px';
cbLayer.style.width = rect.width + 'px';
cbLayer.style.height = rect.height + 'px';
} else {
cbLayer.style.display = 'none';
}
} else {
cbLayer.style.display = 'none';
}
}
const cb = document.getElementById('sbCheckerboard');
if (cb) cb.checked = on;
}