High-performance Rust + WebAssembly image processing for modern web applications.
Photeryx is a fast, lightweight image processing pipeline powered by Rust + WebAssembly, designed for browsers and modern frontend apps. It supports loading multiple images, transforming them, and exporting them in various formats, all locally without backend services.
This makes it ideal for:
- Image editors
- Upload preprocessors
- Offline-first web apps
- High-performance React / Vue / Svelte applications
- Written in Rust, compiled to WebAssembly
- Manage multiple images in memory at once
- Load images from:
FileURLArrayBuffer
- Transformations:
- Rotate
- Crop
- Resize
- Filters (brightness, contrast, blur, sharpen, etc.)
- Export formats:
JPEGPNGWebP
- Export as:
Uint8ArrayBlob- Base64
data:URL
- Duplicate detection across loaded images (returns
Photo[][]) - Manual memory control: free images when youβre done
- Zero network dependency
npm install photeryxexport interface RotationConfig {
degrees: number;
}
export interface CropConfig {
x: number;
y: number;
width: number;
height: number;
}
export interface ResizeConfig {
max_width: number;
max_height: number;
mode: "fit" | "exact" | "fill";
}
export interface SharpenConfig {
radius: number;
threshold: number;
}
export interface FilterConfig {
grayscale?: boolean;
invert?: boolean;
sharpen?: SharpenConfig | null;
brightness?: number | null;
contrast?: number | null;
blur?: number | null;
}
export type ExportConfig =
| {
format: "jpeg";
quality: number;
}
| {
format: "png";
}
| {
format: "webp";
};
export interface ImageConfig {
rotation?: RotationConfig | null;
crop?: CropConfig | null;
resize?: ResizeConfig | null;
filters?: FilterConfig | null;
export: ExportConfig;
}
export declare class Photo {
#private;
constructor(manager: Photeryx, id: number);
get id(): number;
exportAsBytes(config: ImageConfig): Promise<Uint8Array>;
exportAsBlob(config: ImageConfig): Promise<Blob>;
exportAsDataUrl(config: ImageConfig): Promise<string>;
free(): void;
_unsafeFreeWithoutDetach(): void;
}
export declare class Photeryx {
#private;
get photos(): readonly Photo[];
addFromFile(file: File): Promise<Photo>;
addFromUrl(url: string): Promise<Photo>;
addFromArrayBuffer(buffer: ArrayBuffer): Promise<Photo>;
exportAllAsBytes(config: ImageConfig): Promise<Uint8Array[]>;
exportAllAsBlobs(config: ImageConfig): Promise<Blob[]>;
exportAllAsDataUrls(config: ImageConfig): Promise<string[]>;
findDuplicates(threshold?: number): Promise<Photo[][]>;
freeAll(): void;
_detach(photo: Photo): void;
}
export default Photeryx;import Photeryx, { type ImageConfig } from "photeryx";
const ph = new Photeryx();const photo1 = await ph.addFromFile(fileInput.files[0]);
const photo2 = await ph.addFromUrl("https://example.com/image.jpg");
// Or from ArrayBuffer
const buffer = await someFetchOrFileApi();
const photo3 = await ph.addFromArrayBuffer(buffer);const config: ImageConfig = {
rotation: { degrees: 90 },
crop: { x: 0, y: 0, width: 800, height: 600 },
resize: { max_width: 1200, max_height: 1200, mode: "fit" },
filters: {
grayscale: false,
sharpen: { radius: 2, threshold: 1 },
brightness: 10,
contrast: 20,
blur: 1,
},
export: { format: "jpeg", quality: 85 },
};// Uint8Array
const bytes = await photo1.exportAsBytes(config);
// Blob
const blob = await photo1.exportAsBlob(config);
// Base64 string (data URL)
const base64 = await photo1.exportAsDataUrl(config);
// If you need a File instance:
const file = new File([blob], "output.jpeg", { type: "image/jpeg" });// As Uint8Array[]
const allBytes = await ph.exportAllAsBytes(config);
// As Blob[]
const allBlobs = await ph.exportAllAsBlobs(config);
// As data URLs
const allDataUrls = await ph.exportAllAsDataUrls(config);findDuplicates compares all loaded photos and returns groups of Photo instances that are considered duplicates or very similar.
// Optionally pass a threshold (implementation-defined, e.g. 0β100)
const groups = await ph.findDuplicates(90);
// Example shape:
// [
// [Photo, Photo], // first duplicate group
// [Photo, Photo, Photo] // second duplicate group
// ]
for (const group of groups) {
console.log("Duplicate group:");
for (const photo of group) {
console.log(" Photo id:", photo.id);
}
}You can still access the flat list of currently loaded photos through ph.photos.
Photeryx gives you full control over WebAssembly memory:
photo1.free(); // Free one image
ph.freeAll(); // Free all images.free() or .freeAll(), freed objects can no longer be used.
| Feature | Support |
|---|---|
| WebAssembly | Required |
| ES6 Modules | Required |
| Offscreen Canvas (optional) | Optional |
| Member / Method | Description |
|---|---|
photos: readonly Photo[] |
Readonly list of currently loaded photos |
addFromFile(file) |
Load an image from a File |
addFromUrl(url) |
Fetch and load image from a URL |
addFromArrayBuffer(buffer) |
Load image from raw ArrayBuffer |
exportAllAsBytes(config) |
Export all loaded images as Uint8Array[] |
exportAllAsBlobs(config) |
Export all loaded images as Blob[] |
exportAllAsDataUrls(config) |
Export all loaded images as Base64 data URLs (string[]) |
findDuplicates(threshold?) |
Find duplicate/similar images, returns groups of Photo[][] |
freeAll() |
Free all images from WebAssembly memory |
| Member / Method | Description |
|---|---|
id: number |
Stable numeric ID for this photo |
exportAsBytes(config) |
Export as Uint8Array |
exportAsBlob(config) |
Export as Blob |
exportAsDataUrl(config) |
Export as Base64 data URL string |
free() |
Free memory of this image |
Apache-2.0 Β© Mehran Taslimi