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.
- package/LICENSE +21 -21
- package/README.md +69 -69
- package/dist/editor.css.map +1 -1
- package/dist/editor.esm.js +1 -1
- package/dist/editor.esm.js.map +1 -1
- package/dist/fonts/README.md +6 -6
- package/dist/fonts/geist-LICENSE.txt +93 -93
- package/dist/image-to-pixel.bundle.esm.js +13 -13
- package/dist/image-to-pixel.bundle.esm.js.map +2 -2
- 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 +2 -2
- package/dist/video.esm.js +1 -1
- package/dist/video.esm.js.map +1 -1
- 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/frames.js +144 -144
- package/lib/core/video/mux-mp4.js +131 -131
- package/lib/core/video/mux-webm.js +127 -127
- package/lib/editor/controls.js +139 -139
- package/lib/editor/dom.js +65 -65
- package/lib/editor/exporter.js +185 -185
- package/lib/editor/icons.js +61 -61
- package/lib/editor/index.js +793 -793
- package/lib/editor/inspector.js +328 -328
- package/lib/editor/library.js +182 -182
- 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 +385 -385
- 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/index.js +3 -1
- package/lib/video.js +92 -92
- package/package.json +92 -92
- package/types/editor.d.ts +34 -34
- package/types/index.d.ts +196 -196
- package/types/video.d.ts +43 -43
package/lib/video.js
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
// image-to-pixel/video: encode processed frames to MP4 (H.264) or WebM (VP9/VP8) with WebCodecs.
|
|
2
|
-
// The muxers are this project's own (lib/core/video/mux-*.js): MIT, no dependencies.
|
|
3
|
-
|
|
4
|
-
import { createMp4Muxer } from './core/video/mux-mp4.js';
|
|
5
|
-
import { createWebmMuxer } from './core/video/mux-webm.js';
|
|
6
|
-
import { upscale } from './core/export/scale.js';
|
|
7
|
-
|
|
8
|
-
export { forEachFrame, estimateFps, seekTo, frameTimes, videoReady } from './core/video/frames.js';
|
|
9
|
-
export { createMp4Muxer, createWebmMuxer };
|
|
10
|
-
|
|
11
|
-
const AVC = ['avc1.640033', 'avc1.4d0033', 'avc1.42003e', 'avc1.640028', 'avc1.42001f'];
|
|
12
|
-
const VP = [['vp09.00.10.08', 'vp9'], ['vp8', 'vp8']];
|
|
13
|
-
|
|
14
|
-
/** Which formats this browser can encode. */
|
|
15
|
-
export async function videoSupport(width = 640, height = 480) {
|
|
16
|
-
const out = { mp4: false, webm: false, webcodecs: typeof VideoEncoder !== 'undefined' };
|
|
17
|
-
if (out.webcodecs) {
|
|
18
|
-
for (const codec of AVC) if ((await VideoEncoder.isConfigSupported({ codec, width, height }).catch(() => ({}))).supported) { out.mp4 = true; break; }
|
|
19
|
-
for (const [codec] of VP) if ((await VideoEncoder.isConfigSupported({ codec, width, height }).catch(() => ({}))).supported) { out.webm = true; break; }
|
|
20
|
-
}
|
|
21
|
-
return out;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const even = n => n + (n & 1);
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Start an encoder; add frames one at a time (so a long clip never sits in memory), then finish().
|
|
28
|
-
* @param {{ format: 'mp4' | 'webm', fps: number, width: number, height: number, scale?: number, bitrate?: number }} opts
|
|
29
|
-
* width/height: size of the frames you will add (before scaling)
|
|
30
|
-
*/
|
|
31
|
-
export async function createVideoEncoder(opts) {
|
|
32
|
-
const scale = Math.max(1, Math.round(opts.scale || 1));
|
|
33
|
-
const W = even(opts.width * scale), H = even(opts.height * scale);
|
|
34
|
-
const fps = opts.fps || 30;
|
|
35
|
-
const bitrate = opts.bitrate || Math.min(20e6, Math.max(2e6, W * H * fps * 0.15));
|
|
36
|
-
if (typeof VideoEncoder === 'undefined') throw new Error('This browser has no WebCodecs VideoEncoder. Export a GIF or PNG sequence instead.');
|
|
37
|
-
|
|
38
|
-
let codec = null, muxCodec = null;
|
|
39
|
-
const list = opts.format === 'mp4' ? AVC.map(c => [c, 'avc']) : VP;
|
|
40
|
-
for (const [c, m] of list) {
|
|
41
|
-
const ok = await VideoEncoder.isConfigSupported({ codec: c, width: W, height: H, bitrate, framerate: fps }).catch(() => ({}));
|
|
42
|
-
if (ok.supported) { codec = c; muxCodec = m; break; }
|
|
43
|
-
}
|
|
44
|
-
if (!codec) throw new Error(`No ${opts.format.toUpperCase()} encoder available in this browser at ${W}×${H}.`);
|
|
45
|
-
|
|
46
|
-
const muxer = opts.format === 'mp4'
|
|
47
|
-
? createMp4Muxer({ width: W, height: H })
|
|
48
|
-
: createWebmMuxer({ width: W, height: H, codec: muxCodec, fps });
|
|
49
|
-
|
|
50
|
-
let error = null;
|
|
51
|
-
const encoder = new VideoEncoder({
|
|
52
|
-
output: (chunk, meta) => muxer.addChunk(chunk, meta),
|
|
53
|
-
error: e => { error = e; },
|
|
54
|
-
});
|
|
55
|
-
// avc.format 'avc': length-prefixed NAL units plus a decoder config record, which MP4 needs
|
|
56
|
-
encoder.configure({ codec, width: W, height: H, bitrate, framerate: fps, latencyMode: 'quality', ...(opts.format === 'mp4' ? { avc: { format: 'avc' } } : {}) });
|
|
57
|
-
|
|
58
|
-
const canvas = new OffscreenCanvas(W, H);
|
|
59
|
-
const ctx = canvas.getContext('2d');
|
|
60
|
-
let i = 0;
|
|
61
|
-
const frameUs = 1e6 / fps;
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
width: W, height: H, codec,
|
|
65
|
-
/** @param {{ width, height, data }} img */
|
|
66
|
-
async add(img) {
|
|
67
|
-
if (error) throw error;
|
|
68
|
-
const up = scale > 1 ? upscale(img, scale) : img;
|
|
69
|
-
ctx.fillStyle = '#000'; ctx.fillRect(0, 0, W, H);
|
|
70
|
-
ctx.putImageData(new ImageData(new Uint8ClampedArray(up.data), up.width, up.height), 0, 0);
|
|
71
|
-
const frame = new VideoFrame(canvas, { timestamp: Math.round(i * frameUs), duration: Math.round(frameUs) });
|
|
72
|
-
encoder.encode(frame, { keyFrame: i % Math.max(1, Math.round(fps * 2)) === 0 });
|
|
73
|
-
frame.close();
|
|
74
|
-
i++;
|
|
75
|
-
if (encoder.encodeQueueSize > 8) await new Promise(r => setTimeout(r, 0));
|
|
76
|
-
},
|
|
77
|
-
/** @returns {Promise<Blob>} */
|
|
78
|
-
async finish() {
|
|
79
|
-
await encoder.flush();
|
|
80
|
-
if (error) throw error;
|
|
81
|
-
encoder.close();
|
|
82
|
-
return new Blob([muxer.finish()], { type: opts.format === 'mp4' ? 'video/mp4' : 'video/webm' });
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Convenience: encode an array of frames in one go. */
|
|
88
|
-
export async function encodeVideo(frames, opts) {
|
|
89
|
-
const enc = await createVideoEncoder({ ...opts, width: frames[0].width, height: frames[0].height });
|
|
90
|
-
for (const f of frames) await enc.add(f);
|
|
91
|
-
return enc.finish();
|
|
92
|
-
}
|
|
1
|
+
// image-to-pixel/video: encode processed frames to MP4 (H.264) or WebM (VP9/VP8) with WebCodecs.
|
|
2
|
+
// The muxers are this project's own (lib/core/video/mux-*.js): MIT, no dependencies.
|
|
3
|
+
|
|
4
|
+
import { createMp4Muxer } from './core/video/mux-mp4.js';
|
|
5
|
+
import { createWebmMuxer } from './core/video/mux-webm.js';
|
|
6
|
+
import { upscale } from './core/export/scale.js';
|
|
7
|
+
|
|
8
|
+
export { forEachFrame, estimateFps, seekTo, frameTimes, videoReady } from './core/video/frames.js';
|
|
9
|
+
export { createMp4Muxer, createWebmMuxer };
|
|
10
|
+
|
|
11
|
+
const AVC = ['avc1.640033', 'avc1.4d0033', 'avc1.42003e', 'avc1.640028', 'avc1.42001f'];
|
|
12
|
+
const VP = [['vp09.00.10.08', 'vp9'], ['vp8', 'vp8']];
|
|
13
|
+
|
|
14
|
+
/** Which formats this browser can encode. */
|
|
15
|
+
export async function videoSupport(width = 640, height = 480) {
|
|
16
|
+
const out = { mp4: false, webm: false, webcodecs: typeof VideoEncoder !== 'undefined' };
|
|
17
|
+
if (out.webcodecs) {
|
|
18
|
+
for (const codec of AVC) if ((await VideoEncoder.isConfigSupported({ codec, width, height }).catch(() => ({}))).supported) { out.mp4 = true; break; }
|
|
19
|
+
for (const [codec] of VP) if ((await VideoEncoder.isConfigSupported({ codec, width, height }).catch(() => ({}))).supported) { out.webm = true; break; }
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const even = n => n + (n & 1);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Start an encoder; add frames one at a time (so a long clip never sits in memory), then finish().
|
|
28
|
+
* @param {{ format: 'mp4' | 'webm', fps: number, width: number, height: number, scale?: number, bitrate?: number }} opts
|
|
29
|
+
* width/height: size of the frames you will add (before scaling)
|
|
30
|
+
*/
|
|
31
|
+
export async function createVideoEncoder(opts) {
|
|
32
|
+
const scale = Math.max(1, Math.round(opts.scale || 1));
|
|
33
|
+
const W = even(opts.width * scale), H = even(opts.height * scale);
|
|
34
|
+
const fps = opts.fps || 30;
|
|
35
|
+
const bitrate = opts.bitrate || Math.min(20e6, Math.max(2e6, W * H * fps * 0.15));
|
|
36
|
+
if (typeof VideoEncoder === 'undefined') throw new Error('This browser has no WebCodecs VideoEncoder. Export a GIF or PNG sequence instead.');
|
|
37
|
+
|
|
38
|
+
let codec = null, muxCodec = null;
|
|
39
|
+
const list = opts.format === 'mp4' ? AVC.map(c => [c, 'avc']) : VP;
|
|
40
|
+
for (const [c, m] of list) {
|
|
41
|
+
const ok = await VideoEncoder.isConfigSupported({ codec: c, width: W, height: H, bitrate, framerate: fps }).catch(() => ({}));
|
|
42
|
+
if (ok.supported) { codec = c; muxCodec = m; break; }
|
|
43
|
+
}
|
|
44
|
+
if (!codec) throw new Error(`No ${opts.format.toUpperCase()} encoder available in this browser at ${W}×${H}.`);
|
|
45
|
+
|
|
46
|
+
const muxer = opts.format === 'mp4'
|
|
47
|
+
? createMp4Muxer({ width: W, height: H })
|
|
48
|
+
: createWebmMuxer({ width: W, height: H, codec: muxCodec, fps });
|
|
49
|
+
|
|
50
|
+
let error = null;
|
|
51
|
+
const encoder = new VideoEncoder({
|
|
52
|
+
output: (chunk, meta) => muxer.addChunk(chunk, meta),
|
|
53
|
+
error: e => { error = e; },
|
|
54
|
+
});
|
|
55
|
+
// avc.format 'avc': length-prefixed NAL units plus a decoder config record, which MP4 needs
|
|
56
|
+
encoder.configure({ codec, width: W, height: H, bitrate, framerate: fps, latencyMode: 'quality', ...(opts.format === 'mp4' ? { avc: { format: 'avc' } } : {}) });
|
|
57
|
+
|
|
58
|
+
const canvas = new OffscreenCanvas(W, H);
|
|
59
|
+
const ctx = canvas.getContext('2d');
|
|
60
|
+
let i = 0;
|
|
61
|
+
const frameUs = 1e6 / fps;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
width: W, height: H, codec,
|
|
65
|
+
/** @param {{ width, height, data }} img */
|
|
66
|
+
async add(img) {
|
|
67
|
+
if (error) throw error;
|
|
68
|
+
const up = scale > 1 ? upscale(img, scale) : img;
|
|
69
|
+
ctx.fillStyle = '#000'; ctx.fillRect(0, 0, W, H);
|
|
70
|
+
ctx.putImageData(new ImageData(new Uint8ClampedArray(up.data), up.width, up.height), 0, 0);
|
|
71
|
+
const frame = new VideoFrame(canvas, { timestamp: Math.round(i * frameUs), duration: Math.round(frameUs) });
|
|
72
|
+
encoder.encode(frame, { keyFrame: i % Math.max(1, Math.round(fps * 2)) === 0 });
|
|
73
|
+
frame.close();
|
|
74
|
+
i++;
|
|
75
|
+
if (encoder.encodeQueueSize > 8) await new Promise(r => setTimeout(r, 0));
|
|
76
|
+
},
|
|
77
|
+
/** @returns {Promise<Blob>} */
|
|
78
|
+
async finish() {
|
|
79
|
+
await encoder.flush();
|
|
80
|
+
if (error) throw error;
|
|
81
|
+
encoder.close();
|
|
82
|
+
return new Blob([muxer.finish()], { type: opts.format === 'mp4' ? 'video/mp4' : 'video/webm' });
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Convenience: encode an array of frames in one go. */
|
|
88
|
+
export async function encodeVideo(frames, opts) {
|
|
89
|
+
const enc = await createVideoEncoder({ ...opts, width: frames[0].width, height: frames[0].height });
|
|
90
|
+
for (const f of frames) await enc.add(f);
|
|
91
|
+
return enc.finish();
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "image-to-pixel",
|
|
3
|
-
"version": "3.0.
|
|
4
|
-
"description": "Turn images and video into pixel art: dithering, palettes, batch and video. A WebGL engine (via ShaderMate) with a CPU fallback, plus a full editor.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"author": "Tezumie",
|
|
8
|
-
"homepage": "https://tezumie.github.io/Image-to-Pixel/",
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/Tezumie/Image-to-Pixel.git"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/Tezumie/Image-to-Pixel/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"pixel-art",
|
|
18
|
-
"dithering",
|
|
19
|
-
"palette",
|
|
20
|
-
"quantization",
|
|
21
|
-
"floyd-steinberg",
|
|
22
|
-
"bayer",
|
|
23
|
-
"webgl",
|
|
24
|
-
"glsl",
|
|
25
|
-
"shader",
|
|
26
|
-
"gif",
|
|
27
|
-
"sprite-sheet",
|
|
28
|
-
"lospec"
|
|
29
|
-
],
|
|
30
|
-
"main": "./dist/image-to-pixel.cjs",
|
|
31
|
-
"module": "./dist/image-to-pixel.esm.js",
|
|
32
|
-
"types": "./types/index.d.ts",
|
|
33
|
-
"unpkg": "./dist/image-to-pixel.min.js",
|
|
34
|
-
"jsdelivr": "./dist/image-to-pixel.min.js",
|
|
35
|
-
"exports": {
|
|
36
|
-
".": {
|
|
37
|
-
"types": "./types/index.d.ts",
|
|
38
|
-
"import": "./dist/image-to-pixel.esm.js",
|
|
39
|
-
"require": "./dist/image-to-pixel.cjs",
|
|
40
|
-
"default": "./dist/image-to-pixel.esm.js"
|
|
41
|
-
},
|
|
42
|
-
"./video": {
|
|
43
|
-
"types": "./types/video.d.ts",
|
|
44
|
-
"import": "./dist/video.esm.js",
|
|
45
|
-
"default": "./dist/video.esm.js"
|
|
46
|
-
},
|
|
47
|
-
"./editor": {
|
|
48
|
-
"types": "./types/editor.d.ts",
|
|
49
|
-
"import": "./dist/editor.esm.js",
|
|
50
|
-
"default": "./dist/editor.esm.js"
|
|
51
|
-
},
|
|
52
|
-
"./editor.css": "./dist/editor.css",
|
|
53
|
-
"./dist/*": "./dist/*",
|
|
54
|
-
"./package.json": "./package.json"
|
|
55
|
-
},
|
|
56
|
-
"sideEffects": [
|
|
57
|
-
"./dist/editor.css",
|
|
58
|
-
"./dist/image-to-pixel.js",
|
|
59
|
-
"./dist/image-to-pixel.min.js"
|
|
60
|
-
],
|
|
61
|
-
"files": [
|
|
62
|
-
"dist",
|
|
63
|
-
"lib",
|
|
64
|
-
"types",
|
|
65
|
-
"LICENSE",
|
|
66
|
-
"README.md"
|
|
67
|
-
],
|
|
68
|
-
"engines": {
|
|
69
|
-
"node": ">=18"
|
|
70
|
-
},
|
|
71
|
-
"scripts": {
|
|
72
|
-
"build": "node esbuild.config.mjs",
|
|
73
|
-
"build:site": "npm run build && node scripts/build-site.mjs",
|
|
74
|
-
"watch": "node esbuild.config.mjs --watch",
|
|
75
|
-
"dev": "node scripts/serve.mjs --dev",
|
|
76
|
-
"preview": "node scripts/serve.mjs",
|
|
77
|
-
"bluenoise": "node scripts/gen-bluenoise.mjs",
|
|
78
|
-
"screenshots": "node scripts/screenshots.mjs",
|
|
79
|
-
"clean": "node scripts/clean.mjs",
|
|
80
|
-
"test": "npm run build:site && node --test",
|
|
81
|
-
"prepublishOnly": "npm test",
|
|
82
|
-
"check:runtime": "node scripts/nodepod-check.mjs"
|
|
83
|
-
},
|
|
84
|
-
"dependencies": {
|
|
85
|
-
"shadermate": "^2.0.0"
|
|
86
|
-
},
|
|
87
|
-
"devDependencies": {
|
|
88
|
-
"esbuild": "^0.25.12",
|
|
89
|
-
"highlight.js": "^11.12.0",
|
|
90
|
-
"marked": "^15.0.12"
|
|
91
|
-
}
|
|
92
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "image-to-pixel",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "Turn images and video into pixel art: dithering, palettes, batch and video. A WebGL engine (via ShaderMate) with a CPU fallback, plus a full editor.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Tezumie",
|
|
8
|
+
"homepage": "https://tezumie.github.io/Image-to-Pixel/",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/Tezumie/Image-to-Pixel.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Tezumie/Image-to-Pixel/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pixel-art",
|
|
18
|
+
"dithering",
|
|
19
|
+
"palette",
|
|
20
|
+
"quantization",
|
|
21
|
+
"floyd-steinberg",
|
|
22
|
+
"bayer",
|
|
23
|
+
"webgl",
|
|
24
|
+
"glsl",
|
|
25
|
+
"shader",
|
|
26
|
+
"gif",
|
|
27
|
+
"sprite-sheet",
|
|
28
|
+
"lospec"
|
|
29
|
+
],
|
|
30
|
+
"main": "./dist/image-to-pixel.cjs",
|
|
31
|
+
"module": "./dist/image-to-pixel.esm.js",
|
|
32
|
+
"types": "./types/index.d.ts",
|
|
33
|
+
"unpkg": "./dist/image-to-pixel.min.js",
|
|
34
|
+
"jsdelivr": "./dist/image-to-pixel.min.js",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./types/index.d.ts",
|
|
38
|
+
"import": "./dist/image-to-pixel.esm.js",
|
|
39
|
+
"require": "./dist/image-to-pixel.cjs",
|
|
40
|
+
"default": "./dist/image-to-pixel.esm.js"
|
|
41
|
+
},
|
|
42
|
+
"./video": {
|
|
43
|
+
"types": "./types/video.d.ts",
|
|
44
|
+
"import": "./dist/video.esm.js",
|
|
45
|
+
"default": "./dist/video.esm.js"
|
|
46
|
+
},
|
|
47
|
+
"./editor": {
|
|
48
|
+
"types": "./types/editor.d.ts",
|
|
49
|
+
"import": "./dist/editor.esm.js",
|
|
50
|
+
"default": "./dist/editor.esm.js"
|
|
51
|
+
},
|
|
52
|
+
"./editor.css": "./dist/editor.css",
|
|
53
|
+
"./dist/*": "./dist/*",
|
|
54
|
+
"./package.json": "./package.json"
|
|
55
|
+
},
|
|
56
|
+
"sideEffects": [
|
|
57
|
+
"./dist/editor.css",
|
|
58
|
+
"./dist/image-to-pixel.js",
|
|
59
|
+
"./dist/image-to-pixel.min.js"
|
|
60
|
+
],
|
|
61
|
+
"files": [
|
|
62
|
+
"dist",
|
|
63
|
+
"lib",
|
|
64
|
+
"types",
|
|
65
|
+
"LICENSE",
|
|
66
|
+
"README.md"
|
|
67
|
+
],
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=18"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "node esbuild.config.mjs",
|
|
73
|
+
"build:site": "npm run build && node scripts/build-site.mjs",
|
|
74
|
+
"watch": "node esbuild.config.mjs --watch",
|
|
75
|
+
"dev": "node scripts/serve.mjs --dev",
|
|
76
|
+
"preview": "node scripts/serve.mjs",
|
|
77
|
+
"bluenoise": "node scripts/gen-bluenoise.mjs",
|
|
78
|
+
"screenshots": "node scripts/screenshots.mjs",
|
|
79
|
+
"clean": "node scripts/clean.mjs",
|
|
80
|
+
"test": "npm run build:site && node --test",
|
|
81
|
+
"prepublishOnly": "npm test",
|
|
82
|
+
"check:runtime": "node scripts/nodepod-check.mjs"
|
|
83
|
+
},
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"shadermate": "^2.0.0"
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"esbuild": "^0.25.12",
|
|
89
|
+
"highlight.js": "^11.12.0",
|
|
90
|
+
"marked": "^15.0.12"
|
|
91
|
+
}
|
|
92
|
+
}
|
package/types/editor.d.ts
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
// image-to-pixel/editor
|
|
2
|
-
import type { Settings, SettingsInput, PixelResult, Source } from './index';
|
|
3
|
-
|
|
4
|
-
export interface EditorOptions {
|
|
5
|
-
settings?: SettingsInput;
|
|
6
|
-
/** Remember settings, theme and palettes in localStorage (default true). */
|
|
7
|
-
persist?: boolean;
|
|
8
|
-
/** Show sample images in the empty state (default true). */
|
|
9
|
-
samples?: boolean;
|
|
10
|
-
/** Turn the logo into a link. */
|
|
11
|
-
homeHref?: string;
|
|
12
|
-
}
|
|
13
|
-
export interface EditorStore {
|
|
14
|
-
readonly settings: Settings;
|
|
15
|
-
update(patch: SettingsInput, opts?: { key?: string; history?: boolean }): void;
|
|
16
|
-
replace(settings: SettingsInput): void;
|
|
17
|
-
subscribe(fn: (settings: Settings, why: string) => void): () => void;
|
|
18
|
-
undo(): boolean;
|
|
19
|
-
redo(): boolean;
|
|
20
|
-
readonly canUndo: boolean;
|
|
21
|
-
readonly canRedo: boolean;
|
|
22
|
-
}
|
|
23
|
-
export interface Editor {
|
|
24
|
-
store: EditorStore;
|
|
25
|
-
addFiles(files: File[]): Promise<void>;
|
|
26
|
-
addImage(source: Source, name?: string): Promise<unknown>;
|
|
27
|
-
openSample(id: 'dusk' | 'still-life' | 'potion'): Promise<void>;
|
|
28
|
-
applyPreset(id: string): void;
|
|
29
|
-
setTheme(theme: 'dark' | 'light'): void;
|
|
30
|
-
readonly result: PixelResult | null;
|
|
31
|
-
idle(): Promise<void>;
|
|
32
|
-
}
|
|
33
|
-
export function mountEditor(root: HTMLElement, options?: EditorOptions): Promise<Editor>;
|
|
34
|
-
export const SAMPLES: { id: string; name: string; svg: string }[];
|
|
1
|
+
// image-to-pixel/editor
|
|
2
|
+
import type { Settings, SettingsInput, PixelResult, Source } from './index';
|
|
3
|
+
|
|
4
|
+
export interface EditorOptions {
|
|
5
|
+
settings?: SettingsInput;
|
|
6
|
+
/** Remember settings, theme and palettes in localStorage (default true). */
|
|
7
|
+
persist?: boolean;
|
|
8
|
+
/** Show sample images in the empty state (default true). */
|
|
9
|
+
samples?: boolean;
|
|
10
|
+
/** Turn the logo into a link. */
|
|
11
|
+
homeHref?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface EditorStore {
|
|
14
|
+
readonly settings: Settings;
|
|
15
|
+
update(patch: SettingsInput, opts?: { key?: string; history?: boolean }): void;
|
|
16
|
+
replace(settings: SettingsInput): void;
|
|
17
|
+
subscribe(fn: (settings: Settings, why: string) => void): () => void;
|
|
18
|
+
undo(): boolean;
|
|
19
|
+
redo(): boolean;
|
|
20
|
+
readonly canUndo: boolean;
|
|
21
|
+
readonly canRedo: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface Editor {
|
|
24
|
+
store: EditorStore;
|
|
25
|
+
addFiles(files: File[]): Promise<void>;
|
|
26
|
+
addImage(source: Source, name?: string): Promise<unknown>;
|
|
27
|
+
openSample(id: 'dusk' | 'still-life' | 'potion'): Promise<void>;
|
|
28
|
+
applyPreset(id: string): void;
|
|
29
|
+
setTheme(theme: 'dark' | 'light'): void;
|
|
30
|
+
readonly result: PixelResult | null;
|
|
31
|
+
idle(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export function mountEditor(root: HTMLElement, options?: EditorOptions): Promise<Editor>;
|
|
34
|
+
export const SAMPLES: { id: string; name: string; svg: string }[];
|