Add viewer inspect zoom controls
This commit is contained in:
parent
23d9f19695
commit
1f555869a0
4 changed files with 74 additions and 11 deletions
|
|
@ -104,6 +104,7 @@ type AppState = {
|
|||
solverStepBudget: number;
|
||||
lossAxisMax: number | null;
|
||||
eventLog: LogItem[];
|
||||
viewerInspectZoom: number;
|
||||
paintColor: [number, number, number, number];
|
||||
editorBaseAsset: ImageAsset | null;
|
||||
editorDirty: boolean;
|
||||
|
|
@ -143,6 +144,7 @@ const leftCanvas = byId<HTMLCanvasElement>("leftCanvas");
|
|||
const rightCanvas = byId<HTMLCanvasElement>("rightCanvas");
|
||||
const leftVizLabel = byId<HTMLDivElement>("leftVizLabel");
|
||||
const rightVizLabel = byId<HTMLDivElement>("rightVizLabel");
|
||||
const inspectZoomInput = byId<HTMLSelectElement>("inspectZoomInput");
|
||||
const paintSwatch = byId<HTMLDivElement>("paintSwatch");
|
||||
const zoomInput = byId<HTMLInputElement>("zoomInput");
|
||||
const zoomValue = byId<HTMLSpanElement>("zoomValue");
|
||||
|
|
@ -222,6 +224,7 @@ const state: AppState = {
|
|||
solverStepBudget: 0,
|
||||
lossAxisMax: null,
|
||||
eventLog: [],
|
||||
viewerInspectZoom: Number(inspectZoomInput.value),
|
||||
paintColor: [255, 255, 255, 255],
|
||||
editorBaseAsset: null,
|
||||
editorDirty: false,
|
||||
|
|
@ -423,16 +426,19 @@ async function drawAssetInspection(
|
|||
|
||||
const imageWidth = image.naturalWidth || asset.width;
|
||||
const imageHeight = image.naturalHeight || asset.height;
|
||||
const viewportWidth = Math.min(displayWidth, imageWidth);
|
||||
const viewportHeight = Math.min(displayHeight, imageHeight);
|
||||
const zoomMultiplier = Math.max(1, state.viewerInspectZoom || 1);
|
||||
const viewportWidth = Math.max(1, Math.min(Math.floor(displayWidth / zoomMultiplier), imageWidth));
|
||||
const viewportHeight = Math.max(1, Math.min(Math.floor(displayHeight / zoomMultiplier), imageHeight));
|
||||
const destWidth = Math.min(displayWidth, viewportWidth * zoomMultiplier);
|
||||
const destHeight = Math.min(displayHeight, viewportHeight * zoomMultiplier);
|
||||
const focusX = Math.max(0, Math.min(1, focus.xRatio));
|
||||
const focusY = Math.max(0, Math.min(1, focus.yRatio));
|
||||
const centerX = imageWidth <= displayWidth ? imageWidth * 0.5 : focusX * imageWidth;
|
||||
const centerY = imageHeight <= displayHeight ? imageHeight * 0.5 : focusY * imageHeight;
|
||||
const sourceX = Math.max(0, Math.min(imageWidth - viewportWidth, Math.round(centerX - viewportWidth * 0.5)));
|
||||
const sourceY = Math.max(0, Math.min(imageHeight - viewportHeight, Math.round(centerY - viewportHeight * 0.5)));
|
||||
const destX = Math.floor((displayWidth - viewportWidth) * 0.5);
|
||||
const destY = Math.floor((displayHeight - viewportHeight) * 0.5);
|
||||
const destX = Math.floor((displayWidth - destWidth) * 0.5);
|
||||
const destY = Math.floor((displayHeight - destHeight) * 0.5);
|
||||
|
||||
context.drawImage(
|
||||
image,
|
||||
|
|
@ -442,8 +448,8 @@ async function drawAssetInspection(
|
|||
viewportHeight,
|
||||
destX,
|
||||
destY,
|
||||
viewportWidth,
|
||||
viewportHeight,
|
||||
destWidth,
|
||||
destHeight,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1549,6 +1555,14 @@ runButton.addEventListener("click", () => {
|
|||
void startRun();
|
||||
});
|
||||
|
||||
inspectZoomInput.addEventListener("change", () => {
|
||||
const value = Number(inspectZoomInput.value);
|
||||
state.viewerInspectZoom = Number.isFinite(value) && value > 0 ? value : 1;
|
||||
if (viewerInspect) {
|
||||
scheduleViewerRender();
|
||||
}
|
||||
});
|
||||
|
||||
zoomInput.addEventListener("input", () => {
|
||||
setZoom(Number(zoomInput.value));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const leftCanvas = byId("leftCanvas");
|
|||
const rightCanvas = byId("rightCanvas");
|
||||
const leftVizLabel = byId("leftVizLabel");
|
||||
const rightVizLabel = byId("rightVizLabel");
|
||||
const inspectZoomInput = byId("inspectZoomInput");
|
||||
const paintSwatch = byId("paintSwatch");
|
||||
const zoomInput = byId("zoomInput");
|
||||
const zoomValue = byId("zoomValue");
|
||||
|
|
@ -99,6 +100,7 @@ const state = {
|
|||
solverStepBudget: 0,
|
||||
lossAxisMax: null,
|
||||
eventLog: [],
|
||||
viewerInspectZoom: Number(inspectZoomInput.value),
|
||||
paintColor: [255, 255, 255, 255],
|
||||
editorBaseAsset: null,
|
||||
editorDirty: false,
|
||||
|
|
@ -279,17 +281,20 @@ async function drawAssetInspection(canvas, asset, focus) {
|
|||
context.fillRect(0, 0, displayWidth, displayHeight);
|
||||
const imageWidth = image.naturalWidth || asset.width;
|
||||
const imageHeight = image.naturalHeight || asset.height;
|
||||
const viewportWidth = Math.min(displayWidth, imageWidth);
|
||||
const viewportHeight = Math.min(displayHeight, imageHeight);
|
||||
const zoomMultiplier = Math.max(1, state.viewerInspectZoom || 1);
|
||||
const viewportWidth = Math.max(1, Math.min(Math.floor(displayWidth / zoomMultiplier), imageWidth));
|
||||
const viewportHeight = Math.max(1, Math.min(Math.floor(displayHeight / zoomMultiplier), imageHeight));
|
||||
const destWidth = Math.min(displayWidth, viewportWidth * zoomMultiplier);
|
||||
const destHeight = Math.min(displayHeight, viewportHeight * zoomMultiplier);
|
||||
const focusX = Math.max(0, Math.min(1, focus.xRatio));
|
||||
const focusY = Math.max(0, Math.min(1, focus.yRatio));
|
||||
const centerX = imageWidth <= displayWidth ? imageWidth * 0.5 : focusX * imageWidth;
|
||||
const centerY = imageHeight <= displayHeight ? imageHeight * 0.5 : focusY * imageHeight;
|
||||
const sourceX = Math.max(0, Math.min(imageWidth - viewportWidth, Math.round(centerX - viewportWidth * 0.5)));
|
||||
const sourceY = Math.max(0, Math.min(imageHeight - viewportHeight, Math.round(centerY - viewportHeight * 0.5)));
|
||||
const destX = Math.floor((displayWidth - viewportWidth) * 0.5);
|
||||
const destY = Math.floor((displayHeight - viewportHeight) * 0.5);
|
||||
context.drawImage(image, sourceX, sourceY, viewportWidth, viewportHeight, destX, destY, viewportWidth, viewportHeight);
|
||||
const destX = Math.floor((displayWidth - destWidth) * 0.5);
|
||||
const destY = Math.floor((displayHeight - destHeight) * 0.5);
|
||||
context.drawImage(image, sourceX, sourceY, viewportWidth, viewportHeight, destX, destY, destWidth, destHeight);
|
||||
}
|
||||
async function fileToImageAsset(file) {
|
||||
const dataUrl = await new Promise((resolve, reject) => {
|
||||
|
|
@ -1292,6 +1297,13 @@ dropzone.addEventListener("drop", (event) => {
|
|||
runButton.addEventListener("click", () => {
|
||||
void startRun();
|
||||
});
|
||||
inspectZoomInput.addEventListener("change", () => {
|
||||
const value = Number(inspectZoomInput.value);
|
||||
state.viewerInspectZoom = Number.isFinite(value) && value > 0 ? value : 1;
|
||||
if (viewerInspect) {
|
||||
scheduleViewerRender();
|
||||
}
|
||||
});
|
||||
zoomInput.addEventListener("input", () => {
|
||||
setZoom(Number(zoomInput.value));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -61,6 +61,18 @@
|
|||
<canvas id="rightCanvas"></canvas>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="viewer-inspect-row">
|
||||
<label class="field viewer-inspect-field">
|
||||
<span>Inspect Zoom</span>
|
||||
<select id="inspectZoomInput">
|
||||
<option value="1">Native source</option>
|
||||
<option value="2">2x</option>
|
||||
<option value="4">4x</option>
|
||||
<option value="8">8x</option>
|
||||
</select>
|
||||
</label>
|
||||
<p class="viewer-inspect-copy">Hold left mouse in either preview to inspect both panels together. Drag while holding to pan around. Release to drop back to fit view.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card editor-panel">
|
||||
|
|
|
|||
|
|
@ -652,6 +652,27 @@ p {
|
|||
gap: 10px;
|
||||
}
|
||||
|
||||
.viewer-inspect-row {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 2px 0;
|
||||
}
|
||||
|
||||
.viewer-inspect-field {
|
||||
width: min(100%, 208px);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.viewer-inspect-copy {
|
||||
flex: 1 1 320px;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2;
|
||||
color: var(--ui-text-muted);
|
||||
}
|
||||
|
||||
.viz-card {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
|
|
@ -1019,6 +1040,10 @@ select:focus-visible {
|
|||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.viewer-inspect-row {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.card-head {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue