Service Facades
Specifications for the utility service facades accessed via useEditorServices().
PixelService (pixels)
The unified pixel processing facade. Handles coordinate projection between the three physical spaces, and organizes pixel operations into domain-specific namespaces.
Three-Space Coordinate Projection
OpenGPEX defines three coordinate spaces:
| Space | Origin | Unit | Use Case |
|---|---|---|---|
| Screen (S) | Browser viewport top-left | CSS pixels | Mouse events, UI positioning |
| World (W) | Canvas geometric center | Physical pixels | Layer transforms, alignment |
| Local (L) | Canvas content top-left | Physical pixels | Crop boxes, brush strokes, export |
Projection lives on the geometry service, under geometry.space.* — every method takes the active frame (and optionally a camera):
| Method | From → To | Description |
|---|---|---|
geometry.space.screenToLocal(vx, vy, frame, camera?) |
Screen → Local | Recommended. Mouse pos → canvas pixel. For crop, brush, selection. |
geometry.space.localToScreen(px, py, frame, camera?) |
Local → Screen | Canvas pixel → viewport pos. For tooltip placement. |
geometry.space.screenToWorld(vx, vy, frame, camera?) |
Screen → World | Mouse pos → center-origin world. For layer drag. |
geometry.space.worldToLocal(wx, wy, frame) |
World → Local | Center-origin → top-left pixel. |
geometry.space.localToWorld(px, py, frame) |
Local → World | Top-left pixel → center-origin. |
Rect variants also exist (localToScreenRect, worldToScreenRect, worldToLocalRect, localToWorldRect).
⚠️ Never manually calculate zoom offsets (e.g.
e.clientX / camera.k). Always use the projection APIs.
pixels.image — Bitmap Loading & Caching
| Method | Description | Returns |
|---|---|---|
loadBitmap(src) |
Async decode + cache ImageBitmap | Promise<ImageBitmap> |
cacheBitmap(src, blob) |
Pre-warm cache from existing Blob | Promise<void> |
ensureBitmap(src) |
Sync probe (hit → bitmap, miss → fires background fetch) | ImageBitmap | undefined |
pixels.render — Compositing
All methods accept options?: { precision?: 8 | 16; dpr?: number }.
| Method | Description | Returns |
|---|---|---|
compositeFrame(frame, roi?, options?) |
Composite all visible non-group layers of a frame | Promise<CompositeResult> |
compositeLayers(layers, frame, roi?, options?) |
Composite specific layers | Promise<{ result, bounds: { w, h, cx, cy } }> |
compositeResizedLayers(layers, frame, outputSize) |
Composite + scale in one Worker round-trip | Promise<{ result }> |
registerEncoder(mimeType, encoder) |
Register a custom encoder; returns unregister function | () => void |
There is also a low-level unified entry point: pixels.composite(request) → Promise<CompositeResult>.
pixels.rasterize — Layer Rasterization
| Method | Description | Returns |
|---|---|---|
layer(layer, opts?) |
Rasterize any layer type to a bitmap asset (opts?: { dpr?: number }) |
Promise<{ assetId, url }> |
pixels.utils
| Method | Description |
|---|---|
fetchFromUrl(url) |
Fetch a URL into a File |
download(blob, filename) |
Trigger a browser download |
pixels.fileIO — Worker-based File I/O (wasm-vips)
| Method | Description | Returns |
|---|---|---|
decodeTiff(bytes, options?) |
Decode TIFF via wasm-vips ({ preserveColorSpace? }) |
Promise<{ width, height, data }> |
encodeTiff(rgbaData, w, h, options) |
Encode RGBA to TIFF | Promise<Uint8Array> |
getPageCount(bytes) |
Get multi-page TIFF info | Promise<{ pages, pageWidth, pageHeight }> |
decodePage(bytes, page) |
Decode specific TIFF page | Promise<{ width, height, data }> |
iccToSrgb(bytes) |
Convert non-sRGB ICC pixels to sRGB RGBA (returns ICC bytes) | Promise<{ width, height, data, iccProfileData? }> |
srgbToIcc(rgbaData, w, h, iccProfileData) |
Convert sRGB pixels back to target ICC space | Promise<{ data }> |
encodeAvif(rgbaData, w, h, options) |
Encode RGBA to AVIF | Promise<Uint8Array> |
CompositeResult
All composite operations return a CompositeResult (a PixelResult subclass) with lazy consumption:
interface CompositeResult {
toBlob(format?: string): Promise<Blob>;
toImageData(): Promise<ImageData>;
toAsset(): Promise<AssetRef>; // { assetId, url, dimensions } — auto-registers + warms caches
dimensions: { w: number; h: number };
trimmed(): Promise<{ result: CompositeResult; offset: { x: number; y: number } } | null>;
}
RenderToBlobOptions
Rich export configuration:
interface RenderToBlobOptions {
format?: string; // MIME type, or 'raw' to receive an ImageBitmap (no encoding)
quality?: number; // 0..1 (default 0.92)
exportBitDepth?: 8 | 16; // 16 requests the vips-native lane when eligible
metadata?: ImageMetadata; // EXIF/ICC passthrough
exportConfig?: {
dpi?: number;
preserveExif?: boolean;
embedIcc?: boolean;
frameColorSpace?: 'srgb' | 'display-p3' | 'adobe-rgb' | 'prophoto-rgb';
tiffCompression?: string; // 'lzw' | 'deflate' | 'none'
pngCompression?: number; // 0..9
jpegQuality?: number; // 1..100 (overrides `quality`)
resize?: { w: number; h: number };
};
layerIds?: string[]; // Selective layer rendering
}
LayerService (layers)
Safe, high-level layer operations with built-in validation. Prefer these over raw actions.addLayers etc.
| Method | Description | Safety Check |
|---|---|---|
layers.addLayer(frameId, layer) |
Add a layer | Auto-expands composite (group) layers |
layers.activate(frameId, layerId) |
Set active layer | Guards non-activatable layers (masks, sub-layers) |
layers.removeLayers(frameId, layerIds) |
Remove layers | Cascade-deletes children, auto-migrates focus |
GeometryService (geometry)
Coordinate transforms, bounding boxes, hit testing, snapping, and camera math. The full surface is grouped into namespaces: geometry.space, geometry.transform, geometry.camera, geometry.snapping, geometry.shape, plus a geometry.Matrix constructor.
Key Methods
| Method | Description |
|---|---|
geometry.space.screenToLocal(vx, vy, frame, camera?) |
Viewport physical coords → layer-local coords (and all sibling projection methods) |
geometry.getLayerBoundingBox(layer, extraOverride?) |
Get layer world bounding box (WorldRect) |
geometry.space.testLayerHit(pos, layer) |
Hit-test a world point against a layer |
geometry.space.pickTopLayer(pos, layers) |
Topmost layer under a world point |
geometry.snapping.snapRect(rect, frame, options?) |
Rect alignment snapping with smart guides |
geometry.camera.getFitCamera(viewport, content, options?) |
Compute the camera that fits content in view |
AssetService (assets)
Content-Addressable Storage (CAS) for image blobs, persisted in IndexedDB (OpenGPEXAssets / Assets_V2 store via localforage). Assets are indexed by SHA-256 hash; associated lossless "raw" originals are stored under the raw:${assetId} key topology.
Key Methods
| Method | Description | Returns |
|---|---|---|
register(blob, dimensions, options?) |
Register blob → SHA-256 hash → AssetRef (options?: { dprScale?, precomputedHash? }) |
Promise<AssetRef> |
storeRaw(rawBlob) |
Persist lossless original under raw:${assetId} |
Promise<string | undefined> |
inject(assetId, blob, tileMeta) |
Direct store when hash is known | Promise<string> |
get(assetId) |
Get asset entry info | AssetEntryInfo | undefined |
getURL(assetId) |
Get ObjectURL for asset | string | undefined |
resolve(assetId?, fallbackSrc?) |
Resolve to URL with fallback | string |
sweep(activeIds, force?) |
GC unused assets | void |
hydrate(activeIds?) |
Restore assets from IndexedDB | Promise<void> |
getPool() |
Snapshot of all asset entries | Record<string, AssetEntryInfo> |
setCallbacks({ onRegistered?, onReleased? }) |
Hook asset lifecycle (used to warm/evict render caches) | void |
clear() |
Clear all assets | void |
AssetRef
interface AssetRef {
assetId: string; // SHA-256 hash
url: string; // ObjectURL
dimensions: { w: number; h: number };
}
PluginService (plugins)
Plugin lifecycle management, command registry, and busy state.
Key Methods
| Method | Description |
|---|---|
plugins.getPlugin(uid) |
Get a plugin by UID |
plugins.getCommand(uid) |
Look up a command by UID |
plugins.getShortcutLabel(cmdUid, all?) |
Get formatted shortcut label |
plugins.getAllPlugins() |
Get all registered plugins |
plugins.isBusy(uid) |
Check if a plugin is in busy state |
plugins.subscribe(cb) |
Subscribe to plugin list changes |
plugins.registerPlugin(plugin) |
Register a new plugin |
plugins.unregisterPlugin(uid) |
Remove a plugin |
ClipboardService (clipboard)
System clipboard interaction.
| Method | Description |
|---|---|
writeBlob(blob, metadata) |
Write image + metadata to clipboard |
writeByUrl(url, metadata) |
Download URL then write to clipboard |
read(event?) |
Read image data from clipboard |
StateStorage (storage)
Editor state persistence via localforage (IndexedDB, OpenGPEX / State_V1 store).
| Method | Description |
|---|---|
save(state) |
Persist editor state (EditorData) |
restore() |
Restore editor state (Promise<EditorData | null>) |
gc(state, force?) |
Garbage collect orphan data |
export(frame) |
Export a frame to { state, assets: Record<string, Blob> } |
import(state) |
Import exported state back into a Frame |
clear() |
Clear all stored state |
Last updated: 2026-07-30