@awflow/avatar-export 1.0.0 → 1.1.0
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 +42 -4
- package/dist/animated.d.ts +7 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +17 -2
- package/dist/raster.d.ts +6 -2
- package/dist/sprite.d.ts +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -14,7 +14,14 @@ Every function returns a `Promise<Uint8Array>`. In Node, Bun and Deno the
|
|
|
14
14
|
wasm loads on first use; nothing to set up.
|
|
15
15
|
|
|
16
16
|
```ts
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
svgToPng,
|
|
19
|
+
toApng,
|
|
20
|
+
toGif,
|
|
21
|
+
toPng,
|
|
22
|
+
toSprite,
|
|
23
|
+
} from '@awflow/avatar-export'
|
|
24
|
+
import { frames } from '@awflow/avatar'
|
|
18
25
|
|
|
19
26
|
// a still PNG, 256 px square by default
|
|
20
27
|
const png = await toPng('Linh', { expression: 'happy' }, { size: 512 })
|
|
@@ -25,6 +32,20 @@ const apng = await toApng('Linh', { reaction: 'hop' }, { fps: 30, seconds: 3 })
|
|
|
25
32
|
// a GIF with one 256-colour palette, transparent where nothing is drawn
|
|
26
33
|
const gif = await toGif('Linh', { frame: 'none', activity: 'done' })
|
|
27
34
|
|
|
35
|
+
// a script: idle, then working from 1 s, a cheer at 3 s, idle again at 4 s
|
|
36
|
+
const story = await toGif('Linh', {
|
|
37
|
+
timeline: [
|
|
38
|
+
{ at: 1, activity: 'working' },
|
|
39
|
+
{ at: 3, reaction: 'celebrate' },
|
|
40
|
+
{ at: 4, activity: 'idle' },
|
|
41
|
+
],
|
|
42
|
+
}, { fps: 25, seconds: 5 })
|
|
43
|
+
|
|
44
|
+
// any SVG string, such as one frame of frames(), at a given width
|
|
45
|
+
const still = await svgToPng(frames('Linh', {}, { fps: 1, seconds: 1 })[0], {
|
|
46
|
+
size: 128,
|
|
47
|
+
})
|
|
48
|
+
|
|
28
49
|
// many avatars on one sheet: 3 columns of 64 px tiles, 4 px apart
|
|
29
50
|
const sheet = await toSprite(['Ada', 'Linh', 'Sam'], {}, {
|
|
30
51
|
size: 64,
|
|
@@ -40,7 +61,14 @@ Write the bytes anywhere: `await Bun.write('linh.png', png)`,
|
|
|
40
61
|
## Browsers
|
|
41
62
|
|
|
42
63
|
Load the wasm once with `init` before the first export. It takes a
|
|
43
|
-
`fetch()` response (or its promise), an `ArrayBuffer`, typed array or a `URL
|
|
64
|
+
`fetch()` response (or its promise), an `ArrayBuffer`, typed array or a `URL`.
|
|
65
|
+
The wasm file comes from `@resvg/resvg-wasm`, which this package depends on.
|
|
66
|
+
Importing it by path from your own code needs it as a direct dependency under
|
|
67
|
+
strict package managers (pnpm), at the exact version this package uses:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
npm install @resvg/resvg-wasm@2.6.2
|
|
71
|
+
```
|
|
44
72
|
|
|
45
73
|
```ts
|
|
46
74
|
import { init, toPng } from '@awflow/avatar-export'
|
|
@@ -58,13 +86,21 @@ return at once.
|
|
|
58
86
|
`LIMITS` is
|
|
59
87
|
`{ maxFrames: 600, maxSize: 1024, maxSprite: 400, maxSheet: 16777216 }`, with
|
|
60
88
|
`maxFrames` taken from the core's `FRAME_LIMITS` (1 to 120 fps, up to 60
|
|
61
|
-
seconds). `maxSheet` caps a sprite sheet's area
|
|
89
|
+
seconds). `maxSheet` caps a sprite sheet's area, and an `svgToPng` image's,
|
|
90
|
+
at 4096 by 4096 pixels. A `timeline` holds at most `maxFrames` cues. Frames are `round(fps * seconds)`, checked before anything is
|
|
62
91
|
drawn. A value out of range, or not a finite number, throws a `RangeError`
|
|
63
92
|
that names the limit.
|
|
64
93
|
|
|
65
94
|
GIF counts time in hundredths of a second and browsers slow down frames
|
|
66
95
|
shorter than 2/100 s, so keep GIFs at 50 fps or less. APNG timing is exact.
|
|
67
96
|
|
|
97
|
+
Rasterising is synchronous CPU work on the calling thread. On a recent laptop
|
|
98
|
+
a 1024 px, 60-frame export takes about 13 s as a GIF and 17 s as an APNG, and
|
|
99
|
+
a sheet of 256 tiles at 256 px about 11 s. The worst case the limits allow,
|
|
100
|
+
600 frames at 1024 px, is minutes of CPU. On a server, cap `size`, `fps` and
|
|
101
|
+
`seconds` well below `LIMITS` for untrusted input, and run exports in a worker
|
|
102
|
+
or a queue rather than on the request path.
|
|
103
|
+
|
|
68
104
|
## Docs
|
|
69
105
|
|
|
70
106
|
Full documentation: <https://github.com/awflow/agent-avatar#readme>
|
|
@@ -72,7 +108,9 @@ Full documentation: <https://github.com/awflow/agent-avatar#readme>
|
|
|
72
108
|
## Credits
|
|
73
109
|
|
|
74
110
|
Built on Peek by Doan Labs (MIT). Rasterising by
|
|
75
|
-
[resvg](https://github.com/linebender/resvg)
|
|
111
|
+
[resvg](https://github.com/linebender/resvg) through
|
|
112
|
+
[`@resvg/resvg-wasm`](https://www.npmjs.com/package/@resvg/resvg-wasm)
|
|
113
|
+
(MPL-2.0), GIF encoding by
|
|
76
114
|
[gifenc](https://github.com/mattdesl/gifenc), compression by
|
|
77
115
|
[fflate](https://github.com/101arrowz/fflate).
|
|
78
116
|
|
package/dist/animated.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { type AvatarOptions, type Reaction } from '@awflow/avatar';
|
|
1
|
+
import { type AvatarOptions, type Cue, type Reaction } from '@awflow/avatar';
|
|
2
|
+
/**
|
|
3
|
+
* What to animate: any avatar options (a registered `character` included),
|
|
4
|
+
* a `reaction` from the first frame, and a `timeline` of later cues: an
|
|
5
|
+
* activity, an expression or a reaction at a time in seconds.
|
|
6
|
+
*/
|
|
2
7
|
export type AnimatedOptions = AvatarOptions & {
|
|
3
8
|
reaction?: Reaction;
|
|
9
|
+
timeline?: readonly Cue[];
|
|
4
10
|
};
|
|
5
11
|
export type AnimationSettings = {
|
|
6
12
|
/** Side in pixels, 1 to LIMITS.maxSize. Default 256. */
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,14 @@ export type { WasmSource } from './raster.js';
|
|
|
6
6
|
export { init } from './raster.js';
|
|
7
7
|
export type { SpriteSettings } from './sprite.js';
|
|
8
8
|
export { toSprite } from './sprite.js';
|
|
9
|
+
/**
|
|
10
|
+
* Any avatar SVG (one frame of `frames()`, or a sheet of them) as a PNG,
|
|
11
|
+
* `size` pixels wide (default 256, at most LIMITS.maxSize). The height
|
|
12
|
+
* follows the SVG's aspect ratio.
|
|
13
|
+
*/
|
|
14
|
+
export declare function svgToPng(svg: string, o?: {
|
|
15
|
+
size?: number;
|
|
16
|
+
}): Promise<Uint8Array>;
|
|
9
17
|
/** The avatar as a still PNG, `size` pixels square (default 256). */
|
|
10
18
|
export declare function toPng(name: string, opts?: AvatarOptions, o?: {
|
|
11
19
|
size?: number;
|
package/dist/index.js
CHANGED
|
@@ -101,8 +101,13 @@ var options = (size) => ({
|
|
|
101
101
|
fitTo: { mode: "width", value: size },
|
|
102
102
|
font: { loadSystemFonts: false }
|
|
103
103
|
});
|
|
104
|
-
function png(svg, size) {
|
|
104
|
+
function png(svg, size, maxPixels) {
|
|
105
105
|
const resvg = new Resvg(svg, options(size));
|
|
106
|
+
const area = resvg.width > 0 ? size * size * (resvg.height / resvg.width) : 0;
|
|
107
|
+
if (maxPixels !== undefined && area > maxPixels) {
|
|
108
|
+
resvg.free();
|
|
109
|
+
throw new RangeError(`svgToPng(): ${Math.round(area)} pixels is above maxSheet ${maxPixels}`);
|
|
110
|
+
}
|
|
106
111
|
const image = resvg.render();
|
|
107
112
|
try {
|
|
108
113
|
return image.asPng();
|
|
@@ -134,7 +139,9 @@ function rgba(svg, size) {
|
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
// src/animated.ts
|
|
137
|
-
import {
|
|
142
|
+
import {
|
|
143
|
+
frames
|
|
144
|
+
} from "@awflow/avatar";
|
|
138
145
|
import * as gifenc from "gifenc";
|
|
139
146
|
|
|
140
147
|
// src/png.ts
|
|
@@ -392,6 +399,13 @@ async function toSprite(names, opts = {}, o = {}) {
|
|
|
392
399
|
}
|
|
393
400
|
|
|
394
401
|
// src/index.ts
|
|
402
|
+
async function svgToPng(svg, o = {}) {
|
|
403
|
+
const size = checkSize("svgToPng()", o.size);
|
|
404
|
+
if (typeof svg !== "string" || !svg.trimStart().startsWith("<svg"))
|
|
405
|
+
throw new TypeError("svgToPng(): svg must be an SVG string");
|
|
406
|
+
await ensure();
|
|
407
|
+
return png(svg, size, LIMITS.maxSheet);
|
|
408
|
+
}
|
|
395
409
|
async function toPng(name, opts = {}, o = {}) {
|
|
396
410
|
const size = checkSize("toPng()", o.size);
|
|
397
411
|
await ensure();
|
|
@@ -402,6 +416,7 @@ export {
|
|
|
402
416
|
toPng,
|
|
403
417
|
toGif,
|
|
404
418
|
toApng,
|
|
419
|
+
svgToPng,
|
|
405
420
|
init,
|
|
406
421
|
LIMITS,
|
|
407
422
|
DEFAULTS
|
package/dist/raster.d.ts
CHANGED
|
@@ -9,8 +9,12 @@ export type WasmSource = ArrayBuffer | ArrayBufferView | Response | URL | WebAss
|
|
|
9
9
|
export declare function init(wasm?: WasmSource | PromiseLike<WasmSource>): Promise<void>;
|
|
10
10
|
/** The export functions wait on this: init() with no argument when needed. */
|
|
11
11
|
export declare const ensure: () => Promise<void>;
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* One SVG as a PNG, size pixels wide, encoded by resvg. With `maxPixels`,
|
|
14
|
+
* an SVG whose height would take the image past that area is refused before
|
|
15
|
+
* any pixel is drawn.
|
|
16
|
+
*/
|
|
17
|
+
export declare function png(svg: string, size: number, maxPixels?: number): Uint8Array;
|
|
14
18
|
/**
|
|
15
19
|
* One SVG as straight (not premultiplied) RGBA pixels, size by size. resvg
|
|
16
20
|
* hands premultiplied pixels back; they are demultiplied the way its own PNG
|
package/dist/sprite.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ export type SpriteSettings = {
|
|
|
12
12
|
* (columns - 1) * gap, height likewise for the rows used. The same options
|
|
13
13
|
* apply to every tile. The sheet's area is at most LIMITS.maxSheet pixels. Name i sits at column i % columns, row i / columns.
|
|
14
14
|
*/
|
|
15
|
-
export declare function toSprite(names: string[], opts?: AvatarOptions, o?: SpriteSettings): Promise<Uint8Array>;
|
|
15
|
+
export declare function toSprite(names: readonly string[], opts?: AvatarOptions, o?: SpriteSettings): Promise<Uint8Array>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awflow/avatar-export",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"bin": {
|
|
@@ -21,13 +21,16 @@
|
|
|
21
21
|
"LICENSE"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@awflow/avatar": "^1.
|
|
24
|
+
"@awflow/avatar": "^1.1.0",
|
|
25
25
|
"@resvg/resvg-wasm": "2.6.2",
|
|
26
26
|
"fflate": "0.8.3",
|
|
27
27
|
"gifenc": "1.0.3"
|
|
28
28
|
},
|
|
29
29
|
"description": "Rasterise AWFlow avatars to PNG, APNG, GIF and sprite sheets, in Node and the browser.",
|
|
30
30
|
"license": "MIT",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
},
|
|
31
34
|
"author": "AWFlow",
|
|
32
35
|
"homepage": "https://github.com/awflow/agent-avatar",
|
|
33
36
|
"repository": {
|