Export Engine
Relevant source files
The following files were used as context for generating this wiki page:
The Export Engine in Dotorixel is responsible for converting internal canvas pixel data into standard file formats for external use. It serves as the bridge between the editor's raw RGBA buffers and the final image assets. Initially implemented using Canvas2D APIs, the engine was migrated to a Rust-based implementation to leverage high-performance encoding and maintain logic parity across different platforms, including Web (WASM) and Apple Native (UniFFI).
The engine's primary output format is PNG, providing lossless compression and support for alpha transparency, which is critical for pixel art. It handles the entire lifecycle of an export request: from generating appropriate filenames based on project dimensions to binary encoding and triggering browser-level file downloads.
Architecture and Data Flow
The Export Engine follows a multi-tiered architecture that separates high-level UI interactions from low-level binary encoding. The core encoding logic resides in the Rust core crate, which is exposed to the TypeScript frontend through a WebAssembly (WASM) binding layer.
Cross-Language Data Flow
When a user initiates an export, the request travels from the Svelte UI through several layers. The internal PixelCanvas data is processed synchronously in Rust, returning a binary buffer that the browser then handles as a Blob.
Key Components
| Component | Responsibility | Source |
|---|---|---|
PngExport Trait | Rust extension trait for PixelCanvas that performs 8-bit RGBA PNG encoding. | crates/core/src/export.rs:20 |
WasmPixelCanvas | WASM wrapper that exposes the encoding functionality to JavaScript/TypeScript. | wasm/src/lib.rs:85 |
exportAsPng | TypeScript utility that converts the binary buffer into a downloadable file. | src/lib/canvas/export.ts:10 |
generateExportFilename | Utility to generate standardized filenames (e.g., dotorixel-16x16.png). | src/lib/canvas/export.ts:25 |
Backend: Rust Core Encoding
The core of the export system is implemented as an extension trait in Rust. This allows the PixelCanvas struct to support encoding without being tightly coupled to the png crate in its primary definition.
Implementation Details
The engine utilizes the png crate and std::io::Cursor to perform in-memory encoding. This avoids disk I/O during the encoding phase, ensuring high performance within the WASM environment.
- Color Type: Fixed to
RGBAto support transparency. - Bit Depth: Fixed to
Eight(8-bit) per channel. - Data Source: Operates directly on the raw pixel buffer retrieved from
PixelCanvas.
impl PngExport for PixelCanvas {
fn encode_png(&self) -> Result<Vec<u8>, ExportError> {
let mut buf: Vec<u8> = Vec::new();
{
let cursor = Cursor::new(&mut buf);
let mut encoder = png::Encoder::new(cursor, self.width(), self.height());
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
// ... header and image data writing
}
Ok(buf)
}
}
Sources: crates/core/src/export.rs:25-45, tasks/records/006-png-export.md:10-15
Error Handling
The engine defines a specific ExportError enum to handle failures during the encoding process, such as invalid header writing or image data corruption. This implements std::error::Error for compatibility with the project's broader error handling strategy.
Sources: crates/core/src/export.rs:7-18
Frontend: TypeScript Integration
The frontend layer manages the transition from binary data to a user-accessible file. After receiving a Uint8Array from the WASM layer, the engine uses the browser's URL.createObjectURL to trigger a download.
Export Execution
The exportAsPng function handles the finalization of the export process:
- Calls the WASM canvas instance to get the PNG bytes.
- Generates a filename based on the canvas dimensions.
- Creates a
Blobwith theimage/pngMIME type. - Programmatically clicks a temporary anchor element to start the download.
export function exportAsPng(canvas: PngEncodable) {
const bytes = canvas.encode_png();
const filename = generateExportFilename(canvas);
const blob = new Blob([bytes], { type: 'image/png' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
Sources: src/lib/canvas/export.ts:10-23
Filename Generation
Filenames are generated using a consistent pattern: dotorixel-{width}x{height}.png. This helps users organize their exports and identifies the asset's resolution at a glance.
Sources: src/lib/canvas/export.ts:25-27, src/lib/canvas/export.test.ts:5-20
Migration and Interface Decoupling
The system was migrated from a pure TypeScript/Canvas2D implementation to the Rust/WASM model to improve performance and consistency.
Interface Abstraction
To avoid tight coupling between the export utility and the internal WASM class, a PngEncodable interface is used. This allows any object that provides canvas dimensions and an encode_png method to be exported.
export interface PngEncodable {
width(): number;
height(): number;
encode_png(): Uint8Array;
}
Sources: src/lib/canvas/export.ts:4-8, tasks/records/005-ts-wasm-migration.md:12-18
Sequence of Export Operation
The following sequence diagram illustrates the lifecycle of a single export action initiated by a user.
Summary
The Export Engine transition from Canvas2D to a Rust-based WASM implementation has unified the project's core logic. By leveraging the PngExport extension trait and a decoupled TypeScript interface, the engine provides high-performance, lossless PNG generation while remaining flexible for future format expansions or platform-specific UI integrations.