diff --git a/packages/media-utils/README.md b/packages/media-utils/README.md index 8f3a261..02f7c3a 100644 --- a/packages/media-utils/README.md +++ b/packages/media-utils/README.md @@ -16,7 +16,7 @@ A utility package for handling media files, specifically designed for processing npm install @agent-infra/media-utils ``` -## Base64ImageTool +## Base64ImageParser > [!NOTE] > Currently only supports parsing **static** base64 image formats: PNG, JPEG, WebP, GIF, and BMP @@ -28,10 +28,10 @@ Supports both Node.js and browsers. ### Basic Usage ```typescript -import { Base64ImageTool } from '@agent-infra/media-utils'; +import { Base64ImageParser } from '@agent-infra/media-utils/base64'; // Initialize with a base64 image string -const tool = new Base64ImageTool('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...'); +const tool = new Base64ImageParser('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...'); // Get image type const imageType = tool.getImageType(); // 'png' | 'jpeg' | 'webp' | 'gif' | 'bmp' | null @@ -52,17 +52,57 @@ const dataUri = tool.getDataUri(); // 'data:image/png;base64,...' ### Example ```typescript -import { Base64ImageTool } from '@agent-infra/media-utils'; +import { Base64ImageParser } from '@agent-infra/media-utils/base64'; const base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='; -const tool = new Base64ImageTool(base64Image); +const tool = new Base64ImageParser(base64Image); console.log('Image type:', tool.getImageType()); // 'png' console.log('Dimensions:', tool.getDimensions()); // { width: 1, height: 1 } console.log('Pure base64:', tool.getPureBase64Image()); // 'iVBORw0KGgo...' ``` +## ImageCompressor + +A high-performance image compression utility that supports multiple formats and quality settings. + +### Platform Compatibility + +Node.js only. + +### Basic Usage + +```typescript +import { ImageCompressor } from '@agent-infra/media-utils/compressor'; + +// Create compressor with default options (WebP format, 80% quality) +const compressor = new ImageCompressor(); + +// Or with custom options +const customCompressor = new ImageCompressor({ + quality: 90, + format: 'jpeg' +}); + +// Compress image buffer +const imageBuffer = new Uint8Array(/* your image data */); +const compressedBuffer = await compressor.compressToBuffer(imageBuffer); +const compressedBase64 = await compressor.compressToBase64(imageBuffer); +``` + + +### Configuration Options + +```typescript +interface ImageCompressionOptions { + quality?: number; // 1-100, default: 80 + format?: 'jpeg' | 'png' | 'webp'; // default: 'webp' + width?: number; // optional width constraint + height?: number; // optional height constraint +} +``` + ## License ISC diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json index 9de2717..ae1e820 100644 --- a/packages/media-utils/package.json +++ b/packages/media-utils/package.json @@ -2,6 +2,7 @@ "name": "@agent-infra/media-utils", "description": "media-utils", "version": "0.0.1", + "sideEffects": false, "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", @@ -10,6 +11,16 @@ "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.js" + }, + "./base64": { + "types": "./dist/index.d.ts", + "import": "./dist/base64/index.mjs", + "require": "./dist/base64/index.js" + }, + "./compressor": { + "types": "./dist/index.d.ts", + "import": "./dist/compressor.mjs", + "require": "./dist/compressor.js" } }, "files": [ @@ -26,10 +37,17 @@ }, "dependencies": { "@agent-infra/logger": "0.0.2-beta.2", - "delay": "6.0.0" + "delay": "6.0.0", + "imagemin": "9.0.1", + "imagemin-mozjpeg": "10.0.0", + "imagemin-pngquant": "10.0.0", + "imagemin-webp": "8.0.0" }, "devDependencies": { "@types/node": "24.1.0", + "@types/imagemin": "9.0.1", + "@types/imagemin-mozjpeg": "8.0.4", + "@types/imagemin-webp": "7.0.3", "typescript": "5.8.3", "vitest": "3.2.4", "@vitest/coverage-v8": "3.2.4", diff --git a/packages/media-utils/src/base64/index.ts b/packages/media-utils/src/base64/index.ts index b3437c7..b415e5b 100644 --- a/packages/media-utils/src/base64/index.ts +++ b/packages/media-utils/src/base64/index.ts @@ -14,7 +14,7 @@ import { import type { ImageDimensions, ImageType } from '../type'; -export class Base64ImageTool { +export class Base64ImageParser { private pureBase64: string; private buffer?: Uint8Array; @@ -68,7 +68,7 @@ export class Base64ImageTool { try { switch (imageType) { case 'png': { - const bytes = this.getHeaderBuffer(32); // 16-23 + const bytes = this.getHeaderBuffer(24); // 16-23 this.dimensions = parsePngDimensions(bytes); break; } @@ -93,7 +93,7 @@ export class Base64ImageTool { break; } case 'bmp': { - const bytes = this.getHeaderBuffer(40); // 18-25 + const bytes = this.getHeaderBuffer(32); // 18-25 this.dimensions = parseBmpDimensions(bytes); break; } diff --git a/packages/media-utils/src/compressor.ts b/packages/media-utils/src/compressor.ts new file mode 100644 index 0000000..f63f554 --- /dev/null +++ b/packages/media-utils/src/compressor.ts @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2025 Bytedance, Inc. and its affiliates. + * SPDX-License-Identifier: Apache-2.0 + */ +import imagemin from 'imagemin'; +import imageminPngquant from 'imagemin-pngquant'; +import imageminMozjpeg from 'imagemin-mozjpeg'; +import imageminWebp from 'imagemin-webp'; + +import { ImageCompressionOptions } from './type'; + +/** + * High-performance image compression utility class + */ +export class ImageCompressor { + public readonly options: ImageCompressionOptions; + + constructor(options?: ImageCompressionOptions) { + // Set default options + this.options = { + quality: options?.quality ?? 80, + format: options?.format ?? 'webp', + width: options?.width, + height: options?.height, + }; + } + + /** + * Compress image and return Buffer without writing to file + * @param imageBuffer Image Buffer + */ + async compressToBuffer(imageBuffer: Buffer) { + // Choose appropriate compression plugin + const plugins = this.getPluginsForFormat(); + + // Compress image + return await imagemin.buffer(imageBuffer, { + plugins, + }); + } + + async compressToBase64(imageBuffer: Buffer) { + // Compress image to Buffer + const compressedBuffer = await this.compressToBuffer(imageBuffer); + + // Convert to Base64 + return Buffer.from(compressedBuffer).toString('base64'); + } + + /** + * Select plugins based on target format + */ + private getPluginsForFormat() { + const quality = this.options.quality / 100; // Convert to 0-1 range (required by some plugins) + + switch (this.options.format) { + case 'jpeg': + return [imageminMozjpeg({ quality: this.options.quality })]; + case 'png': + return [ + imageminPngquant({ + quality: [quality, Math.min(quality + 0.2, 1)], + }), + ]; + case 'webp': + return [imageminWebp({ quality: this.options.quality })]; + default: + return [imageminWebp({ quality: this.options.quality })]; + } + } + + /** + * Get formatted description of current compression options + */ + getOptionsDescription(): string { + return `Quality: ${this.options.quality}, Format: ${this.options.format}${ + this.options.width ? `, Width: ${this.options.width}px` : '' + }${this.options.height ? `, Height: ${this.options.height}px` : ''}`; + } +} diff --git a/packages/media-utils/src/index.ts b/packages/media-utils/src/index.ts index 146a2e8..d520fa4 100644 --- a/packages/media-utils/src/index.ts +++ b/packages/media-utils/src/index.ts @@ -3,4 +3,5 @@ * SPDX-License-Identifier: Apache-2.0 */ export * from './base64'; +export * from './compressor'; export * from './type'; diff --git a/packages/media-utils/src/type.ts b/packages/media-utils/src/type.ts index 3a46336..e8d360d 100644 --- a/packages/media-utils/src/type.ts +++ b/packages/media-utils/src/type.ts @@ -8,3 +8,18 @@ export interface ImageDimensions { width: number; height: number; } + +export interface ImageCompressionOptions { + quality: number; // Compression quality (1-100) + format?: 'jpeg' | 'png' | 'webp'; + width?: number; // Optional target width + height?: number; // Optional target height +} + +export interface CompressionResult { + originalSize: number; + compressedSize: number; + compressionRatio: number; + path: string; + buffer: Buffer; +} diff --git a/packages/media-utils/test/base64.test.html b/packages/media-utils/test/base64.test.html index 6f013a5..673a14f 100644 --- a/packages/media-utils/test/base64.test.html +++ b/packages/media-utils/test/base64.test.html @@ -3,10 +3,10 @@ - Base64ImageTool Test + Base64ImageParser Test -

Base64ImageTool Test (Open in firefox to ignore CORS)

+

Base64ImageParser Test (Open in firefox to ignore CORS)

Original Image

Base64 Image @@ -18,16 +18,16 @@

Tool Results

Pure Base64: