Skip to content

Commit bcd277e

Browse files
authored
fix: resizing animated images (#6623)
Fixes resizing of animated images V2 PR [here](#6621)
1 parent da35afb commit bcd277e

File tree

3 files changed

+31
-10
lines changed

3 files changed

+31
-10
lines changed

packages/payload/src/uploads/cropImage.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { SharpOptions } from 'sharp'
2+
13
export const percentToPixel = (value, dimension) => {
24
return Math.floor((parseFloat(value) / 100) * dimension)
35
}
@@ -6,14 +8,20 @@ export async function cropImage({ cropData, dimensions, file, sharp }) {
68
try {
79
const { height, width, x, y } = cropData
810

11+
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
12+
13+
const sharpOptions: SharpOptions = {}
14+
15+
if (fileIsAnimated) sharpOptions.animated = true
16+
917
const formattedCropData = {
1018
height: percentToPixel(height, dimensions.height),
1119
left: percentToPixel(x, dimensions.width),
1220
top: percentToPixel(y, dimensions.height),
1321
width: percentToPixel(width, dimensions.width),
1422
}
1523

16-
const cropped = sharp(file.tempFilePath || file.data).extract(formattedCropData)
24+
const cropped = sharp(file.tempFilePath || file.data, sharpOptions).extract(formattedCropData)
1725

1826
return await cropped.toBuffer({
1927
resolveWithObject: true,

packages/payload/src/uploads/generateFileData.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export const generateFileData = async <T>({
114114
let newData = data
115115
const filesToSave: FileToSave[] = []
116116
const fileData: Partial<FileData> = {}
117-
const fileIsAnimated = file.mimetype === 'image/gif' || file.mimetype === 'image/webp'
117+
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
118118
const cropData =
119119
typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined
120120

@@ -128,7 +128,7 @@ export const generateFileData = async <T>({
128128
let mime: string
129129
const fileHasAdjustments =
130130
fileSupportsResize &&
131-
Boolean(resizeOptions || formatOptions || trimOptions || file.tempFilePath)
131+
Boolean(resizeOptions || formatOptions || imageSizes || trimOptions || file.tempFilePath)
132132

133133
const sharpOptions: SharpOptions = {}
134134

@@ -202,6 +202,7 @@ export const generateFileData = async <T>({
202202
let fileForResize = file
203203

204204
if (cropData && sharp) {
205+
const metadata = await sharpFile.metadata()
205206
const { data: croppedImage, info } = await cropImage({ cropData, dimensions, file, sharp })
206207

207208
filesToSave.push({
@@ -215,7 +216,7 @@ export const generateFileData = async <T>({
215216
size: info.size,
216217
}
217218
fileData.width = info.width
218-
fileData.height = info.height
219+
fileData.height = fileIsAnimated ? info.height / metadata.pages : info.height
219220
fileData.filesize = info.size
220221

221222
if (file.tempFilePath) {

packages/payload/src/uploads/imageResizer.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { OutputInfo } from 'sharp'
1+
import type { OutputInfo, Sharp, SharpOptions } from 'sharp'
22

33
import fileType from 'file-type'
44
const { fromBuffer } = fileType
@@ -219,7 +219,7 @@ const sanitizeResizeConfig = (resizeConfig: ImageSize): ImageSize => {
219219
*
220220
* The image will be resized according to the provided
221221
* resize config. If no image sizes are requested, the resolved data will be empty.
222-
* For every image that dos not need to be resized, an result object with `null`
222+
* For every image that does not need to be resized, a result object with `null`
223223
* parameters will be returned.
224224
*
225225
* @param resizeConfig - the resize config
@@ -256,7 +256,13 @@ export async function resizeAndTransformImageSizes({
256256
return defaultResult
257257
}
258258

259-
const sharpBase = sharp(file.tempFilePath || file.data).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
259+
// Determine if the file is animated
260+
const fileIsAnimated = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)
261+
const sharpOptions: SharpOptions = {}
262+
263+
if (fileIsAnimated) sharpOptions.animated = true
264+
265+
const sharpBase: Sharp | undefined = sharp(file.tempFilePath || file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
260266

261267
const results: ImageSizesResult[] = await Promise.all(
262268
imageSizes.map(async (imageResizeConfig): Promise<ImageSizesResult> => {
@@ -272,6 +278,8 @@ export async function resizeAndTransformImageSizes({
272278
const imageToResize = sharpBase.clone()
273279
let resized = imageToResize
274280

281+
const metadata = await sharpBase.metadata()
282+
275283
if (incomingFocalPoint && applyPayloadAdjustments(imageResizeConfig, dimensions)) {
276284
const { height: resizeHeight, width: resizeWidth } = imageResizeConfig
277285
const resizeAspectRatio = resizeWidth / resizeHeight
@@ -293,14 +301,18 @@ export async function resizeAndTransformImageSizes({
293301
const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX)
294302

295303
const safeResizeHeight = resizeHeight ?? scaledImageInfo.height
296-
const maxOffsetY = scaledImageInfo.height - safeResizeHeight
304+
305+
const maxOffsetY = fileIsAnimated
306+
? resizeHeight - safeResizeHeight
307+
: scaledImageInfo.height - safeResizeHeight
308+
297309
const topFocalEdge = Math.round(
298310
scaledImageInfo.height * (incomingFocalPoint.y / 100) - safeResizeHeight / 2,
299311
)
300312
const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY)
301313

302314
// extract the focal area from the scaled image
303-
resized = scaledImage.extract({
315+
resized = (fileIsAnimated ? imageToResize : scaledImage).extract({
304316
height: safeResizeHeight,
305317
left: safeOffsetX,
306318
top: safeOffsetY,
@@ -354,7 +366,7 @@ export async function resizeAndTransformImageSizes({
354366
name: imageResizeConfig.name,
355367
filename: imageNameWithDimensions,
356368
filesize: size,
357-
height,
369+
height: fileIsAnimated ? height / metadata.pages : height,
358370
mimeType: mimeInfo?.mime || mimeType,
359371
sizesToSave: [{ buffer: bufferData, path: imagePath }],
360372
width,

0 commit comments

Comments
 (0)