agent-avatars 1.0.0-rc.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/CHANGELOG.md +46 -0
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/SECURITY.md +21 -0
- package/dist/catalog-cache.cjs +31 -0
- package/dist/catalog-cache.mjs +29 -0
- package/dist/index.cjs +1399 -0
- package/dist/index.d.cts +197 -0
- package/dist/index.d.mts +197 -0
- package/dist/index.mjs +1402 -0
- package/dist/png-options.cjs +38 -0
- package/dist/png-options.mjs +38 -0
- package/dist/png.cjs +340 -0
- package/dist/png.d.cts +41 -0
- package/dist/png.d.mts +41 -0
- package/dist/png.mjs +340 -0
- package/dist/private.cjs +77 -0
- package/dist/private.d.cts +9 -0
- package/dist/private.d.mts +9 -0
- package/dist/private.mjs +82 -0
- package/dist/react.cjs +32 -0
- package/dist/react.d.cts +13 -0
- package/dist/react.d.mts +13 -0
- package/dist/react.mjs +29 -0
- package/dist/render-descriptor.cjs +43 -0
- package/dist/render-descriptor.mjs +40 -0
- package/dist/visual-distance.cjs +168 -0
- package/dist/visual-distance.mjs +168 -0
- package/examples/preview.png +0 -0
- package/package.json +148 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const DEFAULT_SUPERSAMPLE = 4;
|
|
2
|
+
const MAX_SIZE = 4096;
|
|
3
|
+
const BYTES_PER_PIXEL = 4;
|
|
4
|
+
const MAX_RENDER_BYTES = 64 * 1024 * 1024;
|
|
5
|
+
const MAX_RENDER_WIDTH = Math.floor(Math.sqrt(MAX_RENDER_BYTES / BYTES_PER_PIXEL));
|
|
6
|
+
|
|
7
|
+
function normalizePngSize(value) {
|
|
8
|
+
const number = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
|
|
9
|
+
if (!Number.isInteger(number) || number <= 0 || number > MAX_SIZE) {
|
|
10
|
+
throw new TypeError("PNG size must be an integer in [1, " + MAX_SIZE + "].");
|
|
11
|
+
}
|
|
12
|
+
return number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeSupersample(value, targetSize) {
|
|
16
|
+
const usesDefault = value === undefined || value === null;
|
|
17
|
+
const safeDefault = Math.max(1, Math.min(DEFAULT_SUPERSAMPLE, Math.floor(MAX_RENDER_WIDTH / targetSize)));
|
|
18
|
+
const number = Number(usesDefault ? safeDefault : value);
|
|
19
|
+
if (!Number.isInteger(number) || number < 1 || number > 8) {
|
|
20
|
+
throw new TypeError("supersample must be an integer in [1, 8].");
|
|
21
|
+
}
|
|
22
|
+
const renderWidth = targetSize * number;
|
|
23
|
+
const renderBytes = renderWidth * renderWidth * BYTES_PER_PIXEL;
|
|
24
|
+
if (renderBytes > MAX_RENDER_BYTES) {
|
|
25
|
+
throw new RangeError(
|
|
26
|
+
"PNG render for size " + targetSize + " and supersample " + number + " exceeds the 64 MiB buffer budget."
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
DEFAULT_SUPERSAMPLE,
|
|
34
|
+
MAX_SIZE,
|
|
35
|
+
MAX_RENDER_BYTES,
|
|
36
|
+
normalizePngSize,
|
|
37
|
+
normalizeSupersample,
|
|
38
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const DEFAULT_SUPERSAMPLE = 4;
|
|
2
|
+
const MAX_SIZE = 4096;
|
|
3
|
+
const BYTES_PER_PIXEL = 4;
|
|
4
|
+
const MAX_RENDER_BYTES = 64 * 1024 * 1024;
|
|
5
|
+
const MAX_RENDER_WIDTH = Math.floor(Math.sqrt(MAX_RENDER_BYTES / BYTES_PER_PIXEL));
|
|
6
|
+
|
|
7
|
+
function normalizePngSize(value) {
|
|
8
|
+
const number = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
|
|
9
|
+
if (!Number.isInteger(number) || number <= 0 || number > MAX_SIZE) {
|
|
10
|
+
throw new TypeError("PNG size must be an integer in [1, " + MAX_SIZE + "].");
|
|
11
|
+
}
|
|
12
|
+
return number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeSupersample(value, targetSize) {
|
|
16
|
+
const usesDefault = value === undefined || value === null;
|
|
17
|
+
const safeDefault = Math.max(1, Math.min(DEFAULT_SUPERSAMPLE, Math.floor(MAX_RENDER_WIDTH / targetSize)));
|
|
18
|
+
const number = Number(usesDefault ? safeDefault : value);
|
|
19
|
+
if (!Number.isInteger(number) || number < 1 || number > 8) {
|
|
20
|
+
throw new TypeError("supersample must be an integer in [1, 8].");
|
|
21
|
+
}
|
|
22
|
+
const renderWidth = targetSize * number;
|
|
23
|
+
const renderBytes = renderWidth * renderWidth * BYTES_PER_PIXEL;
|
|
24
|
+
if (renderBytes > MAX_RENDER_BYTES) {
|
|
25
|
+
throw new RangeError(
|
|
26
|
+
"PNG render for size " + targetSize + " and supersample " + number + " exceeds the 64 MiB buffer budget."
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
DEFAULT_SUPERSAMPLE,
|
|
34
|
+
MAX_SIZE,
|
|
35
|
+
MAX_RENDER_BYTES,
|
|
36
|
+
normalizePngSize,
|
|
37
|
+
normalizeSupersample,
|
|
38
|
+
};
|
package/dist/png.cjs
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
const { deflateSync } = require("node:zlib");
|
|
2
|
+
const { mkdirSync, writeFileSync } = require("node:fs");
|
|
3
|
+
const { join, resolve } = require("node:path");
|
|
4
|
+
const { createAvatarDescriptor } = require("./index.cjs");
|
|
5
|
+
const { snapshotRenderableDescriptor } = require("./render-descriptor.cjs");
|
|
6
|
+
const { normalizePngSize, normalizeSupersample } = require("./png-options.cjs");
|
|
7
|
+
|
|
8
|
+
const PLATFORM_PNG_SIZES = Object.freeze([32, 64, 192, 200]);
|
|
9
|
+
const GRID_W = 5;
|
|
10
|
+
const GRID_H = 4;
|
|
11
|
+
const CELL = 10;
|
|
12
|
+
const GLYPH_X = 39;
|
|
13
|
+
const GLYPH_Y = 44;
|
|
14
|
+
const MAX_PNG_SET_SIZES = 64;
|
|
15
|
+
const MAX_PNG_SET_RENDER_PIXELS = 16_777_216;
|
|
16
|
+
|
|
17
|
+
const CRC_TABLE = (() => {
|
|
18
|
+
const table = new Uint32Array(256);
|
|
19
|
+
for (let index = 0; index < 256; index++) {
|
|
20
|
+
let value = index;
|
|
21
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
22
|
+
value = (value & 1) ? (0xedb88320 ^ (value >>> 1)) : (value >>> 1);
|
|
23
|
+
}
|
|
24
|
+
table[index] = value >>> 0;
|
|
25
|
+
}
|
|
26
|
+
return table;
|
|
27
|
+
})();
|
|
28
|
+
|
|
29
|
+
function parseColor(hex) {
|
|
30
|
+
if (typeof hex !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(hex)) {
|
|
31
|
+
throw new TypeError(`Invalid render color: ${hex}`);
|
|
32
|
+
}
|
|
33
|
+
return [
|
|
34
|
+
Number.parseInt(hex.slice(1, 3), 16),
|
|
35
|
+
Number.parseInt(hex.slice(3, 5), 16),
|
|
36
|
+
Number.parseInt(hex.slice(5, 7), 16),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function blendPixel(buffer, width, x, y, color, alpha = 1) {
|
|
41
|
+
if (x < 0 || y < 0 || x >= width || y >= width || alpha <= 0) return;
|
|
42
|
+
const offset = (y * width + x) * 4;
|
|
43
|
+
const sourceAlpha = Math.max(0, Math.min(1, alpha));
|
|
44
|
+
const destinationAlpha = buffer[offset + 3] / 255;
|
|
45
|
+
const outputAlpha = sourceAlpha + destinationAlpha * (1 - sourceAlpha);
|
|
46
|
+
if (outputAlpha <= 0) return;
|
|
47
|
+
|
|
48
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
49
|
+
const source = color[channel] / 255;
|
|
50
|
+
const destination = buffer[offset + channel] / 255;
|
|
51
|
+
const output = (source * sourceAlpha + destination * destinationAlpha * (1 - sourceAlpha)) / outputAlpha;
|
|
52
|
+
buffer[offset + channel] = Math.round(output * 255);
|
|
53
|
+
}
|
|
54
|
+
buffer[offset + 3] = Math.round(outputAlpha * 255);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function fillCircle(buffer, width, scale, centerX, centerY, radius, color, alpha = 1) {
|
|
58
|
+
const cx = centerX * scale;
|
|
59
|
+
const cy = centerY * scale;
|
|
60
|
+
const r = radius * scale;
|
|
61
|
+
const minX = Math.max(0, Math.floor(cx - r));
|
|
62
|
+
const maxX = Math.min(width - 1, Math.ceil(cx + r));
|
|
63
|
+
const minY = Math.max(0, Math.floor(cy - r));
|
|
64
|
+
const maxY = Math.min(width - 1, Math.ceil(cy + r));
|
|
65
|
+
const r2 = r * r;
|
|
66
|
+
|
|
67
|
+
for (let y = minY; y <= maxY; y++) {
|
|
68
|
+
const py = y + 0.5 - cy;
|
|
69
|
+
for (let x = minX; x <= maxX; x++) {
|
|
70
|
+
const px = x + 0.5 - cx;
|
|
71
|
+
if (px * px + py * py <= r2) blendPixel(buffer, width, x, y, color, alpha);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function pointInsideRoundedCell(px, py, x, y, size, radius, corners) {
|
|
77
|
+
if (px < x || px > x + size || py < y || py > y + size) return false;
|
|
78
|
+
const [topLeft, topRight, bottomRight, bottomLeft] = corners;
|
|
79
|
+
|
|
80
|
+
if (topLeft && px < x + radius && py < y + radius) {
|
|
81
|
+
const dx = px - (x + radius);
|
|
82
|
+
const dy = py - (y + radius);
|
|
83
|
+
return dx * dx + dy * dy <= radius * radius;
|
|
84
|
+
}
|
|
85
|
+
if (topRight && px > x + size - radius && py < y + radius) {
|
|
86
|
+
const dx = px - (x + size - radius);
|
|
87
|
+
const dy = py - (y + radius);
|
|
88
|
+
return dx * dx + dy * dy <= radius * radius;
|
|
89
|
+
}
|
|
90
|
+
if (bottomRight && px > x + size - radius && py > y + size - radius) {
|
|
91
|
+
const dx = px - (x + size - radius);
|
|
92
|
+
const dy = py - (y + size - radius);
|
|
93
|
+
return dx * dx + dy * dy <= radius * radius;
|
|
94
|
+
}
|
|
95
|
+
if (bottomLeft && px < x + radius && py > y + size - radius) {
|
|
96
|
+
const dx = px - (x + radius);
|
|
97
|
+
const dy = py - (y + size - radius);
|
|
98
|
+
return dx * dx + dy * dy <= radius * radius;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function fillRoundedCell(buffer, width, scale, x, y, size, radius, corners, color) {
|
|
104
|
+
const scaledX = x * scale;
|
|
105
|
+
const scaledY = y * scale;
|
|
106
|
+
const scaledSize = size * scale;
|
|
107
|
+
const scaledRadius = radius * scale;
|
|
108
|
+
const minX = Math.max(0, Math.floor(scaledX));
|
|
109
|
+
const maxX = Math.min(width - 1, Math.ceil(scaledX + scaledSize));
|
|
110
|
+
const minY = Math.max(0, Math.floor(scaledY));
|
|
111
|
+
const maxY = Math.min(width - 1, Math.ceil(scaledY + scaledSize));
|
|
112
|
+
|
|
113
|
+
for (let py = minY; py <= maxY; py++) {
|
|
114
|
+
for (let px = minX; px <= maxX; px++) {
|
|
115
|
+
if (pointInsideRoundedCell(px + 0.5, py + 0.5, scaledX, scaledY, scaledSize, scaledRadius, corners)) {
|
|
116
|
+
blendPixel(buffer, width, px, py, color, 1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function cellOn(rows, x, y) {
|
|
123
|
+
return y >= 0 && y < GRID_H && x >= 0 && x < GRID_W && ((rows[y] >>> (GRID_W - 1 - x)) & 1) === 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function renderHighResolution(descriptor, targetSize, supersample) {
|
|
127
|
+
const width = targetSize * supersample;
|
|
128
|
+
const designScale = width / 128;
|
|
129
|
+
const buffer = new Uint8Array(width * width * 4);
|
|
130
|
+
const background = parseColor(descriptor.colors.background);
|
|
131
|
+
const foreground = parseColor(descriptor.colors.foreground);
|
|
132
|
+
fillCircle(buffer, width, designScale, 64, 64, 48, background, 1);
|
|
133
|
+
|
|
134
|
+
const cellRadius = 2;
|
|
135
|
+
for (let y = 0; y < GRID_H; y++) {
|
|
136
|
+
for (let x = 0; x < GRID_W; x++) {
|
|
137
|
+
if (!cellOn(descriptor.rows, x, y)) continue;
|
|
138
|
+
const up = cellOn(descriptor.rows, x, y - 1);
|
|
139
|
+
const down = cellOn(descriptor.rows, x, y + 1);
|
|
140
|
+
const left = cellOn(descriptor.rows, x - 1, y);
|
|
141
|
+
const right = cellOn(descriptor.rows, x + 1, y);
|
|
142
|
+
fillRoundedCell(
|
|
143
|
+
buffer,
|
|
144
|
+
width,
|
|
145
|
+
designScale,
|
|
146
|
+
GLYPH_X + x * CELL,
|
|
147
|
+
GLYPH_Y + y * CELL,
|
|
148
|
+
CELL,
|
|
149
|
+
cellRadius,
|
|
150
|
+
[!up && !left, !up && !right, !down && !right, !down && !left],
|
|
151
|
+
foreground
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { buffer, width };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function downsample(source, sourceWidth, targetSize, supersample) {
|
|
160
|
+
if (supersample === 1) return source;
|
|
161
|
+
const output = new Uint8Array(targetSize * targetSize * 4);
|
|
162
|
+
const samples = supersample * supersample;
|
|
163
|
+
|
|
164
|
+
for (let y = 0; y < targetSize; y++) {
|
|
165
|
+
for (let x = 0; x < targetSize; x++) {
|
|
166
|
+
let sumAlpha = 0;
|
|
167
|
+
let sumRed = 0;
|
|
168
|
+
let sumGreen = 0;
|
|
169
|
+
let sumBlue = 0;
|
|
170
|
+
for (let sampleY = 0; sampleY < supersample; sampleY++) {
|
|
171
|
+
for (let sampleX = 0; sampleX < supersample; sampleX++) {
|
|
172
|
+
const sourceX = x * supersample + sampleX;
|
|
173
|
+
const sourceY = y * supersample + sampleY;
|
|
174
|
+
const sourceOffset = (sourceY * sourceWidth + sourceX) * 4;
|
|
175
|
+
const alpha = source[sourceOffset + 3] / 255;
|
|
176
|
+
sumAlpha += alpha;
|
|
177
|
+
sumRed += source[sourceOffset] * alpha;
|
|
178
|
+
sumGreen += source[sourceOffset + 1] * alpha;
|
|
179
|
+
sumBlue += source[sourceOffset + 2] * alpha;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const outputOffset = (y * targetSize + x) * 4;
|
|
184
|
+
const averageAlpha = sumAlpha / samples;
|
|
185
|
+
if (sumAlpha > 0) {
|
|
186
|
+
output[outputOffset] = Math.round(sumRed / sumAlpha);
|
|
187
|
+
output[outputOffset + 1] = Math.round(sumGreen / sumAlpha);
|
|
188
|
+
output[outputOffset + 2] = Math.round(sumBlue / sumAlpha);
|
|
189
|
+
}
|
|
190
|
+
output[outputOffset + 3] = Math.round(averageAlpha * 255);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return output;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function crc32(buffer) {
|
|
198
|
+
let crc = 0xffffffff;
|
|
199
|
+
for (const byte of buffer) crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
200
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function pngChunk(type, data = Buffer.alloc(0)) {
|
|
204
|
+
const typeBuffer = Buffer.from(type, "ascii");
|
|
205
|
+
const length = Buffer.alloc(4);
|
|
206
|
+
length.writeUInt32BE(data.length, 0);
|
|
207
|
+
const checksum = Buffer.alloc(4);
|
|
208
|
+
checksum.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])), 0);
|
|
209
|
+
return Buffer.concat([length, typeBuffer, data, checksum]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function encodePng(rgba, width, height) {
|
|
213
|
+
const raw = Buffer.alloc((width * 4 + 1) * height);
|
|
214
|
+
for (let y = 0; y < height; y++) {
|
|
215
|
+
const rowOffset = y * (width * 4 + 1);
|
|
216
|
+
raw[rowOffset] = 0; // PNG filter: None
|
|
217
|
+
Buffer.from(rgba.buffer, rgba.byteOffset + y * width * 4, width * 4).copy(raw, rowOffset + 1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const header = Buffer.alloc(13);
|
|
221
|
+
header.writeUInt32BE(width, 0);
|
|
222
|
+
header.writeUInt32BE(height, 4);
|
|
223
|
+
header[8] = 8; // bit depth
|
|
224
|
+
header[9] = 6; // RGBA
|
|
225
|
+
header[10] = 0;
|
|
226
|
+
header[11] = 0;
|
|
227
|
+
header[12] = 0;
|
|
228
|
+
|
|
229
|
+
return Buffer.concat([
|
|
230
|
+
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
231
|
+
pngChunk("IHDR", header),
|
|
232
|
+
pngChunk("IDAT", deflateSync(raw, { level: 9 })),
|
|
233
|
+
pngChunk("IEND"),
|
|
234
|
+
]);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function createAvatarPngFromDescriptor(descriptor, size = 96, options = {}) {
|
|
238
|
+
const snapshot = snapshotRenderableDescriptor(descriptor);
|
|
239
|
+
const targetSize = normalizePngSize(size);
|
|
240
|
+
const supersample = normalizeSupersample(options.supersample, targetSize);
|
|
241
|
+
const highResolution = renderHighResolution(snapshot, targetSize, supersample);
|
|
242
|
+
const rgba = downsample(highResolution.buffer, highResolution.width, targetSize, supersample);
|
|
243
|
+
return encodePng(rgba, targetSize, targetSize);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function optionsFromArgs(sizeOrOptions, explicitOptions) {
|
|
247
|
+
if (typeof sizeOrOptions === "object" && sizeOrOptions !== null) {
|
|
248
|
+
return {
|
|
249
|
+
size: sizeOrOptions.size ?? 96,
|
|
250
|
+
options: { ...sizeOrOptions },
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
size: sizeOrOptions ?? 96,
|
|
255
|
+
options: { ...(explicitOptions ?? {}) },
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function createAvatarPng(seed, sizeOrOptions = 96, explicitOptions = {}) {
|
|
260
|
+
const { size, options } = optionsFromArgs(sizeOrOptions, explicitOptions);
|
|
261
|
+
const descriptor = createAvatarDescriptor(seed, options);
|
|
262
|
+
return createAvatarPngFromDescriptor(descriptor, size, options);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function avatarPngDataUri(seed, sizeOrOptions = 96, explicitOptions = {}) {
|
|
266
|
+
return `data:image/png;base64,${createAvatarPng(seed, sizeOrOptions, explicitOptions).toString("base64")}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function normalizeSizes(value, supersampleValue) {
|
|
270
|
+
const sizes = value ?? PLATFORM_PNG_SIZES;
|
|
271
|
+
if (!Array.isArray(sizes) || sizes.length === 0) throw new TypeError("sizes must be a non-empty array.");
|
|
272
|
+
if (sizes.length > MAX_PNG_SET_SIZES) {
|
|
273
|
+
throw new RangeError(`sizes must contain at most ${MAX_PNG_SET_SIZES} entries.`);
|
|
274
|
+
}
|
|
275
|
+
const normalized = sizes.map(normalizePngSize);
|
|
276
|
+
if (new Set(normalized).size !== normalized.length) throw new TypeError("sizes must not contain duplicates.");
|
|
277
|
+
let renderPixels = 0;
|
|
278
|
+
for (const size of normalized) {
|
|
279
|
+
const supersample = normalizeSupersample(supersampleValue, size);
|
|
280
|
+
const renderWidth = size * supersample;
|
|
281
|
+
renderPixels += renderWidth * renderWidth;
|
|
282
|
+
if (renderPixels > MAX_PNG_SET_RENDER_PIXELS) {
|
|
283
|
+
throw new RangeError(`PNG set exceeds the ${MAX_PNG_SET_RENDER_PIXELS} render-pixel budget.`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return normalized;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function createAvatarPngSet(seed, options = {}) {
|
|
290
|
+
const sizes = normalizeSizes(options.sizes, options.supersample);
|
|
291
|
+
const descriptor = createAvatarDescriptor(seed, options);
|
|
292
|
+
const files = {};
|
|
293
|
+
for (const size of sizes) files[size] = createAvatarPngFromDescriptor(descriptor, size, options);
|
|
294
|
+
return { descriptor, files };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function writeAvatarPngSet(seed, directory, options = {}) {
|
|
298
|
+
if (typeof directory !== "string" || directory.trim() === "") {
|
|
299
|
+
throw new TypeError("directory must be a non-empty path string.");
|
|
300
|
+
}
|
|
301
|
+
const baseName = String(options.baseName ?? "avatar");
|
|
302
|
+
if (!/^[A-Za-z0-9._-]{1,96}$/.test(baseName)) {
|
|
303
|
+
throw new TypeError("baseName may contain only letters, digits, periods, underscores, and hyphens.");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const outputDirectory = resolve(directory);
|
|
307
|
+
mkdirSync(outputDirectory, { recursive: true });
|
|
308
|
+
const generated = createAvatarPngSet(seed, options);
|
|
309
|
+
const paths = {};
|
|
310
|
+
|
|
311
|
+
for (const [size, data] of Object.entries(generated.files)) {
|
|
312
|
+
const path = join(outputDirectory, `${baseName}-${size}.png`);
|
|
313
|
+
writeFileSync(path, data);
|
|
314
|
+
paths[size] = path;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const manifest = {
|
|
318
|
+
schema: "deterministic-agent-avatars-png-export/v1",
|
|
319
|
+
styleVersion: generated.descriptor.styleVersion,
|
|
320
|
+
identityKey: generated.descriptor.identityKey,
|
|
321
|
+
signature: generated.descriptor.signature,
|
|
322
|
+
namespace: generated.descriptor.namespace,
|
|
323
|
+
theme: generated.descriptor.theme,
|
|
324
|
+
paletteId: generated.descriptor.paletteId,
|
|
325
|
+
files: Object.fromEntries(Object.entries(paths).map(([size, path]) => [size, path.split(/[\\/]/).pop()])),
|
|
326
|
+
};
|
|
327
|
+
const manifestPath = join(outputDirectory, `${baseName}-manifest.json`);
|
|
328
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
329
|
+
|
|
330
|
+
return { ...generated, paths, manifestPath, manifest };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
module.exports = {
|
|
334
|
+
PLATFORM_PNG_SIZES,
|
|
335
|
+
createAvatarPngFromDescriptor,
|
|
336
|
+
createAvatarPng,
|
|
337
|
+
avatarPngDataUri,
|
|
338
|
+
createAvatarPngSet,
|
|
339
|
+
writeAvatarPngSet,
|
|
340
|
+
};
|
package/dist/png.d.cts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AvatarDescriptor, AvatarOptions } from "./index.cjs";
|
|
2
|
+
|
|
3
|
+
export interface PngRenderOptions {
|
|
4
|
+
supersample?: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface PngOptions extends AvatarOptions, PngRenderOptions {}
|
|
8
|
+
|
|
9
|
+
export interface PngSetOptions extends PngOptions {
|
|
10
|
+
sizes?: readonly number[];
|
|
11
|
+
baseName?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AvatarPngSet {
|
|
15
|
+
descriptor: AvatarDescriptor;
|
|
16
|
+
files: Record<number, Uint8Array>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WrittenAvatarPngSet extends AvatarPngSet {
|
|
20
|
+
paths: Record<string, string>;
|
|
21
|
+
manifestPath: string;
|
|
22
|
+
manifest: {
|
|
23
|
+
schema: "deterministic-agent-avatars-png-export/v1";
|
|
24
|
+
styleVersion: string;
|
|
25
|
+
identityKey: string;
|
|
26
|
+
signature: string;
|
|
27
|
+
namespace: string;
|
|
28
|
+
theme: string;
|
|
29
|
+
paletteId: string;
|
|
30
|
+
files: Record<string, string>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const PLATFORM_PNG_SIZES: readonly [32, 64, 192, 200];
|
|
35
|
+
export function createAvatarPngFromDescriptor(descriptor: AvatarDescriptor, size?: number, options?: PngRenderOptions): Uint8Array;
|
|
36
|
+
export function createAvatarPng(seed: unknown, size?: number, options?: PngOptions): Uint8Array;
|
|
37
|
+
export function createAvatarPng(seed: unknown, options?: PngOptions): Uint8Array;
|
|
38
|
+
export function avatarPngDataUri(seed: unknown, size?: number, options?: PngOptions): string;
|
|
39
|
+
export function avatarPngDataUri(seed: unknown, options?: PngOptions): string;
|
|
40
|
+
export function createAvatarPngSet(seed: unknown, options?: PngSetOptions): AvatarPngSet;
|
|
41
|
+
export function writeAvatarPngSet(seed: unknown, directory: string, options?: PngSetOptions): WrittenAvatarPngSet;
|
package/dist/png.d.mts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AvatarDescriptor, AvatarOptions } from "./index.mjs";
|
|
2
|
+
|
|
3
|
+
export interface PngRenderOptions {
|
|
4
|
+
supersample?: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface PngOptions extends AvatarOptions, PngRenderOptions {}
|
|
8
|
+
|
|
9
|
+
export interface PngSetOptions extends PngOptions {
|
|
10
|
+
sizes?: readonly number[];
|
|
11
|
+
baseName?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AvatarPngSet {
|
|
15
|
+
descriptor: AvatarDescriptor;
|
|
16
|
+
files: Record<number, Uint8Array>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WrittenAvatarPngSet extends AvatarPngSet {
|
|
20
|
+
paths: Record<string, string>;
|
|
21
|
+
manifestPath: string;
|
|
22
|
+
manifest: {
|
|
23
|
+
schema: "deterministic-agent-avatars-png-export/v1";
|
|
24
|
+
styleVersion: string;
|
|
25
|
+
identityKey: string;
|
|
26
|
+
signature: string;
|
|
27
|
+
namespace: string;
|
|
28
|
+
theme: string;
|
|
29
|
+
paletteId: string;
|
|
30
|
+
files: Record<string, string>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const PLATFORM_PNG_SIZES: readonly [32, 64, 192, 200];
|
|
35
|
+
export function createAvatarPngFromDescriptor(descriptor: AvatarDescriptor, size?: number, options?: PngRenderOptions): Uint8Array;
|
|
36
|
+
export function createAvatarPng(seed: unknown, size?: number, options?: PngOptions): Uint8Array;
|
|
37
|
+
export function createAvatarPng(seed: unknown, options?: PngOptions): Uint8Array;
|
|
38
|
+
export function avatarPngDataUri(seed: unknown, size?: number, options?: PngOptions): string;
|
|
39
|
+
export function avatarPngDataUri(seed: unknown, options?: PngOptions): string;
|
|
40
|
+
export function createAvatarPngSet(seed: unknown, options?: PngSetOptions): AvatarPngSet;
|
|
41
|
+
export function writeAvatarPngSet(seed: unknown, directory: string, options?: PngSetOptions): WrittenAvatarPngSet;
|