fix: incorrect height rounding when resizing images with sharp (#11634)

This PR fixes an issue where the Sharp `.resize()` function would round
down an auto-scaled dimension when `fastShrinkOnLoad` was enabled
(enabled by default).

This caused slight discrepancies in height calculations in certain edge
cases.

Be default (`fastShrinkOnLoad: true`), Sharp:
- Uses the built-in shrink-on-load feature for JPEG and WebP
- It is an optimization that prioritizes speed over precision when
resizing images

By setting `fastShrinkOnLoad: false`, we force Sharp to:
- Perform a more accurate resize operation instead of relying on quick
pre-shrink methods.

### Before / Context:

- Upload an image with original dimensions of 1500 × 735
- Define an `imageSize` of the following:
```
{
  name: 'thumbnail',
  width: 300,
},
```

#### Calculation:

`originalAspectRatio = 1500 / 735 ≈ 2.04081632653`

`resizeHeight = 300 / 2.04081632653`
`resizeHeight = 147`

However, Sharp's `.resize()` calculation would output:

`resizeHeight = 146`

This lead to an error of:

```
[17:05:13] ERROR: extract_area: bad extract area
    err: {
      "type": "Error",
      "message": "extract_area: bad extract area",
      "stack":
          Error: extract_area: bad extract area
    }
```

### After:

Sharp's `.resize()` calculation now correctly outputs:

`resizeHeight = 147`
This commit is contained in:
Patrik
2025-03-12 09:48:05 -04:00
committed by GitHub
parent 7be02194d6
commit 9d6583d9de
5 changed files with 38 additions and 0 deletions

View File

@@ -360,6 +360,7 @@ export async function resizeAndTransformImageSizes({
const prioritizeHeight = resizeAspectRatio < originalAspectRatio
// Scales the image before extracting from it
resized = imageToResize.resize({
fastShrinkOnLoad: false,
height: prioritizeHeight ? resizeHeight : undefined,
width: prioritizeHeight ? undefined : resizeWidth,
})

View File

@@ -390,6 +390,10 @@ export default buildConfigWithDefaults({
height: 300,
width: 300,
},
{
name: 'undefinedHeight',
width: 300,
},
],
pasteURL: false,
},

View File

@@ -400,6 +400,20 @@ describe('Uploads', () => {
await expect(page.locator('.row-3 .cell-title')).toContainText('draft')
})
test('should upload edge case media when an image size contains an undefined height', async () => {
await page.goto(mediaURL.create)
await page.setInputFiles(
'input[type="file"]',
path.resolve(dirname, './test-image-1500x735.jpeg'),
)
const filename = page.locator('.file-field__filename')
await expect(filename).toHaveValue('test-image-1500x735.jpeg')
await saveDocAndAssert(page)
})
describe('filterOptions', () => {
test('should restrict mimetype based on filterOptions', async () => {
const audioDoc = (

View File

@@ -64,6 +64,7 @@ export interface Config {
auth: {
users: UserAuthOperations;
};
blocks: {};
collections: {
relation: Relation;
audio: Audio;
@@ -327,6 +328,14 @@ export interface Media {
filesize?: number | null;
filename?: string | null;
};
undefinedHeight?: {
url?: string | null;
width?: number | null;
height?: number | null;
mimeType?: string | null;
filesize?: number | null;
filename?: string | null;
};
};
}
/**
@@ -2018,6 +2027,16 @@ export interface MediaSelect<T extends boolean = true> {
filesize?: T;
filename?: T;
};
undefinedHeight?:
| T
| {
url?: T;
width?: T;
height?: T;
mimeType?: T;
filesize?: T;
filename?: T;
};
};
}
/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB