image-to-pixel 3.0.0 → 3.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.
Files changed (71) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -2
  3. package/dist/editor.css +1 -1
  4. package/dist/editor.css.map +2 -2
  5. package/dist/editor.esm.js +60 -60
  6. package/dist/editor.esm.js.map +4 -4
  7. package/dist/fonts/README.md +6 -6
  8. package/dist/fonts/geist-LICENSE.txt +93 -93
  9. package/dist/image-to-pixel.bundle.esm.js +16 -16
  10. package/dist/image-to-pixel.bundle.esm.js.map +3 -3
  11. package/dist/image-to-pixel.cjs +2 -2
  12. package/dist/image-to-pixel.cjs.map +2 -2
  13. package/dist/image-to-pixel.esm.js +2 -2
  14. package/dist/image-to-pixel.esm.js.map +2 -2
  15. package/dist/image-to-pixel.js +2 -2
  16. package/dist/image-to-pixel.js.map +2 -2
  17. package/dist/image-to-pixel.min.js +11 -11
  18. package/dist/image-to-pixel.min.js.map +3 -3
  19. package/dist/video.esm.js +2 -2
  20. package/dist/video.esm.js.map +4 -4
  21. package/lib/core/color.js +69 -69
  22. package/lib/core/cpu/pipeline.js +21 -21
  23. package/lib/core/cpu/stages.js +227 -227
  24. package/lib/core/dither/bluenoise-data.js +3 -3
  25. package/lib/core/dither/kernels.js +27 -27
  26. package/lib/core/dither/matrices.js +95 -95
  27. package/lib/core/engine.js +158 -158
  28. package/lib/core/export/canvas.js +22 -22
  29. package/lib/core/export/crc32.js +15 -15
  30. package/lib/core/export/gif.js +153 -153
  31. package/lib/core/export/png.js +94 -94
  32. package/lib/core/export/scale.js +103 -103
  33. package/lib/core/export/sheet.js +95 -95
  34. package/lib/core/export/zip.js +55 -55
  35. package/lib/core/gpu/backend.js +159 -159
  36. package/lib/core/gpu/shaders.js +258 -258
  37. package/lib/core/palette/builtin.js +36 -36
  38. package/lib/core/palette/extract.js +69 -69
  39. package/lib/core/palette/lospec.js +24 -24
  40. package/lib/core/palette/parse.js +112 -112
  41. package/lib/core/plan.js +58 -58
  42. package/lib/core/presets.js +42 -42
  43. package/lib/core/settings.js +274 -274
  44. package/lib/core/source.js +89 -89
  45. package/lib/core/video/audio.js +159 -0
  46. package/lib/core/video/frames.js +38 -0
  47. package/lib/core/video/mux-mp4.js +213 -24
  48. package/lib/core/video/mux-webm.js +63 -22
  49. package/lib/editor/controls.js +139 -139
  50. package/lib/editor/dom.js +65 -65
  51. package/lib/editor/exporter.js +43 -3
  52. package/lib/editor/icons.js +4 -0
  53. package/lib/editor/index.js +218 -9
  54. package/lib/editor/inspector.js +19 -4
  55. package/lib/editor/library.js +8 -2
  56. package/lib/editor/persist.js +18 -18
  57. package/lib/editor/samples.js +90 -90
  58. package/lib/editor/stage.js +239 -239
  59. package/lib/editor/store.js +80 -80
  60. package/lib/editor/styles/editor.css +34 -1
  61. package/lib/editor/styles/fonts/README.md +6 -6
  62. package/lib/editor/styles/fonts/geist-LICENSE.txt +93 -93
  63. package/lib/editor/styles/tokens.css +89 -89
  64. package/lib/editor/ui.js +97 -97
  65. package/lib/editor/workspace.js +62 -0
  66. package/lib/index.js +31 -29
  67. package/lib/video.js +37 -7
  68. package/package.json +1 -1
  69. package/types/editor.d.ts +34 -34
  70. package/types/index.d.ts +196 -196
  71. package/types/video.d.ts +61 -5
@@ -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
+ }