commit 204010b9842201c033a1fab89cc95c593543f71e
parent 558740cb1b6d017c8eaeea2aeaee8277f374972b
Author: Hunter
Date:   Wed, 29 Jul 2026 14:56:57 -0400

fix scale/pan flash when adding or duplicating images

Diffstat:
Mresources/images.js | 54++++++++++++++++++------------------------------------
1 file changed, 18 insertions(+), 36 deletions(-)

diff --git a/resources/images.js b/resources/images.js @@ -34,8 +34,12 @@ async function loadImageDimensions(file) { const img = new Image(); const dimensions = await new Promise(resolve => { img.onload = () => { - const aspectRatio = img.width / img.height; - resolve(calculateImageDimensions(aspectRatio)); + const aspectRatio = img.naturalWidth / img.naturalHeight; + resolve({ + ...calculateImageDimensions(aspectRatio), + naturalWidth: img.naturalWidth, + naturalHeight: img.naturalHeight + }); }; img.src = dataUrl; }); @@ -77,7 +81,7 @@ async function processAndAddImages(files, dropX = 0, dropY = 0) { let wrappedOffset = 0; - for (const { idx, dataUrl, widthCells, heightCells } of imageDataArray) { + for (const { idx, dataUrl, widthCells, heightCells, naturalWidth, naturalHeight } of imageDataArray) { // Calculate position with diagonal offset let xCell = baseXCell + idx; let yCell = baseYCell + idx; @@ -94,12 +98,12 @@ async function processAndAddImages(files, dropX = 0, dropY = 0) { wrappedOffset++; } - const imageData = addImage(dataUrl, xCell, yCell, widthCells, heightCells); + const imageData = addImage(dataUrl, xCell, yCell, widthCells, heightCells, { naturalWidth, naturalHeight }); imageData.container.style.zIndex = baseZIndex + idx; } } -function addImage(src, xCell, yCell, widthCells, heightCells) { +function addImage(src, xCell, yCell, widthCells, heightCells, initialState) { const container = document.createElement('div'); container.className = 'image-container'; highestZIndex++; @@ -146,20 +150,15 @@ function addImage(src, xCell, yCell, widthCells, heightCells) { // Store natural image dimensions for calculations naturalWidth: 0, naturalHeight: 0, - baseScale: 1 // scale needed to cover container + baseScale: 1, // scale needed to cover container + // Known up front when copying an already-loaded image, so the first paint is correct + ...initialState }; // Calculate dimensions and scale once image loads img.onload = () => { imageData.naturalWidth = img.naturalWidth; imageData.naturalHeight = img.naturalHeight; - - // Calculate base scale to cover container (mimics object-fit: cover) - const bounds = getPixelPerfectBounds(imageData.xCell, imageData.yCell, imageData.widthCells, imageData.heightCells); - const scaleX = bounds.width / img.naturalWidth; - const scaleY = bounds.height / img.naturalHeight; - imageData.baseScale = Math.max(scaleX, scaleY); - updateImagePosition(imageData); }; images.push(imageData); @@ -218,34 +217,17 @@ function rotateImages(group) { // Copy an image (and its pan/zoom/rotation) to a new grid position function duplicateImage(imageData, newXCell, newYCell) { const imgElement = imageData.container.querySelector('img'); - const newImageData = addImage( + const { panX, panY, userScale, rotation, naturalWidth, naturalHeight } = imageData; + + // The source is already loaded, so the copy's crop can be set before it ever paints + return addImage( imgElement.src, newXCell, newYCell, imageData.widthCells, - imageData.heightCells + imageData.heightCells, + { panX, panY, userScale, rotation, naturalWidth, naturalHeight } ); - - const { panX, panY, userScale, rotation } = imageData; - const applySettings = () => { - newImageData.panX = panX; - newImageData.panY = panY; - newImageData.userScale = userScale; - newImageData.rotation = rotation; - updateImagePosition(newImageData); - }; - - // Settings can only be applied once the copy's natural dimensions are known - const newImg = newImageData.container.querySelector('img'); - const originalOnload = newImg.onload; - newImg.onload = () => { - if (originalOnload) originalOnload.call(newImg); - applySettings(); - }; - - if (newImg.complete && newImageData.naturalWidth > 0) applySettings(); - - return newImageData; } function deleteImage(imageData) {