UNPKG

@fe-daily/libimagequant-wasm

Version:

WASM bindings for libimagequant image quantization library

1 lines 9.66 kB
{"version":3,"file":"index.cjs","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":["constructor","options","this","wasmModule","isInitialized","wasmUrl","preloadedWasmModule","initPromise","initializeWasm","default","wasmLoaderPath","URL","document","require","pathToFileURL","__filename","href","_documentCurrentScript","tagName","toUpperCase","src","baseURI","import","error","Error","message","String","quantizePng","pngData","pngBytes","Blob","arrayBuffer","Uint8Array","ArrayBuffer","decodedResult","decode_png_to_rgba","rgbaData","width","height","quantizeRgbaData","quantizeImageData","imageData","Uint8ClampedArray","data","quantizer","ImageQuantizer","speed","setSpeed","quality","min","target","setQuality","maxColors","setMaxColors","posterization","setPosterization","quantResult","quantizeImage","palette","getPalette","getQuantizationQuality","paletteLength","getPaletteLength","dithering","setDithering","remappedRgbaData","remapImage","paletteIndices","getPaletteIndices","encode_palette_to_png","outputImageData","ImageData","colorSpace","dispose"],"mappings":"2FA+CA,MAOE,WAAAA,CAAYC,EAAgC,IAN5CC,KAAQC,WAAkB,KAC1BD,KAAQE,eAAyB,EAM/BF,KAAKG,QAAUJ,EAAQI,QACvBH,KAAKI,oBAAsBL,EAAQE,WACnCD,KAAKK,YAAcL,KAAKM,gBAC1B,CAKA,oBAAcA,GACZ,IAAIN,KAAKE,cAAT,CAGA,GAAIF,KAAKI,oBAIP,OAHAJ,KAAKC,WAAaD,KAAKI,0BACjBJ,KAAKC,WAAWM,QAAQP,KAAKG,cACnCH,KAAKE,eAAgB,GAKvB,IACE,MAAMM,EAAiBR,KAAKG,SAAW,IAAAM,IAAA,+BAAA,oBAAAC,SAAAC,QAAA,OAAAC,cAAAC,YAAAC,KAAAC,GAAA,WAAAA,EAAAC,QAAAC,eAAAF,EAAAG,KAAA,IAAAT,IAAA,YAAAC,SAAAS,SAAAL,MAAyDA,KAChGd,KAAKC,iBAAmBmB,OAAOZ,SACzBR,KAAKC,WAAWM,QAAQP,KAAKG,SACnCH,KAAKE,eAAgB,CACvB,OAASmB,GACP,MAAM,IAAIC,MACR,6PAGmBD,aAAiBC,MAAQD,EAAME,QAAUC,OAAOH,KAEvE,CAvBwB,CAwB1B,CAKA,iBAAMI,CACJC,EACA3B,EAA+B,IAI/B,IAAI4B,EAEJ,SAJM3B,KAAKK,YAIPqB,aAAmBE,KAAM,CAC3B,MAAMC,QAAoBH,EAAQG,cAClCF,EAAW,IAAIG,WAAWD,EAC5B,MACEF,EADSD,aAAmBK,YACjB,IAAID,WAAWJ,GAEfA,EAIb,MAAMM,EAAgBhC,KAAKC,WAAWgC,mBAAmBN,GACnDO,EAAWF,EAAc,GACzBG,EAAQH,EAAc,GACtBI,EAASJ,EAAc,GAE7B,aAAahC,KAAKqC,iBAAiBH,EAAUC,EAAOC,EAAQrC,EAC9D,CAKA,uBAAMuC,CACJC,EACAxC,EAA+B,UAEzBC,KAAKK,YAGX,MAAM6B,EAAW,IAAIM,kBAAkBD,EAAUE,MAEjD,aAAazC,KAAKqC,iBAAiBH,EAAUK,EAAUJ,MAAOI,EAAUH,OAAQrC,EAClF,CAKA,sBAAcsC,CACZH,EACAC,EACAC,EACArC,GAGA,MAAM2C,EAAY,IAAI1C,KAAKC,WAAW0C,eAOtC,QAJsB,IAAlB5C,EAAQ6C,OACVF,EAAUG,SAAS9C,EAAQ6C,YAGL,IAApB7C,EAAQ+C,QAAuB,CACjC,MAAMC,IAAEA,EAAM,EAAAC,OAAGA,EAAS,KAAQjD,EAAQ+C,QAC1CJ,EAAUO,WAAWF,EAAKC,EAC5B,MAE0B,IAAtBjD,EAAQmD,WACVR,EAAUS,aAAapD,EAAQmD,gBAGH,IAA1BnD,EAAQqD,eACVV,EAAUW,iBAAiBtD,EAAQqD,eAIrC,MAAME,EAAcZ,EAAUa,cAAcrB,EAAUC,EAAOC,GAGvDoB,EAAUF,EAAYG,aACtBX,EAAUQ,EAAYI,yBACtBC,EAAgBL,EAAYM,wBAGR,IAAtB7D,EAAQ8D,WACVP,EAAYQ,aAAa/D,EAAQ8D,WAGnC,MAAME,EAAmBT,EAAYU,WAAW9B,EAAUC,EAAOC,GAG3D6B,EAAiBX,EAAYY,kBAAkBhC,EAAUC,EAAOC,GAGhET,EAAW3B,KAAKC,WAAWkE,sBAC/BF,EACAT,EACArB,EACAC,GAIF,IAAIgC,EACJ,IACEA,EAAkB,IAAIC,UAAUN,EAAkB5B,EAAOC,EAC3D,OAASf,GAEP+C,EAAkB,CAChB3B,KAAMsB,EACN5B,QACAC,SACAkC,WAAY,OAEhB,CAEA,MAAO,CACLd,UACA7B,WACAY,UAAW6B,EACXtB,UACAa,gBACAxB,QACAC,SAEJ,CAKA,OAAAmC,GAEA"}