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.
- package/LICENSE +21 -21
- package/README.md +2 -2
- package/dist/editor.css +1 -1
- package/dist/editor.css.map +2 -2
- package/dist/editor.esm.js +60 -60
- package/dist/editor.esm.js.map +4 -4
- package/dist/fonts/README.md +6 -6
- package/dist/fonts/geist-LICENSE.txt +93 -93
- package/dist/image-to-pixel.bundle.esm.js +16 -16
- package/dist/image-to-pixel.bundle.esm.js.map +3 -3
- package/dist/image-to-pixel.cjs +2 -2
- package/dist/image-to-pixel.cjs.map +2 -2
- package/dist/image-to-pixel.esm.js +2 -2
- package/dist/image-to-pixel.esm.js.map +2 -2
- package/dist/image-to-pixel.js +2 -2
- package/dist/image-to-pixel.js.map +2 -2
- package/dist/image-to-pixel.min.js +11 -11
- package/dist/image-to-pixel.min.js.map +3 -3
- package/dist/video.esm.js +2 -2
- package/dist/video.esm.js.map +4 -4
- package/lib/core/color.js +69 -69
- package/lib/core/cpu/pipeline.js +21 -21
- package/lib/core/cpu/stages.js +227 -227
- package/lib/core/dither/bluenoise-data.js +3 -3
- package/lib/core/dither/kernels.js +27 -27
- package/lib/core/dither/matrices.js +95 -95
- package/lib/core/engine.js +158 -158
- package/lib/core/export/canvas.js +22 -22
- package/lib/core/export/crc32.js +15 -15
- package/lib/core/export/gif.js +153 -153
- package/lib/core/export/png.js +94 -94
- package/lib/core/export/scale.js +103 -103
- package/lib/core/export/sheet.js +95 -95
- package/lib/core/export/zip.js +55 -55
- package/lib/core/gpu/backend.js +159 -159
- package/lib/core/gpu/shaders.js +258 -258
- package/lib/core/palette/builtin.js +36 -36
- package/lib/core/palette/extract.js +69 -69
- package/lib/core/palette/lospec.js +24 -24
- package/lib/core/palette/parse.js +112 -112
- package/lib/core/plan.js +58 -58
- package/lib/core/presets.js +42 -42
- package/lib/core/settings.js +274 -274
- package/lib/core/source.js +89 -89
- package/lib/core/video/audio.js +159 -0
- package/lib/core/video/frames.js +38 -0
- package/lib/core/video/mux-mp4.js +213 -24
- package/lib/core/video/mux-webm.js +63 -22
- package/lib/editor/controls.js +139 -139
- package/lib/editor/dom.js +65 -65
- package/lib/editor/exporter.js +43 -3
- package/lib/editor/icons.js +4 -0
- package/lib/editor/index.js +218 -9
- package/lib/editor/inspector.js +19 -4
- package/lib/editor/library.js +8 -2
- package/lib/editor/persist.js +18 -18
- package/lib/editor/samples.js +90 -90
- package/lib/editor/stage.js +239 -239
- package/lib/editor/store.js +80 -80
- package/lib/editor/styles/editor.css +34 -1
- package/lib/editor/styles/fonts/README.md +6 -6
- package/lib/editor/styles/fonts/geist-LICENSE.txt +93 -93
- package/lib/editor/styles/tokens.css +89 -89
- package/lib/editor/ui.js +97 -97
- package/lib/editor/workspace.js +62 -0
- package/lib/index.js +31 -29
- package/lib/video.js +37 -7
- package/package.json +1 -1
- package/types/editor.d.ts +34 -34
- package/types/index.d.ts +196 -196
- package/types/video.d.ts +61 -5
package/lib/core/gpu/backend.js
CHANGED
|
@@ -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
|
+
}
|