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
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.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 }[];