From 2eb887dce71a534471bfce09998a494afb0c98d4 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Sat, 2 Aug 2025 17:17:52 +0000 Subject: [PATCH] feat(core): add quality parameter for image compression control --- changelog.md | 14 +++++++++ package.json | 2 +- readme.md | 46 +++++++++++++++++++++++++--- ts/smartjimp.classes.smartjimp.ts | 51 ++++++++++++++++++++++--------- 4 files changed, 94 insertions(+), 19 deletions(-) diff --git a/changelog.md b/changelog.md index 1e070be..86fc6e2 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,19 @@ # Changelog +## [1.2.0] - 2025-01-02 + +### Added +- Quality parameter support for image compression +- New `quality` option (1-100) in IAssetVariation interface for controlling compression quality +- Quality control implementation for both Sharp and Jimp modes +- Support for quality settings on all lossy formats (JPEG, WebP, AVIF) +- Enhanced documentation with quality control examples + +### Changed +- Jimp mode now uses fall-through pattern in format switch for cleaner code +- Console logging when Jimp falls back to JPEG for unsupported formats (WebP, AVIF) +- Updated examples in documentation to showcase quality parameter usage + ## [1.1.0] - 2025-01-02 ### Added diff --git a/package.json b/package.json index 8a76de4..32040f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@push.rocks/smartjimp", - "version": "1.1.0", + "version": "1.2.0", "private": false, "description": "A TypeScript library for image processing combining both sharp and jimp libraries.", "main": "dist_ts/index.js", diff --git a/readme.md b/readme.md index b802a66..617f015 100644 --- a/readme.md +++ b/readme.md @@ -93,6 +93,38 @@ const avifBuffer = await processor.getFromSmartfile(imageFile, { }); ``` +### 🎚️ Quality Control +Fine-tune image compression quality for the perfect balance between file size and visual fidelity: + +```typescript +// High quality JPEG (larger file size) +const highQualityJpeg = await processor.getFromSmartfile(imageFile, { + format: 'jpeg', + quality: 90 // 1-100, higher = better quality +}); + +// Optimized for web (smaller file size) +const webOptimized = await processor.getFromSmartfile(imageFile, { + format: 'jpeg', + quality: 75, // Good balance for web + progressive: true +}); + +// Ultra-compressed WebP +const tinyWebP = await processor.getFromSmartfile(imageFile, { + format: 'webp', + quality: 60 // WebP handles lower quality better than JPEG +}); + +// Quality works with all lossy formats +const qualityAvif = await processor.getFromSmartfile(imageFile, { + format: 'avif', + quality: 80 // AVIF provides excellent quality even at lower values +}); +``` + +> 💡 **Pro tip**: Different formats handle quality settings differently. AVIF and WebP generally look better than JPEG at the same quality level. + ### 📸 Progressive JPEG Support Create progressive JPEGs that load in multiple passes for better perceived performance: @@ -138,13 +170,15 @@ Work directly with buffers for maximum flexibility: const processedBuffer = await processor.computeAssetVariation(imageBuffer, { width: 300, format: 'webp', + quality: 85, invert: true }); // Create progressive JPEG from buffer const progressiveBuffer = await processor.computeAssetVariation(imageBuffer, { format: 'jpeg', - progressive: true + progressive: true, + quality: 88 }); // Special AVIF creation method (sharp mode only) @@ -251,20 +285,23 @@ async function optimizeForWeb(sourceImage: SmartFile) { // Modern browsers: AVIF avif: await processor.getFromSmartfile(sourceImage, { format: 'avif', - width: 1200 + width: 1200, + quality: 85 // AVIF excels at lower quality settings }), // Good browser support: WebP webp: await processor.getFromSmartfile(sourceImage, { format: 'webp', - width: 1200 + width: 1200, + quality: 82 // WebP sweet spot for quality/size }), // Universal fallback: Progressive JPEG jpeg: await processor.getFromSmartfile(sourceImage, { format: 'jpeg', progressive: true, - width: 1200 + width: 1200, + quality: 80 // Standard web quality }) }; @@ -289,6 +326,7 @@ interface IAssetVariation { height?: number; // Target height (maintains aspect ratio if width not set) invert?: boolean; // Invert colors (jimp only currently) progressive?: boolean; // Create progressive JPEG (sharp only, jpeg format only) + quality?: number; // Compression quality 1-100 (lossy formats only) } ``` diff --git a/ts/smartjimp.classes.smartjimp.ts b/ts/smartjimp.classes.smartjimp.ts index 42dc0bb..ccdcb9c 100644 --- a/ts/smartjimp.classes.smartjimp.ts +++ b/ts/smartjimp.classes.smartjimp.ts @@ -7,6 +7,7 @@ export interface IAssetVariation { height?: number; invert?: boolean; progressive?: boolean; + quality?: number; } export interface ISmartJimpOptions { @@ -72,24 +73,44 @@ export class SmartJimp { } switch (assetVariationArg.format) { case 'avif': - sharpImage = resultResize.avif(); + const avifOptions: any = {}; + if (assetVariationArg.quality !== undefined) { + avifOptions.quality = assetVariationArg.quality; + } + sharpImage = resultResize.avif(avifOptions); break; case 'webp': - sharpImage = resultResize.webp(); + const webpOptions: any = {}; + if (assetVariationArg.quality !== undefined) { + webpOptions.quality = assetVariationArg.quality; + } + sharpImage = resultResize.webp(webpOptions); break; case 'png': - sharpImage = resultResize.png(); + const pngOptions: any = {}; + if (assetVariationArg.quality !== undefined) { + pngOptions.quality = assetVariationArg.quality; + } + sharpImage = resultResize.png(pngOptions); break; case 'jpeg': - sharpImage = resultResize.jpeg({ + const jpegOptions: any = { progressive: assetVariationArg.progressive || false - }); + }; + if (assetVariationArg.quality !== undefined) { + jpegOptions.quality = assetVariationArg.quality; + } + sharpImage = resultResize.jpeg(jpegOptions); break; default: // Default to JPEG - sharpImage = resultResize.jpeg({ + const defaultJpegOptions: any = { progressive: assetVariationArg.progressive || false - }); + }; + if (assetVariationArg.quality !== undefined) { + defaultJpegOptions.quality = assetVariationArg.quality; + } + sharpImage = resultResize.jpeg(defaultJpegOptions); } return sharpImage.toBuffer(); } else if (this.options.mode === 'jimp') { @@ -106,20 +127,22 @@ export class SmartJimp { } // Note: Jimp does not support progressive JPEG encoding // Progressive option is ignored in jimp mode + const jpegOptions: any = {}; + if (assetVariationArg.quality !== undefined) { + jpegOptions.quality = assetVariationArg.quality; + } + switch (assetVariationArg.format) { case 'png': return await jimpImage.getBuffer("image/png"); - case 'jpeg': - return await jimpImage.getBuffer("image/jpeg"); case 'webp': - // Jimp doesn't support WebP, fallback to JPEG - return await jimpImage.getBuffer("image/jpeg"); case 'avif': - // Jimp doesn't support AVIF, fallback to JPEG - return await jimpImage.getBuffer("image/jpeg"); + console.log(`Jimp doesn't support ${assetVariationArg.format}, falling back to JPEG`); + // Fall through to JPEG + case 'jpeg': default: // Default to JPEG - return await jimpImage.getBuffer("image/jpeg"); + return await jimpImage.getBuffer("image/jpeg", jpegOptions); } } }