Skip to content

Commit 502b1ee

Browse files
authored
fix(mode-r): add cover resize support for Mode R (#313)
Use resizeForModeRCover to resize directly from cropped image to Mode R doubled horizontal resolution, preserving aspect ratio with proper crop.
1 parent cf6a7c1 commit 502b1ee

File tree

1 file changed

+100
-21
lines changed

1 file changed

+100
-21
lines changed

src/app/store/preview/mode-r/mode-r-image.ts

Lines changed: 100 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,76 @@ export function resizeForModeRAuto(
9090
return outCtx.getImageData(0, 0, targetWidth, targetHeight)
9191
}
9292

93+
/**
94+
* Resize image to Mode R target dimensions for COVER mode
95+
*
96+
* Cover mode: Scale the image to fill the target dimensions completely,
97+
* cropping any excess. The image is centered, so cropping is symmetric.
98+
* Mode R has SQUARE perceived pixels, so no aspect ratio correction needed.
99+
*
100+
* @param src - Source ImageData (from cropped image)
101+
* @param targetWidth - Target width (e.g., 320 for standard Mode 0 R)
102+
* @param targetHeight - Target height (e.g., 200 for standard Mode 0 R)
103+
* @returns ImageData filled with the scaled and cropped image
104+
*/
105+
export function resizeForModeRCover(
106+
src: ImageData,
107+
targetWidth: number,
108+
targetHeight: number
109+
): ImageData {
110+
// Mode R has SQUARE perceived pixels
111+
const sourceAspect = src.width / src.height
112+
const targetAspect = targetWidth / targetHeight
113+
114+
// Create temporary canvas for the source
115+
const srcCanvas = document.createElement('canvas')
116+
srcCanvas.width = src.width
117+
srcCanvas.height = src.height
118+
const srcCtx = srcCanvas.getContext('2d')!
119+
srcCtx.putImageData(src, 0, 0)
120+
121+
// Calculate source region to crop (cover mode)
122+
let srcX = 0
123+
let srcY = 0
124+
let srcW = src.width
125+
let srcH = src.height
126+
127+
if (sourceAspect > targetAspect) {
128+
// Source is wider: crop horizontally
129+
const newWidth = src.height * targetAspect
130+
srcX = (src.width - newWidth) / 2
131+
srcW = newWidth
132+
} else if (sourceAspect < targetAspect) {
133+
// Source is taller: crop vertically
134+
const newHeight = src.width / targetAspect
135+
srcY = (src.height - newHeight) / 2
136+
srcH = newHeight
137+
}
138+
139+
// Create output canvas at full target dimensions
140+
const outCanvas = document.createElement('canvas')
141+
outCanvas.width = targetWidth
142+
outCanvas.height = targetHeight
143+
const outCtx = outCanvas.getContext('2d')!
144+
outCtx.imageSmoothingEnabled = true
145+
outCtx.imageSmoothingQuality = 'high'
146+
147+
// Draw cropped and scaled image
148+
outCtx.drawImage(
149+
srcCanvas,
150+
srcX,
151+
srcY,
152+
srcW,
153+
srcH,
154+
0,
155+
0,
156+
targetWidth,
157+
targetHeight
158+
)
159+
160+
return outCtx.getImageData(0, 0, targetWidth, targetHeight)
161+
}
162+
93163
/**
94164
* Resize image to Mode R target dimensions for ORIGIN mode
95165
*
@@ -166,22 +236,20 @@ export const modeRSourceImageAtom = atom(async (get) => {
166236
const resizeMode = get(resizeModeAtom)
167237
const centerImage = get(centerImageAtom)
168238

169-
// In 'origin' mode, use the cropped image BEFORE the standard resize pipeline
170-
// because the standard pipeline compresses to Mode 0 dimensions (160×200)
171-
// but Mode R needs the full 320×200 resolution
172-
// In 'auto' and 'cover' modes, use resizedImageAtom (NOT smoothedImageAtom) to skip horizontal smoothing
173-
// Mode R has its own sub-pixel resolution, horizontal smoothing would blur it
239+
// Target dimensions for Mode R: doubled horizontal resolution
240+
const targetWidth = modeConfig.width * 2 // 320 for standard Mode 0
241+
const targetHeight = modeConfig.height // 200 for standard
242+
243+
// Select source image based on resize mode:
244+
// - 'origin' and 'cover': use cropped image (before resize) to apply Mode R-specific resize
245+
// - 'auto': use resizedImageAtom (already fit to Mode 0 dimensions, then scaled to Mode R)
174246
const sourceImage =
175-
resizeMode === 'origin'
247+
resizeMode === 'origin' || resizeMode === 'cover'
176248
? await get(croppedImageAtom)
177249
: await get(resizedImageAtom)
178250

179251
if (!sourceImage) return null
180252

181-
// Target dimensions for Mode R: doubled horizontal resolution
182-
const targetWidth = modeConfig.width * 2 // 320 for standard Mode 0
183-
const targetHeight = modeConfig.height // 200 for standard
184-
185253
// Skip resize if in 'origin' mode and image already matches target
186254
if (
187255
resizeMode === 'origin' &&
@@ -200,21 +268,32 @@ export const modeRSourceImageAtom = atom(async (get) => {
200268
// Resize to Mode R target dimensions
201269
// Use different resize strategy based on resize mode:
202270
// - origin: pixel-perfect 1:1 mapping (like Mode 1)
203-
// - auto/cover: fit with aspect ratio preservation (cover already cropped by resizedImageAtom)
204-
const modeRImage =
205-
resizeMode === 'origin'
206-
? resizeForModeROrigin(
207-
sourceImage,
208-
targetWidth,
209-
targetHeight,
210-
centerImage
211-
)
212-
: resizeForModeRAuto(sourceImage, targetWidth, targetHeight, centerImage)
271+
// - cover: fill target dimensions, cropping excess (preserves aspect ratio)
272+
// - auto: fit with aspect ratio preservation (may have margins)
273+
let modeRImage: ImageData
274+
if (resizeMode === 'origin') {
275+
modeRImage = resizeForModeROrigin(
276+
sourceImage,
277+
targetWidth,
278+
targetHeight,
279+
centerImage
280+
)
281+
} else if (resizeMode === 'cover') {
282+
modeRImage = resizeForModeRCover(sourceImage, targetWidth, targetHeight)
283+
} else {
284+
modeRImage = resizeForModeRAuto(
285+
sourceImage,
286+
targetWidth,
287+
targetHeight,
288+
centerImage
289+
)
290+
}
213291

214292
logger.info('[Mode R] Source image resized to true high resolution', {
215293
sourceSize: `${sourceImage.width}×${sourceImage.height}`,
216294
modeRSize: `${modeRImage.width}×${modeRImage.height}`,
217-
targetDimensions: `${targetWidth}×${targetHeight}`
295+
targetDimensions: `${targetWidth}×${targetHeight}`,
296+
resizeMode
218297
})
219298

220299
return modeRImage

0 commit comments

Comments
 (0)