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
package/types/index.d.ts CHANGED
@@ -1,196 +1,196 @@
1
- // Type definitions for image-to-pixel 3.x
2
-
3
- export type DitherMethod =
4
- | 'none' | 'bayer2' | 'bayer4' | 'bayer8' | 'bayer16' | 'cluster4' | 'cluster8' | 'lines' | 'bluenoise' | 'custom'
5
- | 'floyd-steinberg' | 'atkinson' | 'sierra-lite' | 'sierra' | 'burkes' | 'jarvis' | 'stucki';
6
-
7
- export interface SizeSettings {
8
- mode: 'width' | 'block';
9
- /** Output width, 1–2048. */
10
- width: number;
11
- /** Output height; 0 keeps the aspect ratio. */
12
- height: number;
13
- /** Source pixels per output pixel when mode is 'block'. */
14
- block: number;
15
- resample: 'box' | 'nearest';
16
- }
17
- export interface AdjustSettings { brightness: number; contrast: number; saturation: number; /** degrees, -180..180 */ hue: number; gamma: number }
18
- export type AspectId = 'original' | '1:1' | '4:3' | '3:4' | '3:2' | '2:3' | '16:9' | '9:16';
19
- /** Which part of the source is used: the largest rectangle of `aspect`, divided by `zoom`, placed by x/y (0..1). */
20
- export interface FrameSettings { aspect: AspectId; zoom: number; x: number; y: number }
21
- export interface PaletteSettings {
22
- source: 'builtin' | 'custom' | 'extract' | 'none';
23
- /** Built-in palette id, e.g. 'pico-8'. */
24
- id: string;
25
- name: string;
26
- /** '#rrggbb' strings. */
27
- colors: string[];
28
- /** Colours to extract when source is 'extract'. */
29
- count: number;
30
- distance: 'oklab' | 'rgb';
31
- }
32
- export interface DitherSettings {
33
- method: DitherMethod;
34
- /** 0–1 */
35
- strength: number;
36
- /** Threshold matrix for 'custom' (up to 16×16). Only the order of values matters. */
37
- matrix: number[][] | null;
38
- }
39
- export interface Settings {
40
- version: number;
41
- size: SizeSettings;
42
- frame: FrameSettings;
43
- adjust: AdjustSettings;
44
- palette: PaletteSettings;
45
- dither: DitherSettings;
46
- alpha: { threshold: number };
47
- cleanup: { orphans: boolean; smooth: boolean };
48
- outline: { mode: 'none' | 'outer' | 'inner'; color: string; corners: boolean };
49
- export: { size: 'scale' | 'source'; scale: number; indexed: boolean; grid: boolean; gridColor: string };
50
- }
51
- type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? (T[K] extends any[] ? T[K] : Partial<T[K]>) : T[K] };
52
- export type SettingsInput = DeepPartial<Settings>;
53
-
54
- export interface RGBAImage { width: number; height: number; data: ArrayLike<number> & { length: number } }
55
-
56
- export type Source =
57
- | HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | HTMLVideoElement | VideoFrame
58
- | ImageData | RGBAImage | Blob | string
59
- | { canvas: HTMLCanvasElement } | { elt: HTMLCanvasElement };
60
-
61
- export interface PNGOptions { scale?: number; grid?: boolean; gridColor?: string; indexed?: boolean; /** resize to exactly this size (with height) */ width?: number; height?: number }
62
-
63
- export interface PixelResult {
64
- width: number;
65
- height: number;
66
- /** RGBA, rows top-down. */
67
- data: Uint8ClampedArray;
68
- /** Palette used ('#rrggbb'), or null when colours were kept. */
69
- palette: string[] | null;
70
- settings: Settings;
71
- kind: 'none' | 'ordered' | 'diffusion';
72
- waves: number;
73
- /** The framed part of the source, as fractions of its width and height. */
74
- crop: { x: number; y: number; w: number; h: number };
75
- backend: 'webgl' | 'cpu';
76
- ms: number;
77
- usedColors(): string[];
78
- toImageData(): ImageData;
79
- toCanvas(scale?: number, opts?: { grid?: boolean; gridColor?: string; canvas?: HTMLCanvasElement }): HTMLCanvasElement;
80
- toPNG(opts?: PNGOptions): Promise<Uint8Array>;
81
- toBlob(opts?: PNGOptions): Promise<Blob>;
82
- }
83
-
84
- export interface PixelatorOptions {
85
- backend?: 'auto' | 'webgl' | 'cpu';
86
- diffusion?: 'auto' | 'gpu' | 'cpu';
87
- maxSourceSize?: number;
88
- }
89
- export interface Pixelator {
90
- readonly backend: 'webgl' | 'cpu';
91
- readonly renderer: string;
92
- readonly fallbackReason: string;
93
- /** The ShaderMate instance behind the GPU backend (null on the CPU). */
94
- readonly shader: unknown;
95
- process(source: Source, settings?: SettingsInput): Promise<PixelResult>;
96
- invalidate(): void;
97
- destroy(): void;
98
- }
99
-
100
- export function createPixelator(options?: PixelatorOptions): Promise<Pixelator>;
101
-
102
- export interface ClassicOptions {
103
- image: Source;
104
- width: number;
105
- dither?: string;
106
- /** 0–100 */
107
- strength?: number;
108
- palette?: string[] | string | null;
109
- resolution?: 'original' | 'pixel';
110
- }
111
- export function pixelate(options: ClassicOptions): Promise<HTMLCanvasElement>;
112
- export function pixelate(source: Source, settings?: SettingsInput): Promise<PixelResult>;
113
-
114
- export const VERSION: string;
115
- export const SETTINGS_VERSION: number;
116
- export const MAX_SIZE: number;
117
- export const DEFAULTS: Readonly<Settings>;
118
- export const DITHER_METHODS: { id: DitherMethod; label: string; kind: 'none' | 'ordered' | 'diffusion' }[];
119
- export const LEGACY_DITHER: Record<string, DitherMethod>;
120
- export function ditherKind(id: DitherMethod): 'none' | 'ordered' | 'diffusion';
121
- export function defaultSettings(): Settings;
122
- export function normalizeSettings(input?: SettingsInput): Settings;
123
- export function mergeSettings(base: SettingsInput, patch: SettingsInput): Settings;
124
- export const ASPECTS: { id: AspectId; label: string; ratio: number | null }[];
125
- /** The source pixels a frame keeps. */
126
- export function cropRect(srcWidth: number, srcHeight: number, frame?: Partial<FrameSettings>): { x: number; y: number; w: number; h: number };
127
- export function resolveSize(srcWidth: number, srcHeight: number, size: SizeSettings): { width: number; height: number };
128
- export function encodeSettings(settings: SettingsInput): string;
129
- export function decodeSettings(stringOrUrl: string): Settings;
130
- export function compactSettings(settings: SettingsInput): SettingsInput;
131
- export function settingsToJSON(settings: SettingsInput): string;
132
- export function settingsFromJSON(json: string | object): Settings;
133
-
134
- export interface Plan { width: number; height: number; crop: { x: number; y: number; w: number; h: number }; quantize: boolean; kind: 'none' | 'ordered' | 'diffusion'; palette: number[][]; waves: number; settings: Settings }
135
- export function makePlan(settings: SettingsInput, source: RGBAImage): Plan;
136
- export function runCPU(plan: Plan, source: RGBAImage): Uint8ClampedArray;
137
- export function makeResult(plan: Plan, data: Uint8ClampedArray, backend?: 'webgl' | 'cpu', ms?: number): PixelResult;
138
-
139
- export interface BuiltinPalette { id: string; name: string; author: string; colors: string[] }
140
- export const PALETTES: BuiltinPalette[];
141
- export function getPalette(idOrName: string): BuiltinPalette | null;
142
- export function parsePalette(text: string, filename?: string): { name: string; colors: string[] };
143
- export function toGPL(name: string, colors: string[]): string;
144
- export function toHexFile(colors: string[]): string;
145
- export function toPaletteJSON(name: string, colors: string[]): string;
146
- export function extractPalette(image: RGBAImage, count?: number, opts?: { maxSamples?: number; alphaThreshold?: number }): string[];
147
- export function fetchLospec(slugOrUrl: string, opts?: { fetch?: typeof fetch }): Promise<{ name: string; author: string; colors: string[]; slug: string }>;
148
- export function lospecSlug(input: string): string;
149
-
150
- export interface ThresholdMap { w: number; h: number; levels: number; values: Uint8Array }
151
- export function thresholdMap(method: DitherMethod, custom?: number[][] | null): ThresholdMap;
152
- export function parseMatrix(text: string): number[][];
153
- export function bayer(n: number): number[][];
154
- export const KERNELS: Record<string, [number, number, number][]>;
155
-
156
- export interface Preset { id: string; name: string; description: string; settings: SettingsInput }
157
- export const PRESETS: Preset[];
158
- export function getPreset(id: string): Preset | null;
159
-
160
- export function parseColor(c: string | number[]): [number, number, number];
161
- export function toHex(rgb: number[]): string;
162
- export function oklab(r: number, g: number, b: number): [number, number, number];
163
-
164
- export interface Frame { width: number; height: number; canvas: HTMLCanvasElement | null; data: ArrayLike<number> }
165
- export function toFrame(source: Source, opts?: { maxSize?: number; canvas?: HTMLCanvasElement }): Promise<Frame>;
166
- export function loadImage(src: string | Blob): Promise<HTMLImageElement | ImageBitmap>;
167
-
168
- export function upscale(image: RGBAImage, k?: number, opts?: { grid?: boolean; gridColor?: string; gridAlpha?: number }): { width: number; height: number; data: Uint8ClampedArray };
169
- export function resizeNearest(image: RGBAImage, width: number, height: number): { width: number; height: number; data: Uint8ClampedArray };
170
- export function uniqueColors(image: RGBAImage, limit?: number): string[];
171
- export function detectGrid(image: RGBAImage, tolerance?: number): number;
172
- export function hasTransparency(image: RGBAImage): boolean;
173
- export function toCanvas(image: RGBAImage, scale?: number, opts?: { grid?: boolean; gridColor?: string; canvas?: HTMLCanvasElement }): HTMLCanvasElement;
174
- export function downloadBlob(data: Blob | Uint8Array | string, filename: string, type?: string): void;
175
- export function encodePNG(image: RGBAImage, opts?: PNGOptions): Promise<Uint8Array>;
176
- export function encodeGIF(frames: (RGBAImage & { delay?: number })[], opts?: { loop?: number; scale?: number; delay?: number }): Uint8Array;
177
- export function createZip(entries: { name: string; data: Uint8Array | string; date?: Date }[]): Uint8Array;
178
- export function packSpriteSheet(frames: (RGBAImage & { name?: string; delay?: number })[], opts?: { columns?: number; padding?: number; name?: string }): { image: { width: number; height: number; data: Uint8ClampedArray }; atlas: any };
179
- export function buildTileset(image: RGBAImage, opts?: { tileWidth?: number; tileHeight?: number; columns?: number; flips?: boolean }): {
180
- image: { width: number; height: number; data: Uint8ClampedArray };
181
- map: { width: number; height: number; tileWidth: number; tileHeight: number; tiles: number[]; flips?: number[] };
182
- count: number;
183
- };
184
-
185
- export function videoReady(video: HTMLVideoElement, opts?: { timeout?: number }): Promise<HTMLVideoElement>;
186
- /** A readable Error for a <video>'s MediaError; `code` is the MediaError code. */
187
- export function mediaError(video: HTMLVideoElement): Error & { code: number; detail?: string };
188
- /** Best-effort codec name read from an MP4/MOV/WebM file's bytes ('' when unknown). */
189
- export function sniffVideoCodec(file: Blob): Promise<string>;
190
- export function seekTo(video: HTMLVideoElement, time: number): Promise<void>;
191
- export function estimateFps(video: HTMLVideoElement, fallback?: number): Promise<number>;
192
- export function frameTimes(opts: { duration: number; fps: number; start?: number; end?: number; maxFrames?: number }): number[];
193
- export function forEachFrame(video: HTMLVideoElement, opts: {
194
- fps: number; start?: number; end?: number; maxFrames?: number; signal?: AbortSignal;
195
- onFrame: (info: { index: number; time: number; total: number }) => unknown;
196
- }): Promise<number>;
1
+ // Type definitions for image-to-pixel 3.x
2
+
3
+ export type DitherMethod =
4
+ | 'none' | 'bayer2' | 'bayer4' | 'bayer8' | 'bayer16' | 'cluster4' | 'cluster8' | 'lines' | 'bluenoise' | 'custom'
5
+ | 'floyd-steinberg' | 'atkinson' | 'sierra-lite' | 'sierra' | 'burkes' | 'jarvis' | 'stucki';
6
+
7
+ export interface SizeSettings {
8
+ mode: 'width' | 'block';
9
+ /** Output width, 1–2048. */
10
+ width: number;
11
+ /** Output height; 0 keeps the aspect ratio. */
12
+ height: number;
13
+ /** Source pixels per output pixel when mode is 'block'. */
14
+ block: number;
15
+ resample: 'box' | 'nearest';
16
+ }
17
+ export interface AdjustSettings { brightness: number; contrast: number; saturation: number; /** degrees, -180..180 */ hue: number; gamma: number }
18
+ export type AspectId = 'original' | '1:1' | '4:3' | '3:4' | '3:2' | '2:3' | '16:9' | '9:16';
19
+ /** Which part of the source is used: the largest rectangle of `aspect`, divided by `zoom`, placed by x/y (0..1). */
20
+ export interface FrameSettings { aspect: AspectId; zoom: number; x: number; y: number }
21
+ export interface PaletteSettings {
22
+ source: 'builtin' | 'custom' | 'extract' | 'none';
23
+ /** Built-in palette id, e.g. 'pico-8'. */
24
+ id: string;
25
+ name: string;
26
+ /** '#rrggbb' strings. */
27
+ colors: string[];
28
+ /** Colours to extract when source is 'extract'. */
29
+ count: number;
30
+ distance: 'oklab' | 'rgb';
31
+ }
32
+ export interface DitherSettings {
33
+ method: DitherMethod;
34
+ /** 0–1 */
35
+ strength: number;
36
+ /** Threshold matrix for 'custom' (up to 16×16). Only the order of values matters. */
37
+ matrix: number[][] | null;
38
+ }
39
+ export interface Settings {
40
+ version: number;
41
+ size: SizeSettings;
42
+ frame: FrameSettings;
43
+ adjust: AdjustSettings;
44
+ palette: PaletteSettings;
45
+ dither: DitherSettings;
46
+ alpha: { threshold: number };
47
+ cleanup: { orphans: boolean; smooth: boolean };
48
+ outline: { mode: 'none' | 'outer' | 'inner'; color: string; corners: boolean };
49
+ export: { size: 'scale' | 'source'; scale: number; indexed: boolean; grid: boolean; gridColor: string };
50
+ }
51
+ type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? (T[K] extends any[] ? T[K] : Partial<T[K]>) : T[K] };
52
+ export type SettingsInput = DeepPartial<Settings>;
53
+
54
+ export interface RGBAImage { width: number; height: number; data: ArrayLike<number> & { length: number } }
55
+
56
+ export type Source =
57
+ | HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | HTMLVideoElement | VideoFrame
58
+ | ImageData | RGBAImage | Blob | string
59
+ | { canvas: HTMLCanvasElement } | { elt: HTMLCanvasElement };
60
+
61
+ export interface PNGOptions { scale?: number; grid?: boolean; gridColor?: string; indexed?: boolean; /** resize to exactly this size (with height) */ width?: number; height?: number }
62
+
63
+ export interface PixelResult {
64
+ width: number;
65
+ height: number;
66
+ /** RGBA, rows top-down. */
67
+ data: Uint8ClampedArray;
68
+ /** Palette used ('#rrggbb'), or null when colours were kept. */
69
+ palette: string[] | null;
70
+ settings: Settings;
71
+ kind: 'none' | 'ordered' | 'diffusion';
72
+ waves: number;
73
+ /** The framed part of the source, as fractions of its width and height. */
74
+ crop: { x: number; y: number; w: number; h: number };
75
+ backend: 'webgl' | 'cpu';
76
+ ms: number;
77
+ usedColors(): string[];
78
+ toImageData(): ImageData;
79
+ toCanvas(scale?: number, opts?: { grid?: boolean; gridColor?: string; canvas?: HTMLCanvasElement }): HTMLCanvasElement;
80
+ toPNG(opts?: PNGOptions): Promise<Uint8Array>;
81
+ toBlob(opts?: PNGOptions): Promise<Blob>;
82
+ }
83
+
84
+ export interface PixelatorOptions {
85
+ backend?: 'auto' | 'webgl' | 'cpu';
86
+ diffusion?: 'auto' | 'gpu' | 'cpu';
87
+ maxSourceSize?: number;
88
+ }
89
+ export interface Pixelator {
90
+ readonly backend: 'webgl' | 'cpu';
91
+ readonly renderer: string;
92
+ readonly fallbackReason: string;
93
+ /** The ShaderMate instance behind the GPU backend (null on the CPU). */
94
+ readonly shader: unknown;
95
+ process(source: Source, settings?: SettingsInput): Promise<PixelResult>;
96
+ invalidate(): void;
97
+ destroy(): void;
98
+ }
99
+
100
+ export function createPixelator(options?: PixelatorOptions): Promise<Pixelator>;
101
+
102
+ export interface ClassicOptions {
103
+ image: Source;
104
+ width: number;
105
+ dither?: string;
106
+ /** 0–100 */
107
+ strength?: number;
108
+ palette?: string[] | string | null;
109
+ resolution?: 'original' | 'pixel';
110
+ }
111
+ export function pixelate(options: ClassicOptions): Promise<HTMLCanvasElement>;
112
+ export function pixelate(source: Source, settings?: SettingsInput): Promise<PixelResult>;
113
+
114
+ export const VERSION: string;
115
+ export const SETTINGS_VERSION: number;
116
+ export const MAX_SIZE: number;
117
+ export const DEFAULTS: Readonly<Settings>;
118
+ export const DITHER_METHODS: { id: DitherMethod; label: string; kind: 'none' | 'ordered' | 'diffusion' }[];
119
+ export const LEGACY_DITHER: Record<string, DitherMethod>;
120
+ export function ditherKind(id: DitherMethod): 'none' | 'ordered' | 'diffusion';
121
+ export function defaultSettings(): Settings;
122
+ export function normalizeSettings(input?: SettingsInput): Settings;
123
+ export function mergeSettings(base: SettingsInput, patch: SettingsInput): Settings;
124
+ export const ASPECTS: { id: AspectId; label: string; ratio: number | null }[];
125
+ /** The source pixels a frame keeps. */
126
+ export function cropRect(srcWidth: number, srcHeight: number, frame?: Partial<FrameSettings>): { x: number; y: number; w: number; h: number };
127
+ export function resolveSize(srcWidth: number, srcHeight: number, size: SizeSettings): { width: number; height: number };
128
+ export function encodeSettings(settings: SettingsInput): string;
129
+ export function decodeSettings(stringOrUrl: string): Settings;
130
+ export function compactSettings(settings: SettingsInput): SettingsInput;
131
+ export function settingsToJSON(settings: SettingsInput): string;
132
+ export function settingsFromJSON(json: string | object): Settings;
133
+
134
+ export interface Plan { width: number; height: number; crop: { x: number; y: number; w: number; h: number }; quantize: boolean; kind: 'none' | 'ordered' | 'diffusion'; palette: number[][]; waves: number; settings: Settings }
135
+ export function makePlan(settings: SettingsInput, source: RGBAImage): Plan;
136
+ export function runCPU(plan: Plan, source: RGBAImage): Uint8ClampedArray;
137
+ export function makeResult(plan: Plan, data: Uint8ClampedArray, backend?: 'webgl' | 'cpu', ms?: number): PixelResult;
138
+
139
+ export interface BuiltinPalette { id: string; name: string; author: string; colors: string[] }
140
+ export const PALETTES: BuiltinPalette[];
141
+ export function getPalette(idOrName: string): BuiltinPalette | null;
142
+ export function parsePalette(text: string, filename?: string): { name: string; colors: string[] };
143
+ export function toGPL(name: string, colors: string[]): string;
144
+ export function toHexFile(colors: string[]): string;
145
+ export function toPaletteJSON(name: string, colors: string[]): string;
146
+ export function extractPalette(image: RGBAImage, count?: number, opts?: { maxSamples?: number; alphaThreshold?: number }): string[];
147
+ export function fetchLospec(slugOrUrl: string, opts?: { fetch?: typeof fetch }): Promise<{ name: string; author: string; colors: string[]; slug: string }>;
148
+ export function lospecSlug(input: string): string;
149
+
150
+ export interface ThresholdMap { w: number; h: number; levels: number; values: Uint8Array }
151
+ export function thresholdMap(method: DitherMethod, custom?: number[][] | null): ThresholdMap;
152
+ export function parseMatrix(text: string): number[][];
153
+ export function bayer(n: number): number[][];
154
+ export const KERNELS: Record<string, [number, number, number][]>;
155
+
156
+ export interface Preset { id: string; name: string; description: string; settings: SettingsInput }
157
+ export const PRESETS: Preset[];
158
+ export function getPreset(id: string): Preset | null;
159
+
160
+ export function parseColor(c: string | number[]): [number, number, number];
161
+ export function toHex(rgb: number[]): string;
162
+ export function oklab(r: number, g: number, b: number): [number, number, number];
163
+
164
+ export interface Frame { width: number; height: number; canvas: HTMLCanvasElement | null; data: ArrayLike<number> }
165
+ export function toFrame(source: Source, opts?: { maxSize?: number; canvas?: HTMLCanvasElement }): Promise<Frame>;
166
+ export function loadImage(src: string | Blob): Promise<HTMLImageElement | ImageBitmap>;
167
+
168
+ export function upscale(image: RGBAImage, k?: number, opts?: { grid?: boolean; gridColor?: string; gridAlpha?: number }): { width: number; height: number; data: Uint8ClampedArray };
169
+ export function resizeNearest(image: RGBAImage, width: number, height: number): { width: number; height: number; data: Uint8ClampedArray };
170
+ export function uniqueColors(image: RGBAImage, limit?: number): string[];
171
+ export function detectGrid(image: RGBAImage, tolerance?: number): number;
172
+ export function hasTransparency(image: RGBAImage): boolean;
173
+ export function toCanvas(image: RGBAImage, scale?: number, opts?: { grid?: boolean; gridColor?: string; canvas?: HTMLCanvasElement }): HTMLCanvasElement;
174
+ export function downloadBlob(data: Blob | Uint8Array | string, filename: string, type?: string): void;
175
+ export function encodePNG(image: RGBAImage, opts?: PNGOptions): Promise<Uint8Array>;
176
+ export function encodeGIF(frames: (RGBAImage & { delay?: number })[], opts?: { loop?: number; scale?: number; delay?: number }): Uint8Array;
177
+ export function createZip(entries: { name: string; data: Uint8Array | string; date?: Date }[]): Uint8Array;
178
+ export function packSpriteSheet(frames: (RGBAImage & { name?: string; delay?: number })[], opts?: { columns?: number; padding?: number; name?: string }): { image: { width: number; height: number; data: Uint8ClampedArray }; atlas: any };
179
+ export function buildTileset(image: RGBAImage, opts?: { tileWidth?: number; tileHeight?: number; columns?: number; flips?: boolean }): {
180
+ image: { width: number; height: number; data: Uint8ClampedArray };
181
+ map: { width: number; height: number; tileWidth: number; tileHeight: number; tiles: number[]; flips?: number[] };
182
+ count: number;
183
+ };
184
+
185
+ export function videoReady(video: HTMLVideoElement, opts?: { timeout?: number }): Promise<HTMLVideoElement>;
186
+ /** A readable Error for a <video>'s MediaError; `code` is the MediaError code. */
187
+ export function mediaError(video: HTMLVideoElement): Error & { code: number; detail?: string };
188
+ /** Best-effort codec name read from an MP4/MOV/WebM file's bytes ('' when unknown). */
189
+ export function sniffVideoCodec(file: Blob): Promise<string>;
190
+ export function seekTo(video: HTMLVideoElement, time: number): Promise<void>;
191
+ export function estimateFps(video: HTMLVideoElement, fallback?: number): Promise<number>;
192
+ export function frameTimes(opts: { duration: number; fps: number; start?: number; end?: number; maxFrames?: number }): number[];
193
+ export function forEachFrame(video: HTMLVideoElement, opts: {
194
+ fps: number; start?: number; end?: number; maxFrames?: number; signal?: AbortSignal;
195
+ onFrame: (info: { index: number; time: number; total: number }) => unknown;
196
+ }): Promise<number>;
package/types/video.d.ts CHANGED
@@ -11,33 +11,89 @@ export interface VideoEncoderOptions {
11
11
  /** Whole-number upscale applied to every frame. */
12
12
  scale?: number;
13
13
  bitrate?: number;
14
+ /**
15
+ * Soundtrack to include, e.g. from decodeAudio(file, { start, end }). Cut to the video's length.
16
+ * MP4 gets AAC (or Opus where AAC can't be encoded), WebM gets Opus. If the browser can't encode
17
+ * either, the video is made without it: check `audioCodec` on the encoder.
18
+ */
19
+ audio?: PCMAudio | null;
20
+ /** Bits per second for the audio. Default 128000. */
21
+ audioBitrate?: number;
14
22
  }
23
+ /** Planar 32-bit float PCM, one array per channel. */
24
+ export interface PCMAudio {
25
+ sampleRate: number;
26
+ numberOfChannels: number;
27
+ channels: Float32Array[];
28
+ /** Seconds (informational). */
29
+ duration?: number;
30
+ }
31
+ export type AudioCodec = 'aac' | 'opus';
15
32
  export interface PixelVideoEncoder {
16
33
  readonly width: number;
17
34
  readonly height: number;
18
35
  readonly codec: string;
36
+ /** The audio codec used, or null when there is no soundtrack (none given, or none this browser can encode). */
37
+ readonly audioCodec: AudioCodec | null;
19
38
  add(frame: RGBAImage): Promise<void>;
20
39
  finish(): Promise<Blob>;
21
40
  }
22
41
  export function createVideoEncoder(opts: VideoEncoderOptions): Promise<PixelVideoEncoder>;
23
42
  export function encodeVideo(frames: RGBAImage[], opts: Omit<VideoEncoderOptions, 'width' | 'height'>): Promise<Blob>;
24
- export function videoSupport(width?: number, height?: number): Promise<{ mp4: boolean; webm: boolean; webcodecs: boolean }>;
43
+ export function videoSupport(width?: number, height?: number): Promise<{ mp4: boolean; webm: boolean; webcodecs: boolean; aac: boolean; opus: boolean }>;
44
+
45
+ /**
46
+ * Decode a file's audio track to PCM (resampled, default 48 kHz; mixed down to at most 2 channels; trimmed to
47
+ * start/end in seconds). Resolves with null when there is no audio track, it can't be decoded, or there is no
48
+ * Web Audio (Node).
49
+ */
50
+ export function decodeAudio(
51
+ source: Blob | ArrayBuffer | ArrayBufferView | string | HTMLMediaElement,
52
+ opts?: { start?: number; end?: number; sampleRate?: number; channels?: 1 | 2 },
53
+ ): Promise<PCMAudio | null>;
54
+ /** Encode PCM with WebCodecs' AudioEncoder; chunks go to onChunk (e.g. a muxer's addAudioChunk). */
55
+ export function encodeAudio(
56
+ pcm: PCMAudio,
57
+ opts: { codec?: AudioCodec; config?: AudioEncoderConfig; bitrate?: number; maxDuration?: number; onChunk: (chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata) => void },
58
+ ): Promise<void>;
59
+ export function audioCodecSupported(codec: AudioCodec, sampleRate?: number, numberOfChannels?: number): Promise<boolean>;
60
+ /** An AudioEncoder configuration this browser accepts for `codec` (common rates and bitrates are tried), or null. */
61
+ export function findAudioConfig(codec: AudioCodec, sampleRate?: number, numberOfChannels?: number, bitrate?: number): Promise<AudioEncoderConfig | null>;
62
+
63
+ /** The audio track a muxer should add. */
64
+ export interface MuxerAudioTrack {
65
+ codec: AudioCodec;
66
+ sampleRate: number;
67
+ numberOfChannels: number;
68
+ /** MP4 only: bitrate written into the AAC decoder config. */
69
+ bitrate?: number;
70
+ }
25
71
 
26
- /** Minimal MP4 muxer (one H.264 track, fast start). Feed it VideoEncoder output, or AVCC samples. */
72
+ /** Minimal MP4 muxer (one H.264 track and an optional AAC or Opus track, fast start). Feed it encoder output, or raw samples. */
27
73
  export interface Mp4Muxer {
28
74
  addChunk(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): void;
29
75
  /** timestamp/duration in microseconds; data is length-prefixed NAL units. */
30
76
  addSample(data: Uint8Array, info: { key: boolean; timestamp: number; duration?: number }): void;
31
77
  /** AVCDecoderConfigurationRecord ('avcC'). */
32
78
  setDescription(avcC: Uint8Array): void;
79
+ /** AudioEncoder output (needs the `audio` option). */
80
+ addAudioChunk(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void;
81
+ /** One encoded audio frame, in order. timestamp/duration in microseconds. */
82
+ addAudioSample(data: Uint8Array, info: { timestamp: number; duration?: number }): void;
83
+ /** AAC AudioSpecificConfig (for Opus, an optional OpusHead to read the pre-skip from). */
84
+ setAudioDescription(config: Uint8Array): void;
33
85
  finish(): Uint8Array;
34
86
  }
35
- export function createMp4Muxer(opts: { width: number; height: number; timescale?: number }): Mp4Muxer;
36
- /** Minimal WebM muxer (one VP8/VP9/AV1 track, with SeekHead and Cues). */
87
+ export function createMp4Muxer(opts: { width: number; height: number; timescale?: number; audio?: MuxerAudioTrack | null }): Mp4Muxer;
88
+ /** Minimal WebM muxer (one VP8/VP9/AV1 track and an optional Opus track, with SeekHead and Cues). */
37
89
  export interface WebmMuxer {
38
90
  addChunk(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): void;
39
91
  /** timestamp in microseconds */
40
92
  addFrame(data: Uint8Array, info: { key: boolean; timestamp: number }): void;
93
+ /** AudioEncoder output (needs the `audio` option). */
94
+ addAudioChunk(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void;
95
+ /** One Opus packet, in order. timestamp/duration in microseconds. */
96
+ addAudioFrame(data: Uint8Array, info: { timestamp: number; duration?: number }): void;
41
97
  finish(): Uint8Array;
42
98
  }
43
- export function createWebmMuxer(opts: { width: number; height: number; codec?: 'vp8' | 'vp9' | 'av1'; fps?: number }): WebmMuxer;
99
+ export function createWebmMuxer(opts: { width: number; height: number; codec?: 'vp8' | 'vp9' | 'av1'; fps?: number; audio?: (MuxerAudioTrack & { codec: 'opus' }) | null }): WebmMuxer;