distark-render 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +81 -2
  2. package/dist/modules/adapters/p5Renderer.d.ts +71 -0
  3. package/dist/modules/adapters/p5Renderer.d.ts.map +1 -0
  4. package/dist/modules/adapters/p5Renderer.js +145 -0
  5. package/dist/modules/adapters/p5Renderer.js.map +1 -0
  6. package/dist/modules/adapters/skiaRenderer.d.ts +84 -0
  7. package/dist/modules/adapters/skiaRenderer.d.ts.map +1 -0
  8. package/dist/modules/adapters/skiaRenderer.js +262 -0
  9. package/dist/modules/adapters/skiaRenderer.js.map +1 -0
  10. package/dist/modules/eyeSystem.d.ts +34 -1
  11. package/dist/modules/eyeSystem.d.ts.map +1 -1
  12. package/dist/modules/eyeSystem.js +46 -20
  13. package/dist/modules/eyeSystem.js.map +1 -1
  14. package/dist/modules/imageLoad.d.ts +4 -1
  15. package/dist/modules/imageLoad.d.ts.map +1 -1
  16. package/dist/modules/imageLoad.js +65 -19
  17. package/dist/modules/imageLoad.js.map +1 -1
  18. package/dist/modules/mouthSystem.d.ts +15 -15
  19. package/dist/modules/mouthSystem.d.ts.map +1 -1
  20. package/dist/modules/mouthSystem.js +91 -52
  21. package/dist/modules/mouthSystem.js.map +1 -1
  22. package/dist/modules/renderRig.d.ts +1 -1
  23. package/dist/modules/renderRig.d.ts.map +1 -1
  24. package/dist/modules/renderRig.js +150 -49
  25. package/dist/modules/renderRig.js.map +1 -1
  26. package/dist/tests/test-joint-movement.d.ts +7 -0
  27. package/dist/tests/test-joint-movement.d.ts.map +1 -0
  28. package/dist/tests/test-joint-movement.js +323 -0
  29. package/dist/tests/test-joint-movement.js.map +1 -0
  30. package/dist/tests/test-skia.d.ts +12 -0
  31. package/dist/tests/test-skia.d.ts.map +1 -0
  32. package/dist/tests/test-skia.js +33 -0
  33. package/dist/tests/test-skia.js.map +1 -0
  34. package/dist/tests/test-visual-verification.d.ts +7 -0
  35. package/dist/tests/test-visual-verification.d.ts.map +1 -0
  36. package/dist/tests/test-visual-verification.js +180 -0
  37. package/dist/tests/test-visual-verification.js.map +1 -0
  38. package/dist/types.d.ts +18 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +21 -2
  41. package/dist/test-skia.d.ts +0 -15
  42. package/dist/test-skia.d.ts.map +0 -1
  43. package/dist/test-skia.js +0 -179
  44. package/dist/test-skia.js.map +0 -1
package/README.md CHANGED
@@ -51,6 +51,87 @@ await renderer.render(canvas, characterData);
51
51
  - ⚡ TypeScript support with full type definitions
52
52
  - 🌐 Works in browser and Node.js
53
53
 
54
+ ## Testing
55
+
56
+ ### Automated Test Suite
57
+
58
+ Run all automated tests to verify rendering and joint movement:
59
+
60
+ ```bash
61
+ # Run all tests (visual verification + joint movement)
62
+ npm test
63
+
64
+ # Or run tests individually
65
+ npm run test-visual # Eyes and mouth rendering
66
+ npm run test-joints # Joint movement and limb positioning
67
+ ```
68
+
69
+ ### Visual Verification Test
70
+
71
+ Verify that eyes and mouth are rendering correctly using automated color comparison:
72
+
73
+ ```bash
74
+ # Test a specific character file
75
+ npm run test-visual
76
+ node dist/tests/test-visual-verification.js character.json
77
+ ```
78
+
79
+ **What it tests:**
80
+ - ✅ Left eye region has rendered content
81
+ - ✅ Right eye region has rendered content
82
+ - ✅ Mouth region has rendered content
83
+
84
+ The test uses pixel sampling and color comparison to verify that character features are actually being rendered to the canvas. It calculates expected regions based on the rig data and checks that those regions contain pixels different from the background color.
85
+
86
+ ### Joint Movement Test
87
+
88
+ Verify that character joints move correctly and limbs appear in expected screen sectors:
89
+
90
+ ```bash
91
+ # Test joint movements
92
+ npm run test-joints
93
+ node dist/tests/test-joint-movement.js character.json
94
+ ```
95
+
96
+ **What it tests:**
97
+ - ✅ **Arm Raised**: Left arm rotates upward and appears in top/middle-left sector
98
+ - ✅ **Leg Forward**: Right leg moves forward and appears in middle/bottom-right sector
99
+ - ✅ **Head Rotation**: Head turns and remains in top/middle-center sector
100
+ - ✅ **Multiple Joints**: Arms spread and legs move simultaneously (T-pose)
101
+
102
+ The test modifies joint rotation values and verifies that limbs render in the expected screen sectors by sampling pixels and checking for content (non-background pixels). This ensures the rendering pipeline correctly applies transformations to the character rig.
103
+
104
+ ### Manual Testing with Command Line
105
+
106
+ You can also manually test character rigs and save rendered output:
107
+
108
+ ```bash
109
+ # Build the project first
110
+ npm run build
111
+
112
+ # Run test-skia with default settings (renders tank.json)
113
+ npm run test-skia
114
+
115
+ # Or run directly with custom parameters:
116
+ node dist/tests/test-skia.js [input.json] [output.png] [width] [height]
117
+
118
+ # Examples:
119
+ node dist/tests/test-skia.js tank.json output.png 1000 1000
120
+ node dist/tests/test-skia.js character.json render.png 800 600
121
+ ```
122
+
123
+ **Parameters:**
124
+ - `input.json` - Path to your character rig JSON file (default: `assets/tank.json`)
125
+ - `output.png` - Output image filename (default: `output-tank.png`)
126
+ - `width` - Canvas width in pixels (default: `1000`)
127
+ - `height` - Canvas height in pixels (default: `1000`)
128
+
129
+ The test script will:
130
+ 1. Load your character rig JSON
131
+ 2. Automatically fetch and cache all images
132
+ 3. Render the character to a PNG file
133
+ 4. Show pivot points for debugging
134
+
54
135
  ## API
55
136
 
56
137
  ### ImageLoader
@@ -70,6 +151,4 @@ const renderer = new CharacterRigRenderer(imageLoader?: ImageLoader);
70
151
  await renderer.render(canvas, rigData, loadedImages?, cameraOffset?, showPivotPoints?);
71
152
  ```
72
153
 
73
- ## License
74
154
 
75
- MIT
@@ -0,0 +1,71 @@
1
+ /**
2
+ * p5.js Renderer - Pre-built adapter for p5.js
3
+ * Reduces setup from ~80 lines to ~3 lines
4
+ */
5
+ import { ImageLoader } from '../imageLoad.js';
6
+ import { CharacterRigRenderer } from '../renderRig.js';
7
+ import type { RigData, RenderOptions, RigRenderData } from '../../types.js';
8
+ /**
9
+ * p5-specific image loader
10
+ * Extends ImageLoader to use p5.loadImage instead of browser Image API
11
+ */
12
+ export declare class P5ImageLoader extends ImageLoader {
13
+ private p5;
14
+ constructor(p5Instance: any, baseHost?: string);
15
+ /**
16
+ * Override to use p5.loadImage
17
+ * Inherits all caching, URL resolution, and batch loading from base class
18
+ */
19
+ loadImage(key: string, imageData: string, onLoad?: (key: string, img: any) => void, onError?: (key: string, error: Event | string) => void): void;
20
+ }
21
+ /**
22
+ * p5.js Renderer - Complete rendering solution for p5.js
23
+ *
24
+ * Usage:
25
+ * const renderer = new P5Renderer(p);
26
+ * await renderer.loadImages(rigData);
27
+ * renderer.render(rigData, { width: 800, height: 800 });
28
+ *
29
+ * Advanced Usage (full p5 control):
30
+ * const renderData = renderer.compute(rigData, options);
31
+ * // Modify renderData.objects as needed
32
+ * renderer.renderObjects(renderData.objects);
33
+ */
34
+ export declare class P5Renderer extends CharacterRigRenderer {
35
+ private p5;
36
+ private loadedImages;
37
+ private isReady;
38
+ constructor(p5Instance: any, baseHost?: string);
39
+ /**
40
+ * Load all images for a character rig
41
+ * Must be called before renderRig()
42
+ */
43
+ loadImages(rigData: RigData): Promise<void>;
44
+ /**
45
+ * Compute rig render data (for advanced control)
46
+ * Returns the computed transforms that you can modify before rendering
47
+ */
48
+ compute(rigData: RigData, options?: Partial<RenderOptions>): RigRenderData;
49
+ /**
50
+ * Render objects to p5 canvas
51
+ * Can be used with modified render data for custom effects
52
+ */
53
+ renderObjects(objects: any[], showPivots?: boolean, pivotPoints?: any[]): void;
54
+ /**
55
+ * Simple one-line render (compute + renderObjects)
56
+ *
57
+ * @param rigData - Character rig data
58
+ * @param options - Render options (width, height, camera offset)
59
+ * @param showPivots - Whether to show pivot points for debugging
60
+ */
61
+ renderRig(rigData: RigData, options?: Partial<RenderOptions>, showPivots?: boolean): void;
62
+ /**
63
+ * Check if images are loaded and ready to render
64
+ */
65
+ ready(): boolean;
66
+ /**
67
+ * Get loaded images (for custom rendering)
68
+ */
69
+ getLoadedImages(): Record<string, any>;
70
+ }
71
+ //# sourceMappingURL=p5Renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"p5Renderer.d.ts","sourceRoot":"","sources":["../../../modules/adapters/p5Renderer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE5E;;;GAGG;AACH,qBAAa,aAAc,SAAQ,WAAW;IAC1C,OAAO,CAAC,EAAE,CAAM;gBAEJ,UAAU,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM;IAK9C;;;OAGG;IACH,SAAS,CACL,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,EACxC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,KAAK,IAAI,GACvD,IAAI;CA6BV;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,UAAW,SAAQ,oBAAoB;IAChD,OAAO,CAAC,EAAE,CAAM;IAChB,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,OAAO,CAAkB;gBAErB,UAAU,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM;IAM9C;;;OAGG;IACG,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjD;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa;IAe1E;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,UAAU,GAAE,OAAe,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI;IA6BrF;;;;;;OAMG;IACH,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,EAAE,UAAU,GAAE,OAAe,GAAG,IAAI;IAKhG;;OAEG;IACH,KAAK,IAAI,OAAO;IAIhB;;OAEG;IACH,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAGzC"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * p5.js Renderer - Pre-built adapter for p5.js
3
+ * Reduces setup from ~80 lines to ~3 lines
4
+ */
5
+ import { ImageLoader } from '../imageLoad.js';
6
+ import { CharacterRigRenderer } from '../renderRig.js';
7
+ /**
8
+ * p5-specific image loader
9
+ * Extends ImageLoader to use p5.loadImage instead of browser Image API
10
+ */
11
+ export class P5ImageLoader extends ImageLoader {
12
+ constructor(p5Instance, baseHost) {
13
+ super(baseHost);
14
+ this.p5 = p5Instance;
15
+ }
16
+ /**
17
+ * Override to use p5.loadImage
18
+ * Inherits all caching, URL resolution, and batch loading from base class
19
+ */
20
+ loadImage(key, imageData, onLoad, onError) {
21
+ const imageUrl = this.resolveImageSource(imageData);
22
+ if (!imageUrl) {
23
+ console.warn(`Could not resolve image source for ${key}: ${imageData}`);
24
+ if (onError)
25
+ onError(key, 'Invalid image source');
26
+ return;
27
+ }
28
+ // Check cache first (from base class)
29
+ const cached = this.getCachedImage(key);
30
+ if (cached) {
31
+ if (onLoad)
32
+ onLoad(key, cached);
33
+ return;
34
+ }
35
+ // Use p5.loadImage
36
+ this.p5.loadImage(imageUrl, (img) => {
37
+ this.cacheImage(key, img);
38
+ if (onLoad)
39
+ onLoad(key, img);
40
+ }, (err) => {
41
+ console.error(`Failed to load ${key}:`, err);
42
+ if (onError)
43
+ onError(key, err);
44
+ });
45
+ }
46
+ }
47
+ /**
48
+ * p5.js Renderer - Complete rendering solution for p5.js
49
+ *
50
+ * Usage:
51
+ * const renderer = new P5Renderer(p);
52
+ * await renderer.loadImages(rigData);
53
+ * renderer.render(rigData, { width: 800, height: 800 });
54
+ *
55
+ * Advanced Usage (full p5 control):
56
+ * const renderData = renderer.compute(rigData, options);
57
+ * // Modify renderData.objects as needed
58
+ * renderer.renderObjects(renderData.objects);
59
+ */
60
+ export class P5Renderer extends CharacterRigRenderer {
61
+ constructor(p5Instance, baseHost) {
62
+ const imageLoader = new P5ImageLoader(p5Instance, baseHost);
63
+ super(imageLoader);
64
+ this.loadedImages = {};
65
+ this.isReady = false;
66
+ this.p5 = p5Instance;
67
+ }
68
+ /**
69
+ * Load all images for a character rig
70
+ * Must be called before renderRig()
71
+ */
72
+ async loadImages(rigData) {
73
+ this.loadedImages = await this.imageLoader.loadAllRigImages(rigData, true);
74
+ this.isReady = true;
75
+ }
76
+ /**
77
+ * Compute rig render data (for advanced control)
78
+ * Returns the computed transforms that you can modify before rendering
79
+ */
80
+ compute(rigData, options) {
81
+ if (!this.isReady) {
82
+ throw new Error('Images not loaded. Call loadImages() first.');
83
+ }
84
+ const renderOptions = {
85
+ canvasWidth: options?.canvasWidth ?? this.p5.width,
86
+ canvasHeight: options?.canvasHeight ?? this.p5.height,
87
+ cameraOffset: options?.cameraOffset ?? { x: 0, y: 0 },
88
+ loadedImages: this.loadedImages
89
+ };
90
+ return this.computeCharacterRigData(rigData, renderOptions);
91
+ }
92
+ /**
93
+ * Render objects to p5 canvas
94
+ * Can be used with modified render data for custom effects
95
+ */
96
+ renderObjects(objects, showPivots = false, pivotPoints) {
97
+ objects.forEach(obj => {
98
+ if (!obj.imageData)
99
+ return;
100
+ this.p5.push();
101
+ this.p5.translate(obj.x, obj.y);
102
+ this.p5.rotate(obj.rotation);
103
+ this.p5.scale(obj.scaleX, obj.scaleY);
104
+ const drawX = -obj.width * obj.anchorX;
105
+ const drawY = -obj.height * obj.anchorY;
106
+ this.p5.image(obj.imageData, drawX, drawY, obj.width, obj.height);
107
+ this.p5.pop();
108
+ });
109
+ // Draw pivot points if requested
110
+ if (showPivots && pivotPoints) {
111
+ pivotPoints.forEach((pivot) => {
112
+ this.p5.push();
113
+ this.p5.fill(0, 150, 255, 230);
114
+ this.p5.stroke(0);
115
+ this.p5.strokeWeight(1);
116
+ this.p5.circle(pivot.x, pivot.y, 8);
117
+ this.p5.pop();
118
+ });
119
+ }
120
+ }
121
+ /**
122
+ * Simple one-line render (compute + renderObjects)
123
+ *
124
+ * @param rigData - Character rig data
125
+ * @param options - Render options (width, height, camera offset)
126
+ * @param showPivots - Whether to show pivot points for debugging
127
+ */
128
+ renderRig(rigData, options, showPivots = false) {
129
+ const renderData = this.compute(rigData, options);
130
+ this.renderObjects(renderData.objects, showPivots, renderData.pivotPoints);
131
+ }
132
+ /**
133
+ * Check if images are loaded and ready to render
134
+ */
135
+ ready() {
136
+ return this.isReady;
137
+ }
138
+ /**
139
+ * Get loaded images (for custom rendering)
140
+ */
141
+ getLoadedImages() {
142
+ return this.loadedImages;
143
+ }
144
+ }
145
+ //# sourceMappingURL=p5Renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"p5Renderer.js","sourceRoot":"","sources":["../../../modules/adapters/p5Renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Skia Canvas Renderer - Pre-built adapter for Node.js/Skia
3
+ * Reduces setup from ~75 lines to ~4 lines
4
+ */
5
+ import { ImageLoader } from '../imageLoad.js';
6
+ import { CharacterRigRenderer } from '../renderRig.js';
7
+ import type { RigData, RenderOptions, RigRenderData } from '../../types.js';
8
+ /**
9
+ * Skia-specific image loader
10
+ * Handles async image loading for Node.js canvas
11
+ */
12
+ export declare class SkiaImageLoader extends ImageLoader {
13
+ constructor(baseHost?: string);
14
+ /**
15
+ * Async image loading for Skia Canvas
16
+ * Note: This requires the @napi-rs/canvas package's loadImage function
17
+ */
18
+ loadImageAsync(key: string, imageData: string): Promise<any>;
19
+ loadAllRigImagesAsync(rigData: RigData): Promise<Record<string, any>>;
20
+ }
21
+ /**
22
+ * Skia Canvas Renderer - Complete rendering solution for Node.js
23
+ *
24
+ * Simple Usage:
25
+ * const renderer = new SkiaRenderer();
26
+ * await renderer.renderToFile('output.png', rigData, { width: 800, height: 800 });
27
+ *
28
+ * Advanced Usage:
29
+ * const renderer = new SkiaRenderer();
30
+ * await renderer.loadImages(rigData);
31
+ * const canvas = renderer.createCanvas(800, 800);
32
+ * renderer.render(canvas, rigData);
33
+ * await canvas.saveAs('output.png');
34
+ */
35
+ export declare class SkiaRenderer extends CharacterRigRenderer {
36
+ private loadedImages;
37
+ private isReady;
38
+ constructor(baseHost?: string);
39
+ /**
40
+ * Load all images for a character rig
41
+ */
42
+ loadImages(rigData: RigData): Promise<void>;
43
+ /**
44
+ * Create a Skia canvas
45
+ */
46
+ createCanvas(width: number, height: number): Promise<any>;
47
+ /**
48
+ * Compute rig render data (for advanced control)
49
+ */
50
+ compute(rigData: RigData, options?: Partial<RenderOptions>): RigRenderData;
51
+ /**
52
+ * Render objects to canvas context
53
+ * Use this to render pre-computed and modified render data
54
+ */
55
+ renderObjects(ctx: any, objects: any[], showPivots?: boolean, pivotPoints?: any[]): void;
56
+ /**
57
+ * Render to a canvas (Skia Canvas 2D API)
58
+ * Computes render data and renders it
59
+ */
60
+ renderToCanvas(canvas: any, rigData: RigData, options?: Partial<RenderOptions>, showPivots?: boolean): void;
61
+ /**
62
+ * One-liner: Render directly to file
63
+ *
64
+ * @param outputPath - Path to save PNG file
65
+ * @param rigData - Character rig data
66
+ * @param options - Render options (width, height, camera offset)
67
+ */
68
+ renderToFile(outputPath: string, rigData: RigData, options?: Partial<RenderOptions> & {
69
+ showPivots?: boolean;
70
+ }): Promise<void>;
71
+ /**
72
+ * Render to buffer (for streaming, HTTP responses, etc.)
73
+ */
74
+ renderToBuffer(rigData: RigData, options?: Partial<RenderOptions>, format?: 'png' | 'jpeg'): Promise<Buffer>;
75
+ /**
76
+ * Check if images are loaded
77
+ */
78
+ ready(): boolean;
79
+ /**
80
+ * Get loaded images (for custom rendering)
81
+ */
82
+ getLoadedImages(): Record<string, any>;
83
+ }
84
+ //# sourceMappingURL=skiaRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skiaRenderer.d.ts","sourceRoot":"","sources":["../../../modules/adapters/skiaRenderer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE5E;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,WAAW;gBAChC,QAAQ,CAAC,EAAE,MAAM;IAI7B;;;OAGG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IA+B5D,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAiE9E;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,YAAa,SAAQ,oBAAoB;IAClD,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,OAAO,CAAkB;gBAErB,QAAQ,CAAC,EAAE,MAAM;IAK7B;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAa/D;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,aAAa;IAe9E;;;OAGG;IACH,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,UAAU,GAAE,OAAe,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI;IAgC/F;;;OAGG;IACH,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,EAAE,UAAU,GAAE,OAAe,GAAG,IAAI;IAgBlH;;;;;;OAMG;IACG,YAAY,CACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5D,OAAO,CAAC,IAAI,CAAC;IAoBhB;;OAEG;IACG,cAAc,CAChB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,EAChC,MAAM,GAAE,KAAK,GAAG,MAAc,GAC/B,OAAO,CAAC,MAAM,CAAC;IAclB;;OAEG;IACH,KAAK,IAAI,OAAO;IAIhB;;OAEG;IACH,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAGzC"}
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Skia Canvas Renderer - Pre-built adapter for Node.js/Skia
3
+ * Reduces setup from ~75 lines to ~4 lines
4
+ */
5
+ import { ImageLoader } from '../imageLoad.js';
6
+ import { CharacterRigRenderer } from '../renderRig.js';
7
+ /**
8
+ * Skia-specific image loader
9
+ * Handles async image loading for Node.js canvas
10
+ */
11
+ export class SkiaImageLoader extends ImageLoader {
12
+ constructor(baseHost) {
13
+ super(baseHost);
14
+ }
15
+ /**
16
+ * Async image loading for Skia Canvas
17
+ * Note: This requires the @napi-rs/canvas package's loadImage function
18
+ */
19
+ async loadImageAsync(key, imageData) {
20
+ const imageUrl = this.resolveImageSource(imageData);
21
+ if (!imageUrl) {
22
+ throw new Error(`Could not resolve image source for ${key}: ${imageData}`);
23
+ }
24
+ // Check cache first
25
+ const cached = this.getCachedImage(key);
26
+ if (cached) {
27
+ return cached;
28
+ }
29
+ // Dynamic import to avoid browser compatibility issues
30
+ let loadImage;
31
+ try {
32
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
33
+ // @ts-ignore - Optional dependency
34
+ const module = await import('@napi-rs/canvas');
35
+ loadImage = module.loadImage;
36
+ }
37
+ catch {
38
+ // Fallback to skia-canvas if @napi-rs/canvas is not available
39
+ const module = await import('skia-canvas');
40
+ loadImage = module.loadImage;
41
+ }
42
+ const img = await loadImage(imageUrl);
43
+ this.cacheImage(key, img);
44
+ return img;
45
+ }
46
+ async loadAllRigImagesAsync(rigData) {
47
+ const imagesToLoad = [];
48
+ // Collect all unique image paths from imagePaths
49
+ // CHANGED: We now ONLY track the actual path/hash, not semantic keys
50
+ if (rigData.imagePaths) {
51
+ for (const [, hash] of Object.entries(rigData.imagePaths)) {
52
+ if (hash && typeof hash === 'string' && !hash.includes('[MEDIA_REMOVED]')) {
53
+ // Only add if not already in list (deduplicate)
54
+ if (!imagesToLoad.find(item => item.hash === hash)) {
55
+ imagesToLoad.push({ hash });
56
+ }
57
+ }
58
+ }
59
+ }
60
+ // Collect all eye images
61
+ if (rigData.eyes) {
62
+ for (const [eyeKey, value] of Object.entries(rigData.eyes)) {
63
+ if (typeof value === 'string' && !value.includes('[MEDIA_REMOVED]') &&
64
+ (eyeKey.includes('Image') || eyeKey.includes('Iris') || eyeKey.includes('Lid'))) {
65
+ // Only add if not already in list (deduplicate)
66
+ if (!imagesToLoad.find(item => item.hash === value)) {
67
+ imagesToLoad.push({ hash: value });
68
+ }
69
+ }
70
+ }
71
+ }
72
+ // Collect all mouth images from rigData.mouth
73
+ if (rigData.mouth) {
74
+ for (const [_mouthKey, value] of Object.entries(rigData.mouth)) {
75
+ if (typeof value === 'string' && !value.includes('[MEDIA_REMOVED]')) {
76
+ // Only add if not already in list (deduplicate)
77
+ if (!imagesToLoad.find(item => item.hash === value)) {
78
+ imagesToLoad.push({ hash: value });
79
+ }
80
+ }
81
+ }
82
+ }
83
+ console.log(`📦 Loading ${imagesToLoad.length} images for character rig...`);
84
+ const loadedImages = {};
85
+ let loaded = 0;
86
+ let failed = 0;
87
+ // Load all images in parallel
88
+ await Promise.all(imagesToLoad.map(async ({ hash }) => {
89
+ // CRITICAL: Use the actual path/hash as the cache key!
90
+ const img = await this.loadImageAsync(hash, hash);
91
+ if (img) {
92
+ loadedImages[hash] = img; // Cache by actual path, not semantic key!
93
+ loaded++;
94
+ }
95
+ else {
96
+ failed++;
97
+ }
98
+ }));
99
+ console.log(`✓ Loaded ${loaded}/${imagesToLoad.length} images (${failed} failed)`);
100
+ console.log('📦 Image Cache (indexed by actual paths):', Object.keys(loadedImages).map(k => k.substring(0, 50)));
101
+ return loadedImages;
102
+ }
103
+ }
104
+ /**
105
+ * Skia Canvas Renderer - Complete rendering solution for Node.js
106
+ *
107
+ * Simple Usage:
108
+ * const renderer = new SkiaRenderer();
109
+ * await renderer.renderToFile('output.png', rigData, { width: 800, height: 800 });
110
+ *
111
+ * Advanced Usage:
112
+ * const renderer = new SkiaRenderer();
113
+ * await renderer.loadImages(rigData);
114
+ * const canvas = renderer.createCanvas(800, 800);
115
+ * renderer.render(canvas, rigData);
116
+ * await canvas.saveAs('output.png');
117
+ */
118
+ export class SkiaRenderer extends CharacterRigRenderer {
119
+ constructor(baseHost) {
120
+ const imageLoader = new SkiaImageLoader(baseHost);
121
+ super(imageLoader);
122
+ this.loadedImages = {};
123
+ this.isReady = false;
124
+ }
125
+ /**
126
+ * Load all images for a character rig
127
+ */
128
+ async loadImages(rigData) {
129
+ const loader = this.getImageLoader();
130
+ this.loadedImages = await loader.loadAllRigImagesAsync(rigData);
131
+ this.isReady = true;
132
+ }
133
+ /**
134
+ * Create a Skia canvas
135
+ */
136
+ async createCanvas(width, height) {
137
+ try {
138
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
139
+ // @ts-ignore - Optional dependency
140
+ const module = await import('@napi-rs/canvas');
141
+ return module.createCanvas(width, height);
142
+ }
143
+ catch {
144
+ // Fallback to skia-canvas
145
+ const module = await import('skia-canvas');
146
+ return module.Canvas ? new module.Canvas(width, height) : module.createCanvas(width, height);
147
+ }
148
+ }
149
+ /**
150
+ * Compute rig render data (for advanced control)
151
+ */
152
+ compute(rigData, options = {}) {
153
+ if (!this.isReady) {
154
+ throw new Error('Images not loaded. Call loadImages() first.');
155
+ }
156
+ const renderOptions = {
157
+ canvasWidth: options.canvasWidth ?? 800,
158
+ canvasHeight: options.canvasHeight ?? 800,
159
+ cameraOffset: options.cameraOffset ?? { x: 0, y: 0 },
160
+ loadedImages: this.loadedImages
161
+ };
162
+ return this.computeCharacterRigData(rigData, renderOptions);
163
+ }
164
+ /**
165
+ * Render objects to canvas context
166
+ * Use this to render pre-computed and modified render data
167
+ */
168
+ renderObjects(ctx, objects, showPivots = false, pivotPoints) {
169
+ objects.forEach(obj => {
170
+ if (!obj.imageData)
171
+ return;
172
+ ctx.save();
173
+ ctx.translate(obj.x, obj.y);
174
+ ctx.rotate(obj.rotation);
175
+ ctx.scale(obj.scaleX, obj.scaleY);
176
+ const drawX = -obj.width * obj.anchorX;
177
+ const drawY = -obj.height * obj.anchorY;
178
+ ctx.drawImage(obj.imageData, drawX, drawY, obj.width, obj.height);
179
+ ctx.restore();
180
+ });
181
+ // Draw pivot points if requested
182
+ if (showPivots && pivotPoints) {
183
+ pivotPoints.forEach(pivot => {
184
+ ctx.save();
185
+ ctx.fillStyle = 'rgba(0, 150, 255, 0.9)';
186
+ ctx.strokeStyle = '#000';
187
+ ctx.lineWidth = 1;
188
+ ctx.beginPath();
189
+ ctx.arc(pivot.x, pivot.y, 4, 0, Math.PI * 2);
190
+ ctx.fill();
191
+ ctx.stroke();
192
+ ctx.restore();
193
+ });
194
+ }
195
+ }
196
+ /**
197
+ * Render to a canvas (Skia Canvas 2D API)
198
+ * Computes render data and renders it
199
+ */
200
+ renderToCanvas(canvas, rigData, options, showPivots = false) {
201
+ const ctx = canvas.getContext('2d');
202
+ const renderData = this.compute(rigData, {
203
+ ...options,
204
+ canvasWidth: canvas.width,
205
+ canvasHeight: canvas.height
206
+ });
207
+ // Clear canvas
208
+ ctx.fillStyle = '#f0f0f0';
209
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
210
+ // Render using renderObjects
211
+ this.renderObjects(ctx, renderData.objects, showPivots, renderData.pivotPoints);
212
+ }
213
+ /**
214
+ * One-liner: Render directly to file
215
+ *
216
+ * @param outputPath - Path to save PNG file
217
+ * @param rigData - Character rig data
218
+ * @param options - Render options (width, height, camera offset)
219
+ */
220
+ async renderToFile(outputPath, rigData, options) {
221
+ // Load images if not already loaded
222
+ if (!this.isReady) {
223
+ await this.loadImages(rigData);
224
+ }
225
+ // Create canvas
226
+ const width = options?.canvasWidth ?? 800;
227
+ const height = options?.canvasHeight ?? 800;
228
+ const canvas = await this.createCanvas(width, height);
229
+ // Render
230
+ this.renderToCanvas(canvas, rigData, options, options?.showPivots);
231
+ // Save to file
232
+ const fs = await import('fs/promises');
233
+ const buffer = await canvas.toBuffer('image/png');
234
+ await fs.writeFile(outputPath, buffer);
235
+ }
236
+ /**
237
+ * Render to buffer (for streaming, HTTP responses, etc.)
238
+ */
239
+ async renderToBuffer(rigData, options, format = 'png') {
240
+ if (!this.isReady) {
241
+ await this.loadImages(rigData);
242
+ }
243
+ const width = options?.canvasWidth ?? 800;
244
+ const height = options?.canvasHeight ?? 800;
245
+ const canvas = await this.createCanvas(width, height);
246
+ this.renderToCanvas(canvas, rigData, options);
247
+ return await canvas.toBuffer(`image/${format}`);
248
+ }
249
+ /**
250
+ * Check if images are loaded
251
+ */
252
+ ready() {
253
+ return this.isReady;
254
+ }
255
+ /**
256
+ * Get loaded images (for custom rendering)
257
+ */
258
+ getLoadedImages() {
259
+ return this.loadedImages;
260
+ }
261
+ }
262
+ //# sourceMappingURL=skiaRenderer.js.map