HACanvas unifies two traditionally separate 2D graphics paradigms—pixel-based raster painting/filtering (GIMP) and retained-mode SVG vector geometry (Inkscape)—within a single deterministic scene graph, non-destructive dynamic filter pipeline, and Open Packaging ZIP+XML archive format (.hacanvas).
All subsystems are structured around clean abstract base class hierarchies:
| Base Class | Module | Responsibility |
|---|---|---|
AbstractImageCodec |
src/codecs/abstract-codec.ts |
Magic-byte detection, multi-layer decoding, and binary encoding for .hacanvas, .psd, .xcf, .ai, .pdf, .svg, .ora, .png, .jpg, .webp, .tiff, .gif, .bmp, .tga, .pcx, .ico, .cur, .ppm, and .ff. |
AbstractRasterFilter |
src/filters/abstract-raster-filter.ts |
Selection-aware RGBA pixel buffer convolution, neighborhood sampling, point-transfer, and 85+ programmable JavaScript GIMP/GEGL filters (JsPresetRasterFilter). |
AbstractJsonStore<T> |
src/storage/abstract-json-store.ts |
Atomic JSON configuration and catalog persistence in OS-standard user configuration directories (HaEditorConfigManager, SharedObjectLibrary). |
AbstractAiTool |
src/ai/abstract-ai-tool-base.ts |
Self-describing OpenAI/LM Studio function-calling tools (HaCanvasAiToolRegistry) with live partial-JSON streaming status labels and full execution against any HaCanvasAiContext. |
Every tool, filter, transition, importer, exporter, and codec resides in its own dedicated .ts module:
src/ai/tools/*.ts— 33 individual*AiToolclasses (1 class per file), registered insrc/ai/ha-canvas-ai-tool-registry.tswith session state insrc/ai/ha-canvas-ai-session.tsand streaming clients insrc/ai/abstract-ai-stream-client.ts&src/ai/lm-studio-ai-client.ts.src/filters/*.ts&src/filters/presets/*.ts—AbstractRasterFilter(abstract-raster-filter-base.ts), concrete filter classes (gaussian-blur-filter.ts,sharpen-filter.ts,invert-filter.ts,threshold-filter.ts,brightness-contrast-filter.ts,drop-shadow-filter.ts,js-preset-raster-filter.ts,raster-filter-registry.ts), runtime helpers (src/filters/runtime/), and 121 individual GIMP filter preset files (src/filters/presets/<filter-id>-filter-preset.ts).src/transitions/*.ts— 14 individual raster keyframe blend transition files (crossfade-transition.ts,step-hold-transition.ts,pixel-dissolve-transition.ts,directional-wipe-transition.ts,luma-wipe-transition.ts,venetian-blinds-wipe-transition.ts,clock-sweep-transition.ts,radial-iris-transition.ts,checkerboard-reveal-transition.ts,additive-glow-transition.ts,chromatic-morph-transition.ts,burn-exposure-transition.ts,ripple-warp-transition.ts,zoom-cross-blur-transition.ts).src/importers/*.ts— Dedicated per-format importers (png-importer.ts,bmp-importer.ts,tga-importer.ts,pcx-importer.ts,ppm-importer.ts,farbfeld-importer.ts,ico-importer.ts,xbm-importer.ts,ora-importer.ts,gif-importer.ts,psd-importer.ts,xcf-importer.ts,ai-importer.ts,pdf-importer.ts,svg-importer.ts,exif-xmp-metadata-importer.ts,any-image-importer.ts).src/exporters/*.ts— Dedicated per-format exporters (png-exporter.ts,bmp-exporter.ts,tga-exporter.ts,ppm-exporter.ts,farbfeld-exporter.ts,ico-exporter.ts,ora-exporter.ts,gif-exporter.ts,pdf-exporter.ts,svg-exporter.ts,spritesheet-exporter.ts,exif-xmp-metadata-exporter.ts).src/codecs/*.ts— 14 individualAbstractImageCodecsubclasses (png-codec.ts,bmp-codec.ts,tga-codec.ts,pcx-codec.ts,ico-codec.ts,pnm-codec.ts,farbfeld-codec.ts,xbm-codec.ts,gif-codec.ts,ora-codec.ts,psd-codec.ts,xcf-codec.ts,ai-codec.ts,pdf-codec.ts) +image-codec-registry.ts.src/config/ha-editor-config-manager.ts&src/library/shared-object-library-manager.ts— Persistent OS configuration and Shared Object Library managers.src/types/&src/utils/— Scene graph/metadata TypeScript interfaces and color, easing, category, and bitmap font utilities.
Every open canvas in HybridArtEngine is represented by an ArtCanvasState object:
export interface ArtCanvasState {
canvasId: string;
width: number;
height: number;
dpi: number;
backgroundColor: string;
selection: SelectionMaskSpec;
layers: ArtLayerSpec[];
sharedObjects: Record<string, SharedObjectCanvasSpec>;
embeddedFonts?: Record<string, EmbeddedFontSpec>;
metadata?: DocumentMetadataSpec;
customFilterPresets?: GimpFilterPreset[];
/** Global toggle for previewing attached dynamic filters in the viewport (default: true). Exporting always renders filters. */
previewDynamicFilters?: boolean;
animation: AnimationTimelineSpec;
timeline: {
fps: number;
totalFrames: number;
keyframes: Array<{ frame: number; targetId: string }>;
};
}A canvas (or Shared Object sub-canvas) contains an ordered array of ArtLayerSpec layers rendered bottom-to-top (index 0 is the bottommost layer; layers[layers.length - 1] is the topmost layer):
- Raster Layers (
type: "raster"):- Backed by uncompressed 32-bit RGBA pixel buffers (
width * height * 4bytes) for frame0plus optional per-frame cel buffers (celFrames). - Supports both immediate pixel filters (
rasterFilter, mutating the.rawbuffer directly) and non-destructive attached filter stacks (attachedFilters: AttachedFilterSpec[],filtersEnabled?: boolean).
- Backed by uncompressed 32-bit RGBA pixel buffers (
- Vector Layers (
type: "vector"):- Backed by an ordered array of retained
VectorObjectSpecnodes (rect,circle,ellipse,polygon,star,line,path,text,boolean,image, andreffor<ObjectRef>instances). - Supports layer-level
attachedFiltersas well as per-objectattachedFilterson individualVectorObjectSpecitems.
- Backed by an ordered array of retained
Every vector shape in HACanvas can be inspected and deformed at the vertex and segment level:
- Line Deformation: Lines (
kind: "line") support optional quadratic Bezier control points (cx,cy) to curve between(x, y)and(x2, y2). - Path & Shape Deformation: Multi-segment paths (
path) and polygons/stars (polygon,star) store per-segment quadratic control points ind(M ... Q cx cy x y ... L x y ...). Parametric shapes (rect,circle,ellipse) are automatically tessellated into editable vertex/segment rings when deformed viadeformVectorObject(bend_segment,move_endpoint,straighten_segment,add_vertex,remove_vertex,convert_to_path).
Each Shared Object is an independent mini-canvas stored in state.sharedObjects[objectId]:
- Has its own
width,height,layers: ArtLayerSpec[],attachedFilters?: AttachedFilterSpec[],filtersEnabled?: boolean, andanimation?: AnimationTimelineSpec. - Organized into hierarchical folder trees via
parseSharedObjectCategory(name, category)andbuildCategoryTree(items)(e.g."UI/Badges/GoldStar"-> category"UI/Badges", display name"GoldStar"). - Referenced any number of times by
<ObjectRef>(kind: "ref",refId: objectId) on the main canvas or nested inside other Shared Objects with cycle detection.
flowchart LR
subgraph ObjectPipeline["Vector Object / Shared Object with Attached Filters"]
BG["Render Base Geometry<br/>(Untouched Vectors/Layers)"] --> BC["Base Cache Buffer<br/>(objectRenderCache.baseRgba)"]
BC -->|"If editing object:<br/>bypass filters"| COMP["Composite onto<br/>Parent Layer Buffer"]
BC -->|"If previewing or exporting:<br/>evaluate attachedFilters"| FC["Filtered Cache Buffer<br/>(objectRenderCache.filteredRgba)"]
FC --> COMP
end
-
Immediate vs. Dynamic Filters:
- Calling
engine.rasterFilter(...)(orapply_gimp_filterwithmode: "immediate") mutates the target raster layer's pixel buffer immediately and permanently. - Calling
engine.attachFilter(...)(orapply_gimp_filterwithmode: "attach") appends anAttachedFilterSpecto a layer (ArtLayerSpec), vector object (VectorObjectSpec), or Shared Object (SharedObjectCanvasSpec) without modifying underlying pixels or vector geometry.
- Calling
-
Cached Object Canvas (
objectRenderCache):- When a
VectorObjectSpecorSharedObjectCanvasSpechas attached filters,HybridArtEnginerenders the un-filtered object into an offscreen cached RGBA buffer (baseRgba, keyed by geometry signature) and runs the enabledattachedFiltersstack intofilteredRgba(keyed by geometry + filter parameter signature). - Subsequent viewport renders reuse
filteredRgbain$O(1)$ without re-evaluating expensive convolution filters unless the object or its filter parameters change.
- When a
-
Edit-Time Filter Bypass (
setObjectEditing,setSharedObjectEditing):- When an object or Shared Object is actively being edited (
editingObjectIds/editingSharedObjectIds),shouldRenderAttachedFiltersreturnsfalseduring interactive viewport rendering (forExport: false) so the user/AI sees the crisp un-filtered geometry while editing. As soon as editing ends, dynamic filters automatically resume rendering.
- When an object or Shared Object is actively being edited (
-
Export-Time Filter Rendering & Selective Rasterization:
- All export paths (
exportCanvas) pass{ forExport: true }, which forces all enabled attached filters (af.enabled !== false) to render even if viewport preview (previewDynamicFiltersorfiltersEnabled) is toggled off. - For vector export formats (
svg,pdf),HybridArtEnginecheckslayerNeedsExportRasterizationandobjectNeedsExportRasterization: unfiltered vector shapes/text are emitted as native<path>,<rect>,<circle>, and<text>primitives, whereas objects or layers with active pixel filters (or raster layers) are selectively rasterized into high-resolution PNG<image>elements.
- All export paths (
When rendering any frame f of a canvas (or an isolated Shared Object sub-canvas):
- Background Fill: Initializes an RGBA buffer of size
width × height × 4filled withbackgroundColor. - Keyframe & Cubic-Bezier Curve Evaluation:
- Evaluates animated properties (
x,y,x2,y2,cx,cy,width,height,r,rx,ry,scaleX,scaleY,rotation,skewX,skewY,opacity,fill,stroke,d,points) at framefacross allAnimationTrackSpectracks usingevaluateCubicBezierEasing(t, controlPoints)or preset easing modes. - Enforces per-layer and per-object timeline visibility spans (
startFrame <= f <= endFrame).
- Evaluates animated properties (
- Raster Cel & Cross-Layer Transition Blending:
- Evaluates
rasterCelkeyframe tracks using the configuredRasterBlendPresetJavaScript transition (crossfade,luma_wipe,directional_wipe,pixel_dissolve,chromatic_morph, or custom JS) between consecutive cels or across a target raster layer (rasterBlendTargetLayerId).
- Evaluates
- Layer-by-Layer Compositing & Dynamic Filter Stack:
- Renders each visible layer (
renderLayerToBuffer), applying object-level and layer-levelattachedFilterswhen active, and blends into the composite buffer using the layer'sblendModeandopacity.
- Renders each visible layer (
HybridArtEngine maintains per-canvas undo and redo stacks (undoStacks and redoStacks) storing full scene + raster buffer snapshots (CanvasHistoryEntry):
- Every mutating operation records a descriptive action label (e.g.,
"Draw Shape (star)","Deform Vector Object (bend_segment)","Attach Filter (Gaussian Blur -> Layer 'Hero')","Bake 2 Filter(s) on Layer"). - Interactive drag operations use
beginUndoGroup(canvasId, label)/endUndoGroup(canvasId, label)orwithUndoGroup(canvasId, label, fn)so a continuous pointer drag records a single atomic undo step.