reorder
This commit is contained in:
19
backlog.md
19
backlog.md
@@ -37,18 +37,19 @@ Scenery:
|
||||
- ✅ OUTPAINT feature/ crop/ pad call qwen
|
||||
- the pad/crop i think should allow also percentage values, since we looking in scaled images.
|
||||
- valid multi-input way to facilite the 2.5/3d orbit
|
||||
- Refine a POSE, so we have the ref (source) image + pose prompt. use the tour GPU + api to refine the pose with user instruction. Then use the source image + updated prompt to generate a new image.
|
||||
- introduce custom padding features - auto crop afterwards
|
||||
- in car.html - studio view we have a action to pad the image we also have this feature in the "Generate" tab.
|
||||
- add a checkbox (or similar) to instruct the image-model to outpaint the padded area. Also implement the outpainting functionality in edit_api.py, we can instruct qwen-image. We can have the same functionality in the "Generate" tab when no prompt is given but a padding is given.
|
||||
- ✅ Refine a POSE, so we have the ref (source) image + pose prompt. use the tour GPU + api to refine the pose with user instruction. Then use the source image + updated prompt to generate a new image.
|
||||
- introduce in the custom padding features + auto crop afterwards
|
||||
- ✅ in car.html - studio view we have a action to pad the image we also have this feature in the "Generate" tab.
|
||||
- ✅ add a checkbox (or similar) to instruct the image-model to outpaint the padded area. Also implement the outpainting functionality in edit_api.py, we can instruct qwen-image. We can have the same functionality in the "Generate" tab when no prompt is given but a padding is given.
|
||||
- ✅ we have a bug that when pressing the ui-action "duplicate" and "pad" the browser needs to refresh before we see the result. Other actions like generating from pose do not hae this bug, fix the bug and boy scout adjecnt actions if they are missing the required technical follow up. Fix the bug for the features.
|
||||
- ✅ In the "Generate" tab, the CUSTOM prompt is shown twice, above POSES and statically always in view in the right bottom. Remove the statically CUSTOM prompt from the UI.
|
||||
- The Generate button is always visible in the "Generate" tab, however we cannot jump to it via keyboard. Add a keyboard shortcut to jump to the Generate button "G" or simialler.
|
||||
- remove the "set prompt"
|
||||
- ✅ remove the "set prompt"
|
||||
- after pose generation, sometimes small black area's occur in the subject. ( presumably the _apply_transparency_black_bg addition)
|
||||
- studio view - orbit tab - view sometimes seems accelerated, liek the tiems is set twice. And most of all a step in the turn seems in in the wrong direction. maybe its an image-ordering bug, but i think its because the image-model generated the step 240deg into the wrong direction
|
||||
- ✅ studio view - orbit tab - view sometimes seems accelerated, liek the tiems is set twice. And most of all a step in the turn seems in in the wrong direction. maybe its an image-ordering bug, but i think its because the image-model generated the step 240deg into the wrong direction
|
||||
- Improve the UI/UX for the end-user experience. Where effort is low and impact is high.
|
||||
|
||||
- add an action + key-bind to move a the current selected image in the filmstrip to the end of the filmstrip. ( so also do the actual reordering of the image )
|
||||
-
|
||||
- animate limbs along keyframe curves (true motion)
|
||||
- 3D-aware rotation / "circling around" with depth estimation
|
||||
- camera-orbit batch with consistent lighting across views
|
||||
@@ -59,7 +60,9 @@ Scenery:
|
||||
- after orbit generation, dont generate the mp4, but do show the orbit orbit-full-img-wrap and render the alpha images 1fps ourselves.
|
||||
- allow re-ordering of the images in the orbit generated frames
|
||||
- sometimes the UI renders two orbits in the same orbitFullViewer, must be a timer not being cleared in the ui
|
||||
-
|
||||
- The UI wants to car.html:2817 GET file:///mnt/zim/tour-comfy/output/_turntable/up_34e48ffb/turntable.jpg?t=1782562827857 net::ERR_FILE_NOT_FOUND, often want to show the wireframe - pose
|
||||
- Jun 27 14:38:03 MIKE bash[3607206]: INFO: 127.0.0.1:53442 - "GET /output/_turntable/up_a1104f04/views/view_023_345deg.png HTTP/1.1" 304 Not Modified those files should be statically served from filesystem, not via server
|
||||
|
||||
## refine
|
||||
|
||||
- when refresh page, we lose track of current jobs running.
|
||||
|
||||
@@ -126,6 +126,19 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.image-card.highlight-prev {
|
||||
border: 2px solid #f59e0b !important;
|
||||
box-shadow: 0 0 15px rgba(245, 158, 11, 0.4);
|
||||
}
|
||||
|
||||
.pad-preview-overlay {
|
||||
position: absolute;
|
||||
background: rgba(245,158,11,0.25);
|
||||
border: 1px dashed #f59e0b;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: rgba(0,0,0,0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
@@ -1902,6 +1915,7 @@
|
||||
<div class="studio-topbar">
|
||||
<div class="studio-topbar-left">
|
||||
<button class="btn" onclick="closeStudio()" style="font-size:11px;padding:4px 10px">✕ Gallery</button>
|
||||
<button class="btn" id="lbBackBtn" onclick="jumpToPrevGroup()" style="font-size:11px;padding:4px 10px;display:none" title="Go back to previously opened group">↺ Back</button>
|
||||
</div>
|
||||
<div class="studio-topbar-center">
|
||||
<input type="text" id="lbGroupNameInput"
|
||||
@@ -2426,8 +2440,11 @@
|
||||
let currentFiles = [];
|
||||
let currentGroups = [];
|
||||
const groupData = new Map(); // gid → { urls[], names[] }
|
||||
const fileGroups = {}; // filename → gid (for jumpToImage)
|
||||
const cycleTimers = new Map(); // gid → intervalId
|
||||
const cycleIdx = new Map(); // gid → current index
|
||||
let _lastSelectedGid = localStorage.getItem('lastSelectedGid') || null;
|
||||
let _prevGid = null; // for "Back" button in studio
|
||||
let imageNames = {}; // filename → human name (from /names)
|
||||
let clipDescriptions = {}; // filename → description
|
||||
let customGroups = {}; // filename → groupId (from /groups)
|
||||
@@ -2510,6 +2527,7 @@
|
||||
}
|
||||
if (!map.has(gid)) map.set(gid, []);
|
||||
map.get(gid).push(f);
|
||||
fileGroups[f.cleanName] = gid;
|
||||
}
|
||||
// Sort each group: by explicit sort_order first, then by filename length
|
||||
for (const files of map.values()) {
|
||||
@@ -2573,6 +2591,7 @@
|
||||
const data = groupData.get(gid);
|
||||
if (!data) return;
|
||||
if (lbCurrentGid !== gid) { // clear refs when switching groups
|
||||
_prevGid = lbCurrentGid;
|
||||
_sbRefIndices = [];
|
||||
_sceneRefs = [null, null, null];
|
||||
_scenePersonInit = false;
|
||||
@@ -2580,6 +2599,8 @@
|
||||
stopOrbitFast();
|
||||
}
|
||||
lbCurrentGid = gid;
|
||||
_lastSelectedGid = gid;
|
||||
localStorage.setItem('lastSelectedGid', gid);
|
||||
lbUrls = data.urls;
|
||||
lbNames = data.names;
|
||||
lbIdx = startIdx !== undefined ? startIdx : (cycleIdx.get(gid) || 0);
|
||||
@@ -2613,6 +2634,60 @@
|
||||
openStudio(gid, idx);
|
||||
}
|
||||
|
||||
function jumpToPrevGroup() {
|
||||
if (_prevGid) openStudio(_prevGid);
|
||||
}
|
||||
|
||||
function formatPadValue(v) {
|
||||
if (v === undefined || v === null || v === '') return '0.00';
|
||||
let val = parseFloat(v);
|
||||
if (isNaN(val)) return '0.00';
|
||||
const s = v.toString();
|
||||
// User requested: show 0.00 if it contains a decimal see it as percentage.
|
||||
// if integer and its greater than 10 its pixel value
|
||||
if (s.includes('.') || s.includes('%')) return val.toFixed(2);
|
||||
if (val > 10) return val.toString();
|
||||
return val.toFixed(2);
|
||||
}
|
||||
|
||||
function updatePadPreview() {
|
||||
const viewer = document.getElementById('studioViewer');
|
||||
if (!viewer) return;
|
||||
// Clean up existing preview overlays
|
||||
viewer.querySelectorAll('.pad-preview-overlay').forEach(el => el.remove());
|
||||
|
||||
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'];
|
||||
sides.forEach(side => {
|
||||
const vStr = getV(side);
|
||||
if (!vStr || vStr === '0' || vStr === '0.00') return;
|
||||
|
||||
const val = parseFloat(vStr);
|
||||
if (isNaN(val) || val <= 0) return;
|
||||
|
||||
const isPercent = vStr.includes('%') || vStr.includes('.');
|
||||
const width = isPercent ? (val + '%') : (val + 'px');
|
||||
|
||||
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;
|
||||
} else {
|
||||
overlay.style.right = '0';
|
||||
overlay.style.width = width;
|
||||
}
|
||||
viewer.appendChild(overlay);
|
||||
});
|
||||
}
|
||||
|
||||
function closeStudio() {
|
||||
document.getElementById('studio').classList.remove('open');
|
||||
document.body.style.overflow = '';
|
||||
@@ -2749,6 +2824,9 @@
|
||||
lbImgEl.src = lbUrls[lbIdx];
|
||||
}
|
||||
|
||||
const backBtn = document.getElementById('lbBackBtn');
|
||||
if (backBtn) backBtn.style.display = _prevGid ? '' : 'none';
|
||||
|
||||
const groupNameInput = document.getElementById('lbGroupNameInput');
|
||||
groupNameInput.value = groupNames[lbCurrentGid] || '';
|
||||
|
||||
@@ -3853,10 +3931,13 @@
|
||||
const bar = document.createElement('div');
|
||||
bar.id = 'padBar';
|
||||
bar.style.cssText = 'position:absolute;top:0;left:0;right:0;display:flex;gap:8px;padding:8px;background:rgba(0,0,0,0.85);z-index:101;justify-content:flex-end;align-items:center;flex-wrap:wrap;';
|
||||
const numInput = (id, label) =>
|
||||
`<label style="display:flex;align-items:center;gap:3px;font-size:11px;color:#ccc">${label}<input type="text" id="${id}" value="0" 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>`;
|
||||
const numInput = (id, label) => {
|
||||
const onInput = `updatePadPreview()`;
|
||||
const onBlur = `this.value = formatPadValue(this.value); updatePadPreview()`;
|
||||
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):</span>`
|
||||
`<span style="flex:1;font-size:11px;color:#aaa">Pad canvas (px or %):</span>`
|
||||
+ numInput('padTop','↑')
|
||||
+ numInput('padRight','→')
|
||||
+ numInput('padBottom','↓')
|
||||
@@ -3873,10 +3954,13 @@
|
||||
+ `<button class="sb-btn" onclick="cancelManualPad()">Cancel</button>`
|
||||
+ `<button class="sb-btn primary" id="padConfirmBtn" onclick="confirmManualPad()">Pad</button>`;
|
||||
viewer.appendChild(bar);
|
||||
updatePadPreview();
|
||||
}
|
||||
|
||||
function cancelManualPad() {
|
||||
document.getElementById('padBar')?.remove();
|
||||
// Clean up preview overlays
|
||||
document.querySelectorAll('.pad-preview-overlay').forEach(el => el.remove());
|
||||
}
|
||||
|
||||
async function confirmManualPad() {
|
||||
@@ -4880,7 +4964,7 @@
|
||||
: `<img src="${firstVisUrl}" alt="${label}" loading="${isRecent ? 'eager' : 'lazy'}" onerror="this.style.opacity='0.2'">`;
|
||||
|
||||
return `
|
||||
<div class="image-card ${isNew ? 'new' : ''}" data-group="${safeGid}" onclick="handleCardClick(this)">
|
||||
<div class="image-card ${isNew ? 'new' : ''} ${_lastSelectedGid === gid ? 'highlight-prev' : ''}" data-group="${safeGid}" onclick="handleCardClick(this)">
|
||||
<div class="image-actions">
|
||||
${!isVideoCard ? `<div class="action-btn" title="Remove Background for Group" onclick="removeBgGroup(this, event)">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -5435,6 +5519,11 @@
|
||||
if ((e.key === 'p' || e.key === 'P') && tag !== 'INPUT' && tag !== 'TEXTAREA') {
|
||||
togglePrivacyMode(); return;
|
||||
}
|
||||
if ((e.key === 'a' || e.key === 'A') && tag !== 'INPUT' && tag !== 'TEXTAREA') {
|
||||
if (document.getElementById('studio').classList.contains('open')) {
|
||||
startManualPad(); return;
|
||||
}
|
||||
}
|
||||
if ((e.key === 'g' || e.key === 'G') && tag !== 'INPUT' && tag !== 'TEXTAREA') {
|
||||
if (_activeSidebarTab === 'generate') {
|
||||
const btn = document.getElementById('sbGenBtn');
|
||||
@@ -5831,10 +5920,12 @@
|
||||
<span style="font-size:11px;color:#666" title="Expand canvas before generation — gives the model space to outpaint">Pad:</span>
|
||||
${['Top','Right','Bottom','Left'].map(side => {
|
||||
const val = side==='Top'?_sbPadTop:side==='Right'?_sbPadRight:side==='Bottom'?_sbPadBottom:_sbPadLeft;
|
||||
const onInput = `updateSbGenBtn(); updatePadPreview()`;
|
||||
const onBlur = `this.value = formatPadValue(this.value); ${onInput}`;
|
||||
return `<label style="display:flex;align-items:center;gap:2px;font-size:11px;color:#888">${side[0]}
|
||||
<input type="text" id="sbPad${side}" value="${val}" placeholder="px or %"
|
||||
style="width:48px;background:#111;border:1px solid #2a2a2a;color:#ccc;border-radius:4px;padding:2px 4px;font-size:11px;text-align:right"
|
||||
oninput="updateSbGenBtn()"></label>`;
|
||||
oninput="${onInput}" onblur="${onBlur}"></label>`;
|
||||
}).join('')}
|
||||
<label style="display:flex;align-items:center;gap:3px;font-size:11px;color:#888;cursor:pointer" title="Instruct the model to fill in the padded area">
|
||||
<input type="checkbox" id="sbPadOutpaint" ${(_sbPadOutpaint??false)?'checked':''} onchange="updateSbGenBtn()" style="cursor:pointer">Outpaint</label>
|
||||
@@ -6087,7 +6178,13 @@
|
||||
bar.innerHTML = overlay.map(a => {
|
||||
const pSafe = a.prompt.replace(/'/g, "\\'");
|
||||
return `<button onclick="submitSingleAngle('${pSafe}')" title="${escHtml(a.name)}">${a.icon}</button>`;
|
||||
}).join('');
|
||||
}).join('') + `
|
||||
<button onclick="startManualPad()" title="Pad canvas (Expand image)" style="color:#f59e0b">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin:auto;display:block">
|
||||
<path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/>
|
||||
<path d="M12 8v8m-4-4h8"/>
|
||||
</svg>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
function updateSbGenBtn() {
|
||||
|
||||
Reference in New Issue
Block a user