image-to-pixel 3.0.0 → 3.0.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.
Files changed (68) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +69 -69
  3. package/dist/editor.css.map +1 -1
  4. package/dist/editor.esm.js +1 -1
  5. package/dist/editor.esm.js.map +1 -1
  6. package/dist/fonts/README.md +6 -6
  7. package/dist/fonts/geist-LICENSE.txt +93 -93
  8. package/dist/image-to-pixel.bundle.esm.js +13 -13
  9. package/dist/image-to-pixel.bundle.esm.js.map +2 -2
  10. package/dist/image-to-pixel.cjs +2 -2
  11. package/dist/image-to-pixel.cjs.map +2 -2
  12. package/dist/image-to-pixel.esm.js +2 -2
  13. package/dist/image-to-pixel.esm.js.map +2 -2
  14. package/dist/image-to-pixel.js +2 -2
  15. package/dist/image-to-pixel.js.map +2 -2
  16. package/dist/image-to-pixel.min.js +11 -11
  17. package/dist/image-to-pixel.min.js.map +2 -2
  18. package/dist/video.esm.js +1 -1
  19. package/dist/video.esm.js.map +1 -1
  20. package/lib/core/color.js +69 -69
  21. package/lib/core/cpu/pipeline.js +21 -21
  22. package/lib/core/cpu/stages.js +227 -227
  23. package/lib/core/dither/bluenoise-data.js +3 -3
  24. package/lib/core/dither/kernels.js +27 -27
  25. package/lib/core/dither/matrices.js +95 -95
  26. package/lib/core/engine.js +158 -158
  27. package/lib/core/export/canvas.js +22 -22
  28. package/lib/core/export/crc32.js +15 -15
  29. package/lib/core/export/gif.js +153 -153
  30. package/lib/core/export/png.js +94 -94
  31. package/lib/core/export/scale.js +103 -103
  32. package/lib/core/export/sheet.js +95 -95
  33. package/lib/core/export/zip.js +55 -55
  34. package/lib/core/gpu/backend.js +159 -159
  35. package/lib/core/gpu/shaders.js +258 -258
  36. package/lib/core/palette/builtin.js +36 -36
  37. package/lib/core/palette/extract.js +69 -69
  38. package/lib/core/palette/lospec.js +24 -24
  39. package/lib/core/palette/parse.js +112 -112
  40. package/lib/core/plan.js +58 -58
  41. package/lib/core/presets.js +42 -42
  42. package/lib/core/settings.js +274 -274
  43. package/lib/core/source.js +89 -89
  44. package/lib/core/video/frames.js +144 -144
  45. package/lib/core/video/mux-mp4.js +131 -131
  46. package/lib/core/video/mux-webm.js +127 -127
  47. package/lib/editor/controls.js +139 -139
  48. package/lib/editor/dom.js +65 -65
  49. package/lib/editor/exporter.js +185 -185
  50. package/lib/editor/icons.js +61 -61
  51. package/lib/editor/index.js +793 -793
  52. package/lib/editor/inspector.js +328 -328
  53. package/lib/editor/library.js +182 -182
  54. package/lib/editor/persist.js +18 -18
  55. package/lib/editor/samples.js +90 -90
  56. package/lib/editor/stage.js +239 -239
  57. package/lib/editor/store.js +80 -80
  58. package/lib/editor/styles/editor.css +385 -385
  59. package/lib/editor/styles/fonts/README.md +6 -6
  60. package/lib/editor/styles/fonts/geist-LICENSE.txt +93 -93
  61. package/lib/editor/styles/tokens.css +89 -89
  62. package/lib/editor/ui.js +97 -97
  63. package/lib/index.js +3 -1
  64. package/lib/video.js +92 -92
  65. package/package.json +92 -92
  66. package/types/editor.d.ts +34 -34
  67. package/types/index.d.ts +196 -196
  68. package/types/video.d.ts +43 -43
@@ -1,159 +1,159 @@
1
- // GPU backend: one ShaderMate instance, six passes, rendered on demand (never animating).
2
-
3
- import { createShader } from 'shadermate';
4
- import { COMMON, PREP, QUANT, DIFFUSE, RESOLVE, ORPHAN, SMOOTH, OUTLINE } from './shaders.js';
5
- import { MAX_KERNEL } from '../dither/kernels.js';
6
-
7
- const canvasOf = (w, h) => { const c = document.createElement('canvas'); c.width = w; c.height = h; return c; };
8
-
9
- /**
10
- * Create the WebGL 2 backend. Rejects when WebGL 2 or float render targets are missing,
11
- * so the caller can fall back to the CPU.
12
- */
13
- export async function createGPUBackend() {
14
- if (typeof document === 'undefined') throw new Error('No DOM');
15
- const probe = canvasOf(1, 1).getContext('webgl2');
16
- if (!probe) throw new Error('WebGL 2 is not available');
17
- if (!probe.getExtension('EXT_color_buffer_float')) throw new Error('Float render targets are not available');
18
- const dbg = probe.getExtension('WEBGL_debug_renderer_info');
19
- const renderer = String(dbg ? probe.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : probe.getParameter(probe.RENDERER));
20
- probe.getExtension('WEBGL_lose_context')?.loseContext();
21
-
22
- const canvas = canvasOf(1, 1);
23
- const source = canvasOf(1, 1);
24
- const palette = canvasOf(256, 1);
25
- const matrix = canvasOf(1, 1);
26
- const nearest = el => ({ element: el, filter: 'nearest', wrap: 'clamp' });
27
-
28
- const sm = createShader({
29
- canvas,
30
- webgl: 2,
31
- size: [1, 1],
32
- pixelRatio: 1,
33
- autoplay: false,
34
- pauseWhenHidden: false,
35
- pointer: false,
36
- errorOverlay: false,
37
- warnMissingUniforms: false,
38
- preserveDrawingBuffer: true,
39
- premultipliedAlpha: false,
40
- antialias: false,
41
- dialect: 'shadermate',
42
- common: COMMON,
43
- samplers: { u_palette: nearest(palette), u_matrix: nearest(matrix) },
44
- uniforms: {
45
- u_count: 1, u_distance: 0, u_resample: 0, u_adjust: [0, 0, 0, 1], u_hue: 0, u_crop: [0, 0, 1, 1], u_alphaCut: 0.5,
46
- u_mode: 0, u_levels: 1, u_amount: 0,
47
- u_skew: 1, u_strength: 1, u_kn: 0, u_kernel: new Float32Array(MAX_KERNEL * 3),
48
- u_outline: 0, u_corners: 0, u_outlineColor: [0, 0, 0],
49
- },
50
- passes: [
51
- { name: 'prep', shader: PREP, float: 'full', filter: 'nearest', channels: [{ ...nearest(source), dynamic: false }] },
52
- { name: 'quant', shader: QUANT, filter: 'nearest', channels: ['prep'] },
53
- { name: 'diffuse', shader: DIFFUSE, float: 'full', filter: 'nearest', pingpong: true, iterations: 2, channels: ['prep', 'diffuse'] },
54
- { name: 'resolve', shader: RESOLVE, filter: 'nearest', channels: ['quant', 'diffuse'] },
55
- { name: 'orphan', shader: ORPHAN, filter: 'nearest', channels: ['resolve'] },
56
- { name: 'smooth', shader: SMOOTH, filter: 'nearest', channels: ['orphan'] },
57
- { name: 'out', shader: OUTLINE, screen: true, channels: ['smooth'] },
58
- ],
59
- });
60
-
61
- let failure = null;
62
- sm.on('error', e => { failure = e; });
63
- await sm.ready;
64
- if (failure || sm.error) { sm.destroy(); throw failure || sm.error; }
65
-
66
- // `u_on` is a per-pass switch: same name, different value in each pass
67
- const passUniforms = (name, values) => { for (const k in values) sm.set(k, values[k], name); };
68
-
69
- let lastSource = null;
70
- let paletteKey = '', matrixKey = '';
71
-
72
- async function setSource(frame) {
73
- if (frame.canvas === lastSource && !frame.dirty) return;
74
- source.width = frame.width; source.height = frame.height;
75
- const ctx = source.getContext('2d');
76
- ctx.clearRect(0, 0, source.width, source.height);
77
- ctx.drawImage(frame.canvas, 0, 0);
78
- await sm.setChannel(0, { ...nearest(source), dynamic: false }, 'prep');
79
- lastSource = frame.canvas;
80
- }
81
-
82
- function setPalette(plan) {
83
- const key = plan.palette.map(c => c.join(',')).join(';');
84
- if (key === paletteKey) return;
85
- paletteKey = key;
86
- const ctx = palette.getContext('2d');
87
- const img = ctx.createImageData(256, 1);
88
- plan.palette.forEach((c, i) => img.data.set([c[0], c[1], c[2], 255], i * 4));
89
- ctx.putImageData(img, 0, 0);
90
- }
91
-
92
- function setMatrix(map) {
93
- const key = map ? `${map.w}x${map.h}:${map.levels}:${map.values.join(',')}` : 'none';
94
- if (key === matrixKey) return;
95
- matrixKey = key;
96
- const m = map || { w: 1, h: 1, values: [0] };
97
- matrix.width = m.w; matrix.height = m.h;
98
- const ctx = matrix.getContext('2d');
99
- const img = ctx.createImageData(m.w, m.h);
100
- for (let i = 0; i < m.values.length; i++) img.data.set([m.values[i], 0, 0, 255], i * 4);
101
- ctx.putImageData(img, 0, 0);
102
- }
103
-
104
- /**
105
- * @param {ReturnType<import('../plan.js').makePlan>} plan
106
- * @param {{ canvas: HTMLCanvasElement, width: number, height: number, dirty?: boolean }} frame
107
- * @returns {Promise<Uint8ClampedArray>} RGBA bytes, rows top-down
108
- */
109
- async function run(plan, frame) {
110
- const s = plan.settings;
111
- await setSource(frame);
112
- if (plan.quantize) setPalette(plan);
113
- setMatrix(plan.map);
114
- if (sm.width !== plan.width || sm.height !== plan.height) sm.setSize(plan.width, plan.height);
115
-
116
- const diffusion = plan.kind === 'diffusion';
117
- const kernel = new Array(MAX_KERNEL).fill(null).map((_, i) => (plan.kernel && plan.kernel[i]) || [0, 0, 0]);
118
- sm.set({
119
- u_count: plan.palette.length || 1,
120
- u_distance: plan.distance,
121
- u_resample: s.size.resample === 'nearest' ? 1 : 0,
122
- u_adjust: [s.adjust.brightness, s.adjust.contrast, s.adjust.saturation, s.adjust.gamma],
123
- u_hue: s.adjust.hue,
124
- u_crop: [plan.crop.x, plan.crop.y, plan.crop.w, plan.crop.h],
125
- u_alphaCut: s.alpha.threshold,
126
- u_mode: !plan.quantize ? 0 : plan.kind === 'ordered' ? 2 : 1,
127
- u_levels: plan.map ? plan.map.levels : 1,
128
- u_amount: plan.map ? plan.spread * s.dither.strength : 0,
129
- u_skew: plan.skew,
130
- u_strength: s.dither.strength,
131
- u_kn: plan.kernel ? plan.kernel.length : 0,
132
- u_kernel: new Float32Array(kernel.flat()),
133
- u_outline: { none: 0, outer: 1, inner: 2 }[plan.outline.mode],
134
- u_corners: plan.outline.corners ? 1 : 0,
135
- u_outlineColor: plan.outline.color.map(v => v / 255),
136
- });
137
- passUniforms('diffuse', { u_on: diffusion ? 1 : 0 });
138
- passUniforms('resolve', { u_on: diffusion ? 1 : 0 });
139
- passUniforms('orphan', { u_on: s.cleanup.orphans ? 1 : 0 });
140
- passUniforms('smooth', { u_on: s.cleanup.smooth ? 1 : 0 });
141
- sm.getPass('diffuse').cfg.iterations = diffusion ? plan.waves : 1;
142
-
143
- const { data } = sm.readPixels();
144
- // GL rows are bottom-up
145
- const w = plan.width, h = plan.height, out = new Uint8ClampedArray(w * h * 4), row = w * 4;
146
- for (let y = 0; y < h; y++) out.set(data.subarray((h - 1 - y) * row, (h - y) * row), y * row);
147
- return out;
148
- }
149
-
150
- return {
151
- name: 'webgl',
152
- renderer,
153
- /** Software rasterisers (SwiftShader, llvmpipe) are slower than the CPU path for error diffusion. */
154
- software: /swiftshader|llvmpipe|softpipe|software|basic render/i.test(renderer),
155
- run,
156
- shader: sm,
157
- destroy() { sm.destroy(); },
158
- };
159
- }
1
+ // GPU backend: one ShaderMate instance, six passes, rendered on demand (never animating).
2
+
3
+ import { createShader } from 'shadermate';
4
+ import { COMMON, PREP, QUANT, DIFFUSE, RESOLVE, ORPHAN, SMOOTH, OUTLINE } from './shaders.js';
5
+ import { MAX_KERNEL } from '../dither/kernels.js';
6
+
7
+ const canvasOf = (w, h) => { const c = document.createElement('canvas'); c.width = w; c.height = h; return c; };
8
+
9
+ /**
10
+ * Create the WebGL 2 backend. Rejects when WebGL 2 or float render targets are missing,
11
+ * so the caller can fall back to the CPU.
12
+ */
13
+ export async function createGPUBackend() {
14
+ if (typeof document === 'undefined') throw new Error('No DOM');
15
+ const probe = canvasOf(1, 1).getContext('webgl2');
16
+ if (!probe) throw new Error('WebGL 2 is not available');
17
+ if (!probe.getExtension('EXT_color_buffer_float')) throw new Error('Float render targets are not available');
18
+ const dbg = probe.getExtension('WEBGL_debug_renderer_info');
19
+ const renderer = String(dbg ? probe.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : probe.getParameter(probe.RENDERER));
20
+ probe.getExtension('WEBGL_lose_context')?.loseContext();
21
+
22
+ const canvas = canvasOf(1, 1);
23
+ const source = canvasOf(1, 1);
24
+ const palette = canvasOf(256, 1);
25
+ const matrix = canvasOf(1, 1);
26
+ const nearest = el => ({ element: el, filter: 'nearest', wrap: 'clamp' });
27
+
28
+ const sm = createShader({
29
+ canvas,
30
+ webgl: 2,
31
+ size: [1, 1],
32
+ pixelRatio: 1,
33
+ autoplay: false,
34
+ pauseWhenHidden: false,
35
+ pointer: false,
36
+ errorOverlay: false,
37
+ warnMissingUniforms: false,
38
+ preserveDrawingBuffer: true,
39
+ premultipliedAlpha: false,
40
+ antialias: false,
41
+ dialect: 'shadermate',
42
+ common: COMMON,
43
+ samplers: { u_palette: nearest(palette), u_matrix: nearest(matrix) },
44
+ uniforms: {
45
+ u_count: 1, u_distance: 0, u_resample: 0, u_adjust: [0, 0, 0, 1], u_hue: 0, u_crop: [0, 0, 1, 1], u_alphaCut: 0.5,
46
+ u_mode: 0, u_levels: 1, u_amount: 0,
47
+ u_skew: 1, u_strength: 1, u_kn: 0, u_kernel: new Float32Array(MAX_KERNEL * 3),
48
+ u_outline: 0, u_corners: 0, u_outlineColor: [0, 0, 0],
49
+ },
50
+ passes: [
51
+ { name: 'prep', shader: PREP, float: 'full', filter: 'nearest', channels: [{ ...nearest(source), dynamic: false }] },
52
+ { name: 'quant', shader: QUANT, filter: 'nearest', channels: ['prep'] },
53
+ { name: 'diffuse', shader: DIFFUSE, float: 'full', filter: 'nearest', pingpong: true, iterations: 2, channels: ['prep', 'diffuse'] },
54
+ { name: 'resolve', shader: RESOLVE, filter: 'nearest', channels: ['quant', 'diffuse'] },
55
+ { name: 'orphan', shader: ORPHAN, filter: 'nearest', channels: ['resolve'] },
56
+ { name: 'smooth', shader: SMOOTH, filter: 'nearest', channels: ['orphan'] },
57
+ { name: 'out', shader: OUTLINE, screen: true, channels: ['smooth'] },
58
+ ],
59
+ });
60
+
61
+ let failure = null;
62
+ sm.on('error', e => { failure = e; });
63
+ await sm.ready;
64
+ if (failure || sm.error) { sm.destroy(); throw failure || sm.error; }
65
+
66
+ // `u_on` is a per-pass switch: same name, different value in each pass
67
+ const passUniforms = (name, values) => { for (const k in values) sm.set(k, values[k], name); };
68
+
69
+ let lastSource = null;
70
+ let paletteKey = '', matrixKey = '';
71
+
72
+ async function setSource(frame) {
73
+ if (frame.canvas === lastSource && !frame.dirty) return;
74
+ source.width = frame.width; source.height = frame.height;
75
+ const ctx = source.getContext('2d');
76
+ ctx.clearRect(0, 0, source.width, source.height);
77
+ ctx.drawImage(frame.canvas, 0, 0);
78
+ await sm.setChannel(0, { ...nearest(source), dynamic: false }, 'prep');
79
+ lastSource = frame.canvas;
80
+ }
81
+
82
+ function setPalette(plan) {
83
+ const key = plan.palette.map(c => c.join(',')).join(';');
84
+ if (key === paletteKey) return;
85
+ paletteKey = key;
86
+ const ctx = palette.getContext('2d');
87
+ const img = ctx.createImageData(256, 1);
88
+ plan.palette.forEach((c, i) => img.data.set([c[0], c[1], c[2], 255], i * 4));
89
+ ctx.putImageData(img, 0, 0);
90
+ }
91
+
92
+ function setMatrix(map) {
93
+ const key = map ? `${map.w}x${map.h}:${map.levels}:${map.values.join(',')}` : 'none';
94
+ if (key === matrixKey) return;
95
+ matrixKey = key;
96
+ const m = map || { w: 1, h: 1, values: [0] };
97
+ matrix.width = m.w; matrix.height = m.h;
98
+ const ctx = matrix.getContext('2d');
99
+ const img = ctx.createImageData(m.w, m.h);
100
+ for (let i = 0; i < m.values.length; i++) img.data.set([m.values[i], 0, 0, 255], i * 4);
101
+ ctx.putImageData(img, 0, 0);
102
+ }
103
+
104
+ /**
105
+ * @param {ReturnType<import('../plan.js').makePlan>} plan
106
+ * @param {{ canvas: HTMLCanvasElement, width: number, height: number, dirty?: boolean }} frame
107
+ * @returns {Promise<Uint8ClampedArray>} RGBA bytes, rows top-down
108
+ */
109
+ async function run(plan, frame) {
110
+ const s = plan.settings;
111
+ await setSource(frame);
112
+ if (plan.quantize) setPalette(plan);
113
+ setMatrix(plan.map);
114
+ if (sm.width !== plan.width || sm.height !== plan.height) sm.setSize(plan.width, plan.height);
115
+
116
+ const diffusion = plan.kind === 'diffusion';
117
+ const kernel = new Array(MAX_KERNEL).fill(null).map((_, i) => (plan.kernel && plan.kernel[i]) || [0, 0, 0]);
118
+ sm.set({
119
+ u_count: plan.palette.length || 1,
120
+ u_distance: plan.distance,
121
+ u_resample: s.size.resample === 'nearest' ? 1 : 0,
122
+ u_adjust: [s.adjust.brightness, s.adjust.contrast, s.adjust.saturation, s.adjust.gamma],
123
+ u_hue: s.adjust.hue,
124
+ u_crop: [plan.crop.x, plan.crop.y, plan.crop.w, plan.crop.h],
125
+ u_alphaCut: s.alpha.threshold,
126
+ u_mode: !plan.quantize ? 0 : plan.kind === 'ordered' ? 2 : 1,
127
+ u_levels: plan.map ? plan.map.levels : 1,
128
+ u_amount: plan.map ? plan.spread * s.dither.strength : 0,
129
+ u_skew: plan.skew,
130
+ u_strength: s.dither.strength,
131
+ u_kn: plan.kernel ? plan.kernel.length : 0,
132
+ u_kernel: new Float32Array(kernel.flat()),
133
+ u_outline: { none: 0, outer: 1, inner: 2 }[plan.outline.mode],
134
+ u_corners: plan.outline.corners ? 1 : 0,
135
+ u_outlineColor: plan.outline.color.map(v => v / 255),
136
+ });
137
+ passUniforms('diffuse', { u_on: diffusion ? 1 : 0 });
138
+ passUniforms('resolve', { u_on: diffusion ? 1 : 0 });
139
+ passUniforms('orphan', { u_on: s.cleanup.orphans ? 1 : 0 });
140
+ passUniforms('smooth', { u_on: s.cleanup.smooth ? 1 : 0 });
141
+ sm.getPass('diffuse').cfg.iterations = diffusion ? plan.waves : 1;
142
+
143
+ const { data } = sm.readPixels();
144
+ // GL rows are bottom-up
145
+ const w = plan.width, h = plan.height, out = new Uint8ClampedArray(w * h * 4), row = w * 4;
146
+ for (let y = 0; y < h; y++) out.set(data.subarray((h - 1 - y) * row, (h - y) * row), y * row);
147
+ return out;
148
+ }
149
+
150
+ return {
151
+ name: 'webgl',
152
+ renderer,
153
+ /** Software rasterisers (SwiftShader, llvmpipe) are slower than the CPU path for error diffusion. */
154
+ software: /swiftshader|llvmpipe|softpipe|software|basic render/i.test(renderer),
155
+ run,
156
+ shader: sm,
157
+ destroy() { sm.destroy(); },
158
+ };
159
+ }