Skip to content

Commit cf6a7c1

Browse files
authored
feat(resize): add "cover" resize mode with crop to fill (#312)
- Add new resize mode "cover" that scales image to fill CPC dimensions while preserving aspect ratio and cropping excess (centered) - Fix aspect ratio calculation for non-square CPC pixels - Use cropped image for quantization in all modes for consistent palettes - Add translations for EN/DE/ES - Update EGX and Mode R pipelines to support cover mode
1 parent e873dbf commit cf6a7c1

File tree

9 files changed

+249
-214
lines changed

9 files changed

+249
-214
lines changed

src/locales/de/messages.po

Lines changed: 57 additions & 49 deletions
Large diffs are not rendered by default.

src/locales/de/messages.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/locales/en/messages.po

Lines changed: 57 additions & 49 deletions
Large diffs are not rendered by default.

src/locales/en/messages.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/locales/es/messages.po

Lines changed: 57 additions & 49 deletions
Large diffs are not rendered by default.

src/locales/es/messages.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/locales/fr/messages.po

Lines changed: 57 additions & 49 deletions
Large diffs are not rendered by default.

src/locales/fr/messages.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/source/image-resize.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ function resizeOrigin(
111111
* cropping any excess. The image is centered, so cropping is symmetric.
112112
*
113113
* Similar to CSS background-size: cover or object-fit: cover.
114+
* Takes into account CPC pixel aspect ratio to preserve perceived proportions.
114115
*
115116
* @param sourceCanvas - Source canvas with the image
116117
* @param selection - Selection rectangle within the source
@@ -139,31 +140,33 @@ function resizeCover(
139140
throw new Error('Failed to get 2D context')
140141
}
141142

142-
// Source dimensions adjusted for pixel ratio
143-
// For Mode 0 (ratio 2:1), we need to consider that 1 CPC pixel = 2 source pixels horizontally
144-
const sourceAspect = selection.width / pixelRatio / selection.height
145-
const targetAspect = targetWidth / targetHeight
143+
// Calculate PERCEIVED aspect ratios
144+
// Source image has square pixels
145+
const sourceAspect = selection.width / selection.height
146+
// CPC target has non-square pixels in mode 0 (2:1) and mode 2 (0.5:1)
147+
// The perceived aspect ratio = (targetWidth * pixelRatio) / targetHeight
148+
const targetPerceivedAspect = (targetWidth * pixelRatio) / targetHeight
146149

147150
let srcX = selection.sx
148151
let srcY = selection.sy
149152
let srcW = selection.width
150153
let srcH = selection.height
151154

152-
// Cover mode: scale to fill, then crop excess
153-
// We need to determine which dimension to match
154-
if (sourceAspect > targetAspect) {
155-
// Source is wider than target: crop horizontally
156-
// Match height, crop width
157-
const newWidth = selection.height * targetAspect * pixelRatio
155+
// Cover mode: scale to fill, crop excess to match perceived aspect ratio
156+
if (sourceAspect > targetPerceivedAspect) {
157+
// Source is wider than target (perceived): crop horizontally
158+
// Keep full height, calculate width to match target aspect ratio
159+
const newWidth = selection.height * targetPerceivedAspect
158160
srcX = selection.sx + (selection.width - newWidth) / 2
159161
srcW = newWidth
160-
} else {
161-
// Source is taller than target: crop vertically
162-
// Match width, crop height
163-
const newHeight = selection.width / pixelRatio / targetAspect
162+
} else if (sourceAspect < targetPerceivedAspect) {
163+
// Source is taller than target (perceived): crop vertically
164+
// Keep full width, calculate height to match target aspect ratio
165+
const newHeight = selection.width / targetPerceivedAspect
164166
srcY = selection.sy + (selection.height - newHeight) / 2
165167
srcH = newHeight
166168
}
169+
// If equal, no cropping needed
167170

168171
ctx.imageSmoothingEnabled = true
169172
ctx.drawImage(

0 commit comments

Comments
 (0)