From 37d6a72fbb18c668bfb147f686f9f50135e9fc74 Mon Sep 17 00:00:00 2001 From: Lars Foleide Date: Wed, 11 Jun 2025 12:34:28 +0000 Subject: [PATCH] feat: add maxWidth and maxHeight options for finer image resizing control - Adds maxWidth and maxHeight to Options interface and docs - Updates handleMaxWidthOrHeight to apply maxWidth/maxHeight before maxWidthOrHeight - Updates README and types for new API --- README.md | 5 ++++- lib/index.d.ts | 4 ++++ lib/utils.js | 47 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 55e11ea..a372655 100644 --- a/README.md +++ b/README.md @@ -109,12 +109,15 @@ If this project helps you reduce the time to develop, you can buy me a cup of co ## API ### Main function ```javascript -// you should provide one of maxSizeMB, maxWidthOrHeight in the options +// you should provide one of maxSizeMB, maxWidthOrHeight, maxWidth, or maxHeight in the options const options: Options = { maxSizeMB: number, // (default: Number.POSITIVE_INFINITY) maxWidthOrHeight: number, // compressedFile will scale down by ratio to a point that width or height is smaller than maxWidthOrHeight (default: undefined) // but, automatically reduce the size to smaller than the maximum Canvas size supported by each browser. // Please check the Caveat part for details. + maxWidth: number, // (optional, new) clamp width only (preserve aspect ratio, no upscaling) + maxHeight: number, // (optional, new) clamp height only (preserve aspect ratio, no upscaling) + // If both maxWidth and maxHeight are set, the most restrictive is used. If maxWidthOrHeight is also set, it is applied after maxWidth/maxHeight. onProgress: Function, // optional, a function takes one progress argument (percentage from 0 to 100) useWebWorker: boolean, // optional, use multi-thread web worker, fallback to run in main-thread (default: true) libURL: string, // optional, the libURL of this library for importing script in Web Worker (default: https://cdn.jsdelivr.net/npm/browser-image-compression/dist/browser-image-compression.js) diff --git a/lib/index.d.ts b/lib/index.d.ts index e8cb912..4a0ca50 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -7,6 +7,10 @@ export interface Options { maxSizeMB?: number; /** @default undefined */ maxWidthOrHeight?: number; + /** @default undefined */ + maxWidth?: number; // new: clamp width only (preserve aspect ratio) + /** @default undefined */ + maxHeight?: number; // new: clamp height only (preserve aspect ratio) /** @default true */ useWebWorker?: boolean; /** @default 10 */ diff --git a/lib/utils.js b/lib/utils.js index a0335be..e539b61 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -380,28 +380,45 @@ export function getExifOrientation(file) { export function handleMaxWidthOrHeight(canvas, options) { const { width } = canvas; const { height } = canvas; - const { maxWidthOrHeight } = options; - - const needToHandle = isFinite(maxWidthOrHeight) && (width > maxWidthOrHeight || height > maxWidthOrHeight); - - let newCanvas = canvas; - let ctx; + const { maxWidthOrHeight, maxWidth, maxHeight } = options; + + let newWidth = width; + let newHeight = height; + + // Step 1: Apply maxWidth and/or maxHeight if provided + if (isFinite(maxWidth) || isFinite(maxHeight)) { + let widthRatio = isFinite(maxWidth) ? maxWidth / newWidth : 1; + let heightRatio = isFinite(maxHeight) ? maxHeight / newHeight : 1; + let ratio = Math.min(widthRatio, heightRatio, 1); // Don't upscale + if (ratio < 1) { + newWidth = Math.round(newWidth * ratio); + newHeight = Math.round(newHeight * ratio); + } + } - if (needToHandle) { - [newCanvas, ctx] = getNewCanvasAndCtx(width, height); - if (width > height) { - newCanvas.width = maxWidthOrHeight; - newCanvas.height = (height / width) * maxWidthOrHeight; + // Step 2: Apply maxWidthOrHeight if provided (on already-resized image) + if (isFinite(maxWidthOrHeight) && (newWidth > maxWidthOrHeight || newHeight > maxWidthOrHeight)) { + if (newWidth > newHeight) { + const ratio = maxWidthOrHeight / newWidth; + newWidth = maxWidthOrHeight; + newHeight = Math.round(newHeight * ratio); } else { - newCanvas.width = (width / height) * maxWidthOrHeight; - newCanvas.height = maxWidthOrHeight; + const ratio = maxWidthOrHeight / newHeight; + newHeight = maxWidthOrHeight; + newWidth = Math.round(newWidth * ratio); } - ctx.drawImage(canvas, 0, 0, newCanvas.width, newCanvas.height); + } + // Only create a new canvas if resizing is needed + if (newWidth !== width || newHeight !== height) { + let newCanvas, ctx; + [newCanvas, ctx] = getNewCanvasAndCtx(newWidth, newHeight); + ctx.drawImage(canvas, 0, 0, newWidth, newHeight); cleanupCanvasMemory(canvas); + return newCanvas; } - return newCanvas; + return canvas; } /**