@ewanc26/noise-avatar 0.1.0 → 0.2.1
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 +8 -12
- package/dist/index.cjs +24 -65
- package/dist/index.d.cts +13 -25
- package/dist/index.d.ts +13 -25
- package/dist/index.js +25 -62
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @ewanc26/noise-avatar
|
|
2
2
|
|
|
3
|
-
Deterministic value-noise avatar generation from a string seed. Zero runtime dependencies, works in any environment with a Canvas API (browsers, jsdom).
|
|
3
|
+
Deterministic value-noise avatar generation from a string seed. Thin opinionated wrapper around [`@ewanc26/noise`](../noise). Zero extra runtime dependencies, works in any environment with a Canvas API (browsers, jsdom).
|
|
4
4
|
|
|
5
5
|
Part of the [`@ewanc26/pkgs`](https://github.com/ewanc26/pkgs) monorepo.
|
|
6
6
|
|
|
@@ -36,7 +36,7 @@ renderNoiseAvatar(canvas, 'Alice|Subscription');
|
|
|
36
36
|
|
|
37
37
|
### `renderNoiseAvatar(canvas, seed, options?)`
|
|
38
38
|
|
|
39
|
-
Renders a deterministic value-noise texture onto `canvas
|
|
39
|
+
Renders a deterministic HSL value-noise texture onto `canvas` at `displaySize × displaySize` pixels.
|
|
40
40
|
|
|
41
41
|
| Option | Type | Default | Description |
|
|
42
42
|
|---|---|---|---|
|
|
@@ -48,19 +48,15 @@ Renders a deterministic value-noise texture onto `canvas`. Resizes the canvas to
|
|
|
48
48
|
|
|
49
49
|
### `noiseAvatarAction(canvas, seed, options?)`
|
|
50
50
|
|
|
51
|
-
Svelte action wrapper
|
|
51
|
+
Svelte action wrapper. Re-renders when `seed` changes via `update`.
|
|
52
52
|
|
|
53
|
-
###
|
|
53
|
+
### Re-exported primitives
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
`hash32`, `makePrng`, `hslToRgb`, `makeValueNoiseSampler`, and `generateNoisePixels`
|
|
56
|
+
are all re-exported from `@ewanc26/noise` for convenience.
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Seeded LCG PRNG — returns a `() => number` producing floats in `[0, 1)`.
|
|
60
|
-
|
|
61
|
-
### `hslToRgb(h, s, l)`
|
|
62
|
-
|
|
63
|
-
Converts HSL (components in `[0, 1]`) to an RGB triple (`[0, 255]` each).
|
|
58
|
+
For full control over dimensions, FBM octaves, and colour modes, use
|
|
59
|
+
[`@ewanc26/noise`](../noise) directly.
|
|
64
60
|
|
|
65
61
|
## Licence
|
|
66
62
|
|
package/dist/index.cjs
CHANGED
|
@@ -20,90 +20,49 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
generateNoisePixels: () => import_noise.generateNoisePixels,
|
|
24
|
+
hash32: () => import_noise.hash32,
|
|
25
|
+
hslToRgb: () => import_noise.hslToRgb,
|
|
26
|
+
makePrng: () => import_noise.makePrng,
|
|
27
|
+
makeValueNoiseSampler: () => import_noise.makeValueNoiseSampler,
|
|
26
28
|
noiseAvatarAction: () => noiseAvatarAction,
|
|
27
29
|
renderNoiseAvatar: () => renderNoiseAvatar
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(index_exports);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
s = Math.imul(1664525, s) + 1013904223 >>> 0;
|
|
39
|
-
return s / 4294967296;
|
|
32
|
+
var import_noise = require("@ewanc26/noise");
|
|
33
|
+
var import_noise2 = require("@ewanc26/noise");
|
|
34
|
+
function toRenderOptions(opts) {
|
|
35
|
+
const colorMode = {
|
|
36
|
+
type: "hsl",
|
|
37
|
+
hueRange: opts.hueRange,
|
|
38
|
+
saturationRange: opts.saturationRange,
|
|
39
|
+
lightnessRange: opts.lightnessRange
|
|
40
40
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const k = (n + h * 12) % 12;
|
|
46
|
-
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
41
|
+
return {
|
|
42
|
+
size: opts.displaySize ?? 64,
|
|
43
|
+
gridSize: opts.gridSize,
|
|
44
|
+
colorMode
|
|
47
45
|
};
|
|
48
|
-
return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
|
|
49
|
-
}
|
|
50
|
-
function smoothstep(t) {
|
|
51
|
-
return t * t * (3 - 2 * t);
|
|
52
46
|
}
|
|
53
47
|
function renderNoiseAvatar(canvas, seed, options = {}) {
|
|
54
|
-
|
|
55
|
-
gridSize = 5,
|
|
56
|
-
displaySize = 64,
|
|
57
|
-
hueRange = 60,
|
|
58
|
-
saturationRange = [45, 70],
|
|
59
|
-
lightnessRange = [40, 70]
|
|
60
|
-
} = options;
|
|
61
|
-
canvas.width = displaySize;
|
|
62
|
-
canvas.height = displaySize;
|
|
63
|
-
const ctx = canvas.getContext("2d");
|
|
64
|
-
if (!ctx) return;
|
|
65
|
-
const seedNum = hash32(seed);
|
|
66
|
-
const rng = makePrng(seedNum);
|
|
67
|
-
const baseHue = seedNum % 360;
|
|
68
|
-
const G = gridSize + 1;
|
|
69
|
-
const grid = Array.from({ length: G * G }, () => rng());
|
|
70
|
-
const gridVal = (gx, gy) => grid[gy * G + gx];
|
|
71
|
-
const imageData = ctx.createImageData(displaySize, displaySize);
|
|
72
|
-
for (let py = 0; py < displaySize; py++) {
|
|
73
|
-
for (let px = 0; px < displaySize; px++) {
|
|
74
|
-
const fx = px / displaySize * gridSize;
|
|
75
|
-
const fy = py / displaySize * gridSize;
|
|
76
|
-
const gx = Math.floor(fx);
|
|
77
|
-
const gy = Math.floor(fy);
|
|
78
|
-
const tx = smoothstep(fx - gx);
|
|
79
|
-
const ty = smoothstep(fy - gy);
|
|
80
|
-
const v = (1 - ty) * ((1 - tx) * gridVal(gx, gy) + tx * gridVal(gx + 1, gy)) + ty * ((1 - tx) * gridVal(gx, gy + 1) + tx * gridVal(gx + 1, gy + 1));
|
|
81
|
-
const hue = (baseHue + v * hueRange) % 360;
|
|
82
|
-
const sat = saturationRange[0] + v * (saturationRange[1] - saturationRange[0]);
|
|
83
|
-
const light = lightnessRange[0] + v * (lightnessRange[1] - lightnessRange[0]);
|
|
84
|
-
const [r, g, b] = hslToRgb(hue / 360, sat / 100, light / 100);
|
|
85
|
-
const i = (py * displaySize + px) * 4;
|
|
86
|
-
imageData.data[i] = r;
|
|
87
|
-
imageData.data[i + 1] = g;
|
|
88
|
-
imageData.data[i + 2] = b;
|
|
89
|
-
imageData.data[i + 3] = 255;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
ctx.putImageData(imageData, 0, 0);
|
|
48
|
+
(0, import_noise2.renderNoise)(canvas, seed, toRenderOptions(options));
|
|
93
49
|
}
|
|
94
|
-
function noiseAvatarAction(canvas, seed, options) {
|
|
95
|
-
|
|
50
|
+
function noiseAvatarAction(canvas, seed, options = {}) {
|
|
51
|
+
const params = { seed, ...toRenderOptions(options) };
|
|
52
|
+
const action = (0, import_noise2.noiseAction)(canvas, params);
|
|
96
53
|
return {
|
|
97
54
|
update(newSeed) {
|
|
98
|
-
|
|
55
|
+
action.update({ seed: newSeed, ...toRenderOptions(options) });
|
|
99
56
|
}
|
|
100
57
|
};
|
|
101
58
|
}
|
|
102
59
|
// Annotate the CommonJS export names for ESM import in node:
|
|
103
60
|
0 && (module.exports = {
|
|
61
|
+
generateNoisePixels,
|
|
104
62
|
hash32,
|
|
105
63
|
hslToRgb,
|
|
106
64
|
makePrng,
|
|
65
|
+
makeValueNoiseSampler,
|
|
107
66
|
noiseAvatarAction,
|
|
108
67
|
renderNoiseAvatar
|
|
109
68
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,31 +1,18 @@
|
|
|
1
|
+
export { generateNoisePixels, hash32, hslToRgb, makePrng, makeValueNoiseSampler } from '@ewanc26/noise';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @ewanc26/noise-avatar
|
|
3
5
|
*
|
|
4
|
-
*
|
|
6
|
+
* Opinionated avatar-flavoured wrapper around @ewanc26/noise.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* djb2 hash — returns an unsigned 32-bit integer for any string.
|
|
12
|
-
*/
|
|
13
|
-
declare function hash32(str: string): number;
|
|
14
|
-
/**
|
|
15
|
-
* Seeded LCG pseudo-random number generator.
|
|
16
|
-
* Returns a function that produces floats in [0, 1).
|
|
17
|
-
*/
|
|
18
|
-
declare function makePrng(seed: number): () => number;
|
|
19
|
-
/**
|
|
20
|
-
* Convert HSL (each component in [0, 1]) to an RGB triple ([0, 255] each).
|
|
21
|
-
*/
|
|
22
|
-
declare function hslToRgb(h: number, s: number, l: number): [number, number, number];
|
|
23
|
-
/**
|
|
24
|
-
* Options for `renderNoiseAvatar`.
|
|
8
|
+
* Provides a fixed HSL colour mode and square-canvas defaults so callers
|
|
9
|
+
* don't have to think about noise options for the common avatar use-case.
|
|
10
|
+
* The underlying primitives are re-exported for consumers that want them.
|
|
25
11
|
*/
|
|
12
|
+
|
|
26
13
|
interface NoiseAvatarOptions {
|
|
27
14
|
/**
|
|
28
|
-
* Side length of the
|
|
15
|
+
* Side length of the noise grid.
|
|
29
16
|
* @default 5
|
|
30
17
|
*/
|
|
31
18
|
gridSize?: number;
|
|
@@ -35,7 +22,7 @@ interface NoiseAvatarOptions {
|
|
|
35
22
|
*/
|
|
36
23
|
displaySize?: number;
|
|
37
24
|
/**
|
|
38
|
-
* Hue spread
|
|
25
|
+
* Hue spread in degrees around the seed-derived base hue.
|
|
39
26
|
* @default 60
|
|
40
27
|
*/
|
|
41
28
|
hueRange?: number;
|
|
@@ -55,11 +42,12 @@ interface NoiseAvatarOptions {
|
|
|
55
42
|
*
|
|
56
43
|
* @param canvas Target HTMLCanvasElement (will be resized to `displaySize`).
|
|
57
44
|
* @param seed Arbitrary string — same seed always produces the same image.
|
|
58
|
-
* @param options
|
|
45
|
+
* @param options Avatar rendering options.
|
|
59
46
|
*/
|
|
60
47
|
declare function renderNoiseAvatar(canvas: HTMLCanvasElement, seed: string, options?: NoiseAvatarOptions): void;
|
|
61
48
|
/**
|
|
62
|
-
*
|
|
49
|
+
* Svelte action that renders a noise avatar and updates reactively when
|
|
50
|
+
* `seed` changes.
|
|
63
51
|
*
|
|
64
52
|
* @example
|
|
65
53
|
* ```svelte
|
|
@@ -70,4 +58,4 @@ declare function noiseAvatarAction(canvas: HTMLCanvasElement, seed: string, opti
|
|
|
70
58
|
update(newSeed: string): void;
|
|
71
59
|
};
|
|
72
60
|
|
|
73
|
-
export { type NoiseAvatarOptions,
|
|
61
|
+
export { type NoiseAvatarOptions, noiseAvatarAction, renderNoiseAvatar };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,31 +1,18 @@
|
|
|
1
|
+
export { generateNoisePixels, hash32, hslToRgb, makePrng, makeValueNoiseSampler } from '@ewanc26/noise';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @ewanc26/noise-avatar
|
|
3
5
|
*
|
|
4
|
-
*
|
|
6
|
+
* Opinionated avatar-flavoured wrapper around @ewanc26/noise.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* djb2 hash — returns an unsigned 32-bit integer for any string.
|
|
12
|
-
*/
|
|
13
|
-
declare function hash32(str: string): number;
|
|
14
|
-
/**
|
|
15
|
-
* Seeded LCG pseudo-random number generator.
|
|
16
|
-
* Returns a function that produces floats in [0, 1).
|
|
17
|
-
*/
|
|
18
|
-
declare function makePrng(seed: number): () => number;
|
|
19
|
-
/**
|
|
20
|
-
* Convert HSL (each component in [0, 1]) to an RGB triple ([0, 255] each).
|
|
21
|
-
*/
|
|
22
|
-
declare function hslToRgb(h: number, s: number, l: number): [number, number, number];
|
|
23
|
-
/**
|
|
24
|
-
* Options for `renderNoiseAvatar`.
|
|
8
|
+
* Provides a fixed HSL colour mode and square-canvas defaults so callers
|
|
9
|
+
* don't have to think about noise options for the common avatar use-case.
|
|
10
|
+
* The underlying primitives are re-exported for consumers that want them.
|
|
25
11
|
*/
|
|
12
|
+
|
|
26
13
|
interface NoiseAvatarOptions {
|
|
27
14
|
/**
|
|
28
|
-
* Side length of the
|
|
15
|
+
* Side length of the noise grid.
|
|
29
16
|
* @default 5
|
|
30
17
|
*/
|
|
31
18
|
gridSize?: number;
|
|
@@ -35,7 +22,7 @@ interface NoiseAvatarOptions {
|
|
|
35
22
|
*/
|
|
36
23
|
displaySize?: number;
|
|
37
24
|
/**
|
|
38
|
-
* Hue spread
|
|
25
|
+
* Hue spread in degrees around the seed-derived base hue.
|
|
39
26
|
* @default 60
|
|
40
27
|
*/
|
|
41
28
|
hueRange?: number;
|
|
@@ -55,11 +42,12 @@ interface NoiseAvatarOptions {
|
|
|
55
42
|
*
|
|
56
43
|
* @param canvas Target HTMLCanvasElement (will be resized to `displaySize`).
|
|
57
44
|
* @param seed Arbitrary string — same seed always produces the same image.
|
|
58
|
-
* @param options
|
|
45
|
+
* @param options Avatar rendering options.
|
|
59
46
|
*/
|
|
60
47
|
declare function renderNoiseAvatar(canvas: HTMLCanvasElement, seed: string, options?: NoiseAvatarOptions): void;
|
|
61
48
|
/**
|
|
62
|
-
*
|
|
49
|
+
* Svelte action that renders a noise avatar and updates reactively when
|
|
50
|
+
* `seed` changes.
|
|
63
51
|
*
|
|
64
52
|
* @example
|
|
65
53
|
* ```svelte
|
|
@@ -70,4 +58,4 @@ declare function noiseAvatarAction(canvas: HTMLCanvasElement, seed: string, opti
|
|
|
70
58
|
update(newSeed: string): void;
|
|
71
59
|
};
|
|
72
60
|
|
|
73
|
-
export { type NoiseAvatarOptions,
|
|
61
|
+
export { type NoiseAvatarOptions, noiseAvatarAction, renderNoiseAvatar };
|
package/dist/index.js
CHANGED
|
@@ -1,80 +1,43 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
import {
|
|
3
|
+
hash32,
|
|
4
|
+
makePrng,
|
|
5
|
+
hslToRgb,
|
|
6
|
+
makeValueNoiseSampler,
|
|
7
|
+
generateNoisePixels
|
|
8
|
+
} from "@ewanc26/noise";
|
|
9
|
+
import { renderNoise, noiseAction } from "@ewanc26/noise";
|
|
10
|
+
function toRenderOptions(opts) {
|
|
11
|
+
const colorMode = {
|
|
12
|
+
type: "hsl",
|
|
13
|
+
hueRange: opts.hueRange,
|
|
14
|
+
saturationRange: opts.saturationRange,
|
|
15
|
+
lightnessRange: opts.lightnessRange
|
|
12
16
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const k = (n + h * 12) % 12;
|
|
18
|
-
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
|
|
17
|
+
return {
|
|
18
|
+
size: opts.displaySize ?? 64,
|
|
19
|
+
gridSize: opts.gridSize,
|
|
20
|
+
colorMode
|
|
19
21
|
};
|
|
20
|
-
return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
|
|
21
|
-
}
|
|
22
|
-
function smoothstep(t) {
|
|
23
|
-
return t * t * (3 - 2 * t);
|
|
24
22
|
}
|
|
25
23
|
function renderNoiseAvatar(canvas, seed, options = {}) {
|
|
26
|
-
|
|
27
|
-
gridSize = 5,
|
|
28
|
-
displaySize = 64,
|
|
29
|
-
hueRange = 60,
|
|
30
|
-
saturationRange = [45, 70],
|
|
31
|
-
lightnessRange = [40, 70]
|
|
32
|
-
} = options;
|
|
33
|
-
canvas.width = displaySize;
|
|
34
|
-
canvas.height = displaySize;
|
|
35
|
-
const ctx = canvas.getContext("2d");
|
|
36
|
-
if (!ctx) return;
|
|
37
|
-
const seedNum = hash32(seed);
|
|
38
|
-
const rng = makePrng(seedNum);
|
|
39
|
-
const baseHue = seedNum % 360;
|
|
40
|
-
const G = gridSize + 1;
|
|
41
|
-
const grid = Array.from({ length: G * G }, () => rng());
|
|
42
|
-
const gridVal = (gx, gy) => grid[gy * G + gx];
|
|
43
|
-
const imageData = ctx.createImageData(displaySize, displaySize);
|
|
44
|
-
for (let py = 0; py < displaySize; py++) {
|
|
45
|
-
for (let px = 0; px < displaySize; px++) {
|
|
46
|
-
const fx = px / displaySize * gridSize;
|
|
47
|
-
const fy = py / displaySize * gridSize;
|
|
48
|
-
const gx = Math.floor(fx);
|
|
49
|
-
const gy = Math.floor(fy);
|
|
50
|
-
const tx = smoothstep(fx - gx);
|
|
51
|
-
const ty = smoothstep(fy - gy);
|
|
52
|
-
const v = (1 - ty) * ((1 - tx) * gridVal(gx, gy) + tx * gridVal(gx + 1, gy)) + ty * ((1 - tx) * gridVal(gx, gy + 1) + tx * gridVal(gx + 1, gy + 1));
|
|
53
|
-
const hue = (baseHue + v * hueRange) % 360;
|
|
54
|
-
const sat = saturationRange[0] + v * (saturationRange[1] - saturationRange[0]);
|
|
55
|
-
const light = lightnessRange[0] + v * (lightnessRange[1] - lightnessRange[0]);
|
|
56
|
-
const [r, g, b] = hslToRgb(hue / 360, sat / 100, light / 100);
|
|
57
|
-
const i = (py * displaySize + px) * 4;
|
|
58
|
-
imageData.data[i] = r;
|
|
59
|
-
imageData.data[i + 1] = g;
|
|
60
|
-
imageData.data[i + 2] = b;
|
|
61
|
-
imageData.data[i + 3] = 255;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
ctx.putImageData(imageData, 0, 0);
|
|
24
|
+
renderNoise(canvas, seed, toRenderOptions(options));
|
|
65
25
|
}
|
|
66
|
-
function noiseAvatarAction(canvas, seed, options) {
|
|
67
|
-
|
|
26
|
+
function noiseAvatarAction(canvas, seed, options = {}) {
|
|
27
|
+
const params = { seed, ...toRenderOptions(options) };
|
|
28
|
+
const action = noiseAction(canvas, params);
|
|
68
29
|
return {
|
|
69
30
|
update(newSeed) {
|
|
70
|
-
|
|
31
|
+
action.update({ seed: newSeed, ...toRenderOptions(options) });
|
|
71
32
|
}
|
|
72
33
|
};
|
|
73
34
|
}
|
|
74
35
|
export {
|
|
36
|
+
generateNoisePixels,
|
|
75
37
|
hash32,
|
|
76
38
|
hslToRgb,
|
|
77
39
|
makePrng,
|
|
40
|
+
makeValueNoiseSampler,
|
|
78
41
|
noiseAvatarAction,
|
|
79
42
|
renderNoiseAvatar
|
|
80
43
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ewanc26/noise-avatar",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Deterministic value-noise avatar generation from a string seed. Zero dependencies, works in
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Deterministic value-noise avatar generation from a string seed. Thin wrapper around @ewanc26/noise. Zero extra dependencies, works in any environment with a Canvas API.",
|
|
5
5
|
"author": "Ewan Croft",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"type": "module",
|
|
@@ -26,8 +26,11 @@
|
|
|
26
26
|
"canvas",
|
|
27
27
|
"deterministic"
|
|
28
28
|
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@ewanc26/noise": "^0.1.1"
|
|
31
|
+
},
|
|
29
32
|
"devDependencies": {
|
|
30
|
-
"tsup": "^8.5.
|
|
33
|
+
"tsup": "^8.5.1",
|
|
31
34
|
"typescript": "^5.9.3"
|
|
32
35
|
},
|
|
33
36
|
"publishConfig": {
|