@fe-daily/libimagequant-wasm
Version:
WASM bindings for libimagequant image quantization library
1 lines • 9.03 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../src/index.ts"],"sourcesContent":["/**\n * libimagequant WASM - Promise-based API for PNG image quantization in browsers\n *\n * This module provides a high-level, promise-based interface for the libimagequant\n * image quantization library using WASM directly.\n */\n\nexport interface QuantizationOptions {\n /** Speed vs quality trade-off (1-10, lower = better quality) */\n speed?: number;\n /** Quality settings */\n quality?: {\n min: number;\n target: number;\n };\n /** Maximum colors in palette (2-256) */\n maxColors?: number;\n /** Dithering level (0.0-1.0) */\n dithering?: number;\n /** Posterization level (0-4) */\n posterization?: number;\n}\n\nexport interface QuantizationResult {\n /** Color palette array */\n palette: number[][];\n /** PNG bytes (Uint8Array) - quantized image as indexed PNG */\n pngBytes: Uint8Array;\n /** ImageData object - quantized image as RGBA data */\n imageData: ImageData;\n /** Achieved quality (0-1) */\n quality: number;\n /** Number of colors in palette */\n paletteLength: number;\n /** Image width */\n width: number;\n /** Image height */\n height: number;\n}\n\nexport interface LibImageQuantOptions {\n /** Custom path to WASM module directory (should contain libimagequant_wasm.js) */\n wasmUrl?: string;\n /** Pre-loaded WASM module (useful for Next.js and other bundlers) */\n wasmModule?: any;\n}\n\nexport default class LibImageQuant {\n private wasmModule: any = null;\n private isInitialized: boolean = false;\n private wasmUrl?: string;\n private preloadedWasmModule?: any;\n private initPromise: Promise<void>;\n\n constructor(options: LibImageQuantOptions = {}) {\n this.wasmUrl = options.wasmUrl;\n this.preloadedWasmModule = options.wasmModule;\n this.initPromise = this.initializeWasm();\n }\n\n /**\n * Initialize the WASM module\n */\n private async initializeWasm(): Promise<void> {\n if (this.isInitialized) return;\n\n // Use pre-loaded WASM module if provided (useful for Next.js)\n if (this.preloadedWasmModule) {\n this.wasmModule = this.preloadedWasmModule;\n await this.wasmModule.default(this.wasmUrl);\n this.isInitialized = true;\n return;\n }\n\n // Try to load WASM module dynamically\n try {\n const wasmLoaderPath = this.wasmUrl || new URL(\"./wasm/libimagequant_wasm.js\", import.meta.url).href;\n this.wasmModule = await import(wasmLoaderPath);\n await this.wasmModule.default(this.wasmUrl);\n this.isInitialized = true;\n } catch (error) {\n throw new Error(\n `Failed to load WASM module. For Next.js applications, please provide the WASM module directly: \\n` +\n `import wasmModule from '@fe-daily/libimagequant-wasm/wasm/libimagequant_wasm.js';\\n` +\n `const quantizer = new LibImageQuant({ wasmModule });\\n` +\n `Original error: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Quantize a PNG from bytes or Blob\n */\n async quantizePng(\n pngData: Uint8Array | ArrayBuffer | Blob,\n options: QuantizationOptions = {}\n ): Promise<QuantizationResult> {\n await this.initPromise;\n\n let pngBytes: Uint8Array;\n\n if (pngData instanceof Blob) {\n const arrayBuffer = await pngData.arrayBuffer();\n pngBytes = new Uint8Array(arrayBuffer);\n } else if (pngData instanceof ArrayBuffer) {\n pngBytes = new Uint8Array(pngData);\n } else {\n pngBytes = pngData;\n }\n\n // Decode PNG to RGBA\n const decodedResult = this.wasmModule.decode_png_to_rgba(pngBytes);\n const rgbaData = decodedResult[0]; // Uint8ClampedArray\n const width = decodedResult[1];\n const height = decodedResult[2];\n\n return await this.quantizeRgbaData(rgbaData, width, height, options);\n }\n\n /**\n * Quantize from ImageData and return as PNG bytes or ImageData\n */\n async quantizeImageData(\n imageData: ImageData,\n options: QuantizationOptions = {}\n ): Promise<QuantizationResult> {\n await this.initPromise;\n\n // Convert to Uint8ClampedArray\n const rgbaData = new Uint8ClampedArray(imageData.data);\n\n return await this.quantizeRgbaData(rgbaData, imageData.width, imageData.height, options);\n }\n\n /**\n * Internal method to quantize RGBA data\n */\n private async quantizeRgbaData(\n rgbaData: Uint8ClampedArray,\n width: number,\n height: number,\n options: QuantizationOptions\n ): Promise<QuantizationResult> {\n // Create quantizer instance\n const quantizer = new this.wasmModule.ImageQuantizer();\n\n // Apply options\n if (options.speed !== undefined) {\n quantizer.setSpeed(options.speed);\n }\n\n if (options.quality !== undefined) {\n const { min = 0, target = 100 } = options.quality;\n quantizer.setQuality(min, target);\n }\n\n if (options.maxColors !== undefined) {\n quantizer.setMaxColors(options.maxColors);\n }\n\n if (options.posterization !== undefined) {\n quantizer.setPosterization(options.posterization);\n }\n\n // Quantize the image\n const quantResult = quantizer.quantizeImage(rgbaData, width, height);\n\n // Extract results\n const palette = quantResult.getPalette();\n const quality = quantResult.getQuantizationQuality();\n const paletteLength = quantResult.getPaletteLength();\n\n // Set dithering if specified\n if (options.dithering !== undefined) {\n quantResult.setDithering(options.dithering);\n }\n\n const remappedRgbaData = quantResult.remapImage(rgbaData, width, height);\n\n // Get palette indices directly from Rust\n const paletteIndices = quantResult.getPaletteIndices(rgbaData, width, height);\n\n // Generate indexed PNG\n const pngBytes = this.wasmModule.encode_palette_to_png(\n paletteIndices,\n palette,\n width,\n height\n );\n\n // Generate ImageData (handle environments where ImageData is not available)\n let outputImageData: ImageData;\n try {\n outputImageData = new ImageData(remappedRgbaData, width, height);\n } catch (error) {\n // Fallback for Node.js or other environments without ImageData\n outputImageData = {\n data: remappedRgbaData,\n width,\n height,\n colorSpace: 'srgb'\n } as ImageData;\n }\n\n return {\n palette,\n pngBytes,\n imageData: outputImageData,\n quality,\n paletteLength,\n width,\n height,\n };\n }\n\n /**\n * Clean up resources (no longer needed since we're not using workers)\n */\n dispose() {\n // No cleanup needed for direct WASM usage\n }\n}\n"],"names":[],"mappings":"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"}