Image Converter WASM
A Rust WebAssembly image conversion package for browser and Node.js runtimes, published as an npm package.
Repository Links
GitHub: https://github.com/walujanle/image-converter-wasm
GitLab: https://gitlab.com/walujanle/image-converter-wasm
Package Link
npm: https://www.npmjs.com/package/@walujanle/image-converter-wasm
Download Links
Package: https://links.leonardwalujan.eu.org/lw/image-converter-wasm-latest-package
Package Checksum (SHA-256): https://links.leonardwalujan.eu.org/lw/image-converter-wasm-latest-package-checksum
Source Code: https://links.leonardwalujan.eu.org/lw/image-converter-wasm-latest-source-code
Short Explanation
Image Converter WASM is a Rust-based image conversion engine compiled to WebAssembly and packaged for JavaScript applications. It is published as the npm package @walujanle/image-converter-wasm.
The package accepts image bytes as Uint8Array, converts them in memory, and returns encoded output bytes as Uint8Array. It does not choose files, create download buttons, upload files, or write to an output folder. Those parts stay in the host application, which makes the package suitable for frontend apps, browser workers, and Node.js workflows that need direct control over their own file handling.
The current package supports JPEG, PNG, WebP, and AVIF as output formats. It can read jpg, jpeg, png, webp, avif, heic, and heif inputs. HEIC/HEIF support is decode-only; the package can convert from HEIC/HEIF, but it intentionally does not encode HEIC output.
Why I Built This Project
I built this project because I wanted to understand the practical path from Rust source code to WebAssembly, then from WebAssembly to a package that can be installed from npm and called from a frontend application.
This project was also a way to study image conversion at a lower level than a normal web UI. I wanted an engine where the conversion behavior could be customized directly: output format, quality, PNG compression, WebP lossless mode, resize, crop, aspect-ratio handling, metadata preservation, and progress reporting. The goal was not to build a generic online converter service. The goal was to build a reusable conversion package that a website can call while still letting the frontend decide how files are selected, previewed, downloaded, cached, or uploaded.
In short, this project became a practical bridge between three things I wanted to understand better: Rust image processing, WebAssembly packaging, and frontend integration through npm.
Tech Stack Used
- Rust for the conversion engine.
- WebAssembly through
wasm-bindgen. - TypeScript declaration files for the public JavaScript API.
- ESM and CommonJS wrapper entries.
- npm package distribution.
imagefor shared decoding paths.jpeg-encoderfor JPEG output.pngfor PNG output.zenwebpfor WebP output.raviffor AVIF output.heicfor HEIC/HEIF input decoding.fast_image_resizefor resize processing.
The package is licensed as AGPL-3.0-only. That license is important because the distributed WASM artifacts include AGPL/commercial-licensed dependencies used by the current encoder and decoder paths.
Package Model
The package is not built as one large WebAssembly file. The build script creates separate WASM modules for each output target:
- JPEG module
- PNG module
- WebP module
- AVIF module
The JavaScript wrapper lazy-loads the module that matches the requested output format. This keeps the public API simple while avoiding one monolithic binary for every conversion path.
The generated npm package contains both module styles:
index.jsfor ESM imports.index.cjsfor CommonJSrequire.index.d.tsfor TypeScript users.formats/<format>/esm/index_bg.wasmfor browser and bundler ESM usage.formats/<format>/cjs/index_bg.wasmfor CommonJS usage.
In Node.js ESM, the wrapper reads the packaged WASM bytes from disk. In browser builds, the wrapper resolves the generated WASM asset URL unless the host application passes explicit wasmSources.
Public API
The public JavaScript API is intentionally small.
init(options?)
Initializes wrapper configuration and can preload selected output modules. It also supports custom WASM sources when a framework needs the WASM files to be served from a public or static directory.
convertImage(fileBytes, ext, options)
Converts one input buffer and returns output bytes.
convertImageWithInfo(fileBytes, ext, options, onProgress?)
Converts one input buffer and returns output bytes plus final width and height. The progress callback reports stage-based progress from 0 to 1.
getImageDimensions(fileBytes, ext, format?)
Reads image dimensions without running a full conversion.
extractMetadata(fileBytes, ext, format?)
Extracts available EXIF, XMP, IPTC, and ICC metadata from an in-memory image buffer.
getProjectVersion(format?)
Returns the project version embedded into the generated WASM module.
Conversion Options
The frontend-facing options cover the parts that are useful for a browser or Node.js caller:
format: target output format, one ofJpeg,Png,WebP, orAvif.quality: JPEG, lossy WebP, and AVIF quality input.pngCompressed: higher PNG compression mode.lossless: WebP lossless mode.resize: enables resize processing.targetWidthandtargetHeight: target dimensions.resizeLockAspectRatio: keeps the original aspect ratio when resizing.crop: enables percent-based crop.cropTop,cropBottom,cropLeft, andcropRight: crop percentages.keepMetadata: asks the encoder to preserve supported metadata.
Some conversion state fields still exist internally because the Rust structure shares shape with broader conversion logic, but the npm wrapper exposes only the byte-in, byte-out conversion features that make sense for a WebAssembly package.
How It Works
The conversion flow starts in the JavaScript wrapper. The wrapper normalizes the requested output format, normalizes the input extension, maps JavaScript option names into the Rust JSON shape, and loads the correct WASM module.
Inside Rust, the conversion pipeline validates the file size, extension, and magic bytes before decoding. The hard limits currently include a 256 MB max file size, 16384 pixels per image side, and a 300 second conversion deadline.
After validation, the source image is decoded into memory. JPEG, PNG, WebP, and AVIF use the normal image decoding path. HEIC/HEIF input uses the pure Rust HEIC decoder when that feature is enabled.
For non-HEIC inputs, EXIF orientation can be applied so rotated camera images are corrected before further processing. HEIC/HEIF decode output is treated as already orientation-corrected, so the pipeline avoids applying the same orientation twice.
If metadata preservation is requested, the pipeline extracts metadata before transformation. ICC color profile extraction is also handled separately so color profile data can be carried when the output path supports it.
The engine then applies optional percent crop, optional resize, and format-specific encoding. The output is written into memory and returned to JavaScript. There is no output file path at this layer.
Metadata Behavior
Metadata extraction and metadata preservation are separate behaviors. extractMetadata(...) reads metadata from the source bytes. keepMetadata: true asks the conversion pipeline to write supported metadata into the output.
Current output preservation support:
| Output Format | EXIF | XMP | IPTC | ICC |
|---|---|---|---|---|
| JPEG | Yes | Yes | Yes | Yes |
| PNG | Yes | Yes | Yes | Yes |
| WebP | Yes | Yes | No | Yes |
| AVIF | Yes | Yes | No | No |
JPEG writes EXIF and XMP through APP1 segments, IPTC through Photoshop APP13, and ICC through APP2. PNG writes metadata through PNG chunks and also projects selected EXIF/XMP fields into text chunks for better operating-system visibility. WebP writes EXIF, XMP, and ICC chunks in the RIFF container. AVIF preserves EXIF and XMP, while ICC embedding is not promised by the current AVIF output path.
For AVIF and HEIC input, metadata is read from ISOBMFF structures when the parser can find it. The ISOBMFF path handles item metadata for EXIF and XMP and can extract ICC from colr boxes with prof or rICC profile types.
Example Usage
import { convertImage, init } from "@walujanle/image-converter-wasm";
await init({ preload: ["Jpeg"] });
const input = new Uint8Array(await file.arrayBuffer());
const output = await convertImage(input, ".png", {
format: "Jpeg",
quality: 82,
resize: true,
targetWidth: 1600,
resizeLockAspectRatio: true,
keepMetadata: true,
});
const blob = new Blob([output], { type: "image/jpeg" });
For browser applications, this code should run in a client-side path such as a client component, browser-only hook, or web worker. Server-side rendering code should not assume browser file APIs are available.
Build and Release Notes
The repository build script is build_wasm.bat. It compiles one output module at a time, runs wasm-bindgen, assembles the npm package under pkg/, and copies the npm README from js-wrapper/README.npm.md.
The repository deliberately keeps two README files:
README.mdfor maintainers reading the source repository.js-wrapper/README.npm.mdfor npm package users.
This separation matters because maintainers need build and architecture details, while package users mostly need installation, API, runtime, and bundler guidance.
There is also an optional WASM_THREADS=1 build path. That path is only useful when the host environment is ready for threaded WebAssembly requirements such as SharedArrayBuffer, COOP, and COEP.
Limitations
- HEIC/HEIF is decode-only. HEIC output is not supported.
- TIFF is not a public input or output format in this package.
- The conversion model is in-memory, so very large files can still require significant memory.
- Browser file selection, saving, previews, and downloads must be implemented by the host application.
- Progress is stage-based, not codec-internal byte or scanline progress.
- AVIF output does not currently promise ICC embedding.
- WebP and AVIF output do not preserve IPTC because those output paths do not provide native IPTC handling in this package.
- Threaded WASM builds require browser headers and runtime support from the host application.
License
Image Converter WASM is released under AGPL-3.0-only.
Users can study, modify, and redistribute the project under the AGPL terms. Applications distributing or providing network access to this package need to follow the license obligations or use a separate compatible licensing path.
Latest Projects
Image Converter Web App
A browser-native local-first image converter built with React, TypeScript, Web Workers, and a Rust/WebAssembly npm engine.
Image Converter Windows App
A local-first Windows image converter built with Rust for private, offline batch conversion.
Test
Test