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.
- package/README.md +81 -2
- package/dist/modules/adapters/p5Renderer.d.ts +71 -0
- package/dist/modules/adapters/p5Renderer.d.ts.map +1 -0
- package/dist/modules/adapters/p5Renderer.js +145 -0
- package/dist/modules/adapters/p5Renderer.js.map +1 -0
- package/dist/modules/adapters/skiaRenderer.d.ts +84 -0
- package/dist/modules/adapters/skiaRenderer.d.ts.map +1 -0
- package/dist/modules/adapters/skiaRenderer.js +262 -0
- package/dist/modules/adapters/skiaRenderer.js.map +1 -0
- package/dist/modules/eyeSystem.d.ts +34 -1
- package/dist/modules/eyeSystem.d.ts.map +1 -1
- package/dist/modules/eyeSystem.js +46 -20
- package/dist/modules/eyeSystem.js.map +1 -1
- package/dist/modules/imageLoad.d.ts +4 -1
- package/dist/modules/imageLoad.d.ts.map +1 -1
- package/dist/modules/imageLoad.js +65 -19
- package/dist/modules/imageLoad.js.map +1 -1
- package/dist/modules/mouthSystem.d.ts +15 -15
- package/dist/modules/mouthSystem.d.ts.map +1 -1
- package/dist/modules/mouthSystem.js +91 -52
- package/dist/modules/mouthSystem.js.map +1 -1
- package/dist/modules/renderRig.d.ts +1 -1
- package/dist/modules/renderRig.d.ts.map +1 -1
- package/dist/modules/renderRig.js +150 -49
- package/dist/modules/renderRig.js.map +1 -1
- package/dist/tests/test-joint-movement.d.ts +7 -0
- package/dist/tests/test-joint-movement.d.ts.map +1 -0
- package/dist/tests/test-joint-movement.js +323 -0
- package/dist/tests/test-joint-movement.js.map +1 -0
- package/dist/tests/test-skia.d.ts +12 -0
- package/dist/tests/test-skia.d.ts.map +1 -0
- package/dist/tests/test-skia.js +33 -0
- package/dist/tests/test-skia.js.map +1 -0
- package/dist/tests/test-visual-verification.d.ts +7 -0
- package/dist/tests/test-visual-verification.d.ts.map +1 -0
- package/dist/tests/test-visual-verification.js +180 -0
- package/dist/tests/test-visual-verification.js.map +1 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +21 -2
- package/dist/test-skia.d.ts +0 -15
- package/dist/test-skia.d.ts.map +0 -1
- package/dist/test-skia.js +0 -179
- 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
|