wtfgif 1.2.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/BENCHMARKS.md ADDED
@@ -0,0 +1,185 @@
1
+ # Benchmarks
2
+
3
+ Performance claims are only meaningful when the backend, cache state, fixtures,
4
+ and correctness checks are explicit.
5
+
6
+ ## MakeEmoji pixel-perfect timing export
7
+
8
+ This reproduces MakeEmoji's current export after its preview frames have been
9
+ decoded: each RGBA frame is quantized with MakeEmoji's `image-q` settings and
10
+ written with `omggif`. It compares that path with a one-shot wtfgif timing edit
11
+ and with a GIF compiled while the upload loads.
12
+
13
+ ```bash
14
+ BENCH_ITERATIONS=5 BENCH_WARMUP_ITERATIONS=2 \
15
+ npm run bench:makeemoji-retime
16
+ ```
17
+
18
+ Five measured samples per implementation and fixture, with two warmups:
19
+
20
+ | Fixture | One-shot parse + retime | Page-load compiled retime |
21
+ | --- | ---: | ---: |
22
+ | 18d | 1,110.2x | 4,225.9x |
23
+ | Clap | 321.6x | 951.5x |
24
+ | Homer | 9,418.9x | 29,954.7x |
25
+ | Chipmunk | 6,706.7x | 27,473.8x |
26
+ | GIGACHAD | 5,386.7x | 12,998.1x |
27
+ | NODDERS | 1,414.5x | 3,158.2x |
28
+ | Proud | 5,005.4x | 32,190.9x |
29
+ | catJAM | 3,953.6x | 6,151.6x |
30
+ | excuseme | 4,922.4x | 10,105.7x |
31
+ | party_blob | 7,268.6x | 16,010.2x |
32
+ | partyparrot | 600.4x | 1,643.9x |
33
+ | tenor | 26,241.3x | 72,672.1x |
34
+ | **Geometric mean** | **3,338.5x** | **9,469.6x** |
35
+
36
+ Every wtfgif output had the requested delays and exact composited RGBA pixels.
37
+ The unit suite separately verifies that original compressed frame data is
38
+ retained. MakeEmoji's current quantize-and-reencode output was pixel-perfect on
39
+ only 2 of 12 fixtures.
40
+
41
+ This result comes from doing less work, not from a faster quantizer: timing
42
+ changes do not require decoding or recompressing the image stream.
43
+
44
+ The page-load compiled column is the intended web path and the >100x contract.
45
+ Cold one-shot results on tiny GIFs are more sensitive to JavaScript startup,
46
+ JIT, and microsecond timer noise; a separate three-sample filtered run observed
47
+ a 59.3x one-shot outlier while the compiled edit remained 782.5x.
48
+
49
+ The same benchmark exercises strict reverse and MakeEmoji-style boomerang on
50
+ fixtures whose full-canvas/disposal structure proves that frame order is
51
+ independent:
52
+
53
+ | Operation | Slowest accepted fixture | Geometric mean |
54
+ | --- | ---: | ---: |
55
+ | Reverse | 5,329.5x | 18,054.3x |
56
+ | Boomerang | 6,866.7x | 21,720.1x |
57
+
58
+ The structural path compares each output frame with its mapped source frame,
59
+ including exact composited RGBA and delay values. Fixtures that cannot be
60
+ proven independent are rejected and excluded, rather than silently rewritten.
61
+
62
+ ## Experimental cold pixel-perfect reencoding
63
+
64
+ The current research checkpoint starts a fresh process for each sample and
65
+ reencodes every GIF image descriptor with the experimental native addon. The
66
+ timer excludes the decoded-pixel verification that follows each sample. Every
67
+ output and source GIF is then decoded and compared; any pixel mismatch aborts
68
+ the run.
69
+
70
+ Command:
71
+
72
+ ```bash
73
+ BENCH_ITERATIONS=31 BENCH_REENCODE_ONLY=1 npm run bench
74
+ ```
75
+
76
+ 31 fresh-process samples per implementation and fixture:
77
+
78
+ | Fixture | wtfgif faster than omggif |
79
+ | --- | ---: |
80
+ | 18d | 142.65x |
81
+ | Clap | 100.97x |
82
+ | Homer | 191.85x |
83
+ | Chip | 200.35x |
84
+ | GIG | 180.09x |
85
+ | NOD | 111.37x |
86
+ | Proud | 229.79x |
87
+ | cat | 180.53x |
88
+ | excuse | 117.74x |
89
+ | blob | 126.32x |
90
+ | parrot | 149.29x |
91
+ | tenor | 214.14x |
92
+ | Geometric mean | 156.69x |
93
+
94
+ These are experimental-addon results on the development machine, not a promise
95
+ for the portable npm package or other hardware. The 1.0.0 npm artifact ships
96
+ JavaScript and WebAssembly; it does not ship a prebuilt native addon.
97
+
98
+ ## Page-load-prepared portable pixel-perfect remux
99
+
100
+ This is the web product benchmark. Wasm initialization and generic hot-path
101
+ preparation happen before the clock, as they would during page load. Each timed
102
+ sample is still the first real GIF processed in a fresh process. Preparation
103
+ uses unrelated generated GIFs and cannot cache a fixture's input or output.
104
+
105
+ wtfgif validates and rewrites the GIF container while preserving original LZW
106
+ frame payloads. omggif decodes and freshly recompresses the frames. The
107
+ externally tested contract is the same—an owned GIF with identical decoded
108
+ pixels—but the algorithms intentionally differ.
109
+
110
+ ```bash
111
+ BENCH_ITERATIONS=31 BENCH_REENCODE_ONLY=1 \
112
+ WTFGIF_BENCH_BACKEND=wasm \
113
+ WTFGIF_PREPARE_WASM_AT_PAGE_LOAD=1 \
114
+ WTFGIF_REENCODE_MODE=remux npm run bench
115
+ ```
116
+
117
+ | Fixture | wtfgif faster than omggif |
118
+ | --- | ---: |
119
+ | 18d | 738.64x |
120
+ | Clap | 309.84x |
121
+ | Homer | 983.53x |
122
+ | Chip | 768.77x |
123
+ | GIG | 607.23x |
124
+ | NOD | 512.23x |
125
+ | Proud | 681.13x |
126
+ | cat | 519.37x |
127
+ | excuse | 480.06x |
128
+ | blob | 955.88x |
129
+ | parrot | 244.09x |
130
+ | tenor | 1,238.09x |
131
+ | Geometric mean | 609.73x |
132
+
133
+ Every fixture clears 100x. The slowest is partyparrot at 244.09x.
134
+
135
+ The excluded initialization plus generic preparation cost was measured
136
+ separately across 31 fresh processes on the same machine: 8.64 ms median,
137
+ 9.86 ms p95, and 9.91 ms maximum.
138
+
139
+ ## Cold portable Rust/WebAssembly reencoding
140
+
141
+ The same pixel-perfect Rust reencoder now runs in browsers, Cloudflare Workers,
142
+ Vercel Edge, and Node through WebAssembly.
143
+
144
+ ```bash
145
+ BENCH_ITERATIONS=31 BENCH_REENCODE_ONLY=1 \
146
+ WTFGIF_BENCH_BACKEND=wasm npm run bench
147
+ ```
148
+
149
+ | Fixture | wtfgif faster than omggif |
150
+ | --- | ---: |
151
+ | 18d | 8.19x |
152
+ | Clap | 1.23x |
153
+ | Homer | 14.68x |
154
+ | Chip | 17.70x |
155
+ | GIG | 15.44x |
156
+ | NOD | 8.60x |
157
+ | Proud | 20.00x |
158
+ | cat | 14.42x |
159
+ | excuse | 9.60x |
160
+ | blob | 9.74x |
161
+ | parrot | 3.38x |
162
+ | tenor | 16.00x |
163
+ | Geometric mean | 9.45x |
164
+
165
+ These are first-call numbers in 31 fresh Node processes per implementation and
166
+ fixture. They include Wasm's first-call cost and use no warmup. Browser and edge
167
+ support is separately exercised by real-browser, Cloudflare workerd, and Vercel
168
+ Edge VM smoke tests.
169
+
170
+ Page-load preparation improves fresh LZW recompression, but it does not remove
171
+ the actual LZW decode and encode work. Profiling found decoding responsible for
172
+ roughly 57–85% of its hot runtime and literal reencoding for roughly 15–43%.
173
+ The remux API avoids both stages by preserving the original compressed payload.
174
+
175
+ ## Correctness gates
176
+
177
+ - Source and reencoded decoded pixels must match exactly for every timed sample.
178
+ - Source and remuxed decoded pixels must match exactly for every timed sample.
179
+ - A randomized parity stress run covered 200 generated GIFs across 4, 16, 64,
180
+ 128, and 256 colors, global palettes, and identical local palettes.
181
+ - Unit tests compare reader and writer behavior with omggif across the fixture
182
+ corpus.
183
+
184
+ Record new results here only with the exact command, sample count, environment,
185
+ backend, and correctness gate.
package/CHANGELOG.md ADDED
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ All notable changes to wtfgif are documented here.
4
+
5
+ ## 1.2.0 - 2026-07-19
6
+
7
+ - Add `compileGif()`, an immutable compiled-GIF representation that retains
8
+ original palettes, frame rectangles, extensions, and compressed LZW data.
9
+ - Add `CompiledGif.withDelays()` and `retimeGifPixelPerfect()` for timing edits
10
+ that patch metadata without decoding, quantizing, or recompressing pixels.
11
+ - Add `CompiledGif.withLoop()` with Netscape and ANIMEXTS loop support.
12
+ - Add strict `reverseFrames()` and MakeEmoji-compatible `boomerangFrames()`
13
+ linker paths for structurally independent full-canvas animations.
14
+ - Preserve exact pixels and compressed frame payloads across the real fixture
15
+ corpus, including local palettes, interlacing, transparency, disposal,
16
+ comments, and application extensions.
17
+ - Add a MakeEmoji-shaped timing export benchmark. The page-load-compiled path
18
+ measured at least 951x faster on every fixture.
19
+ - Benchmark proven-safe reverse at least 5,329x faster and boomerang at least
20
+ 6,866x faster while checking exact mapped pixels and delays.
21
+
22
+ ## 1.1.0 - 2026-07-19
23
+
24
+ - Run the pixel-perfect one-shot Rust reencoder and all-frame RGBA decoder
25
+ through WebAssembly in browsers and edge runtimes.
26
+ - Add static Wasm initialization for Cloudflare Workers and Vercel Edge.
27
+ - Add `remuxGifPixelPerfect()`, a portable lossless Rust/Wasm path that
28
+ preserves original compressed frame payloads and exceeds 100x on every
29
+ page-load-prepared benchmark fixture.
30
+ - Prepare generic Wasm and public API hot paths during explicit page-load
31
+ initialization.
32
+ - Add explicit `rust` and `wasm` encoder backend choices plus fast-backend
33
+ status reporting.
34
+ - Validate real reencoding and decoded-pixel parity in the packaged browser
35
+ build, Cloudflare workerd, Vercel Edge VM, and the full GIF corpus.
36
+ - Add an interactive GitHub Pages race across all 12 benchmark fixtures with
37
+ live browser timing and post-timer full RGBA parity verification.
38
+
39
+ ## 1.0.0 - 2026-07-19
40
+
41
+ First public release.
42
+
43
+ ### Highlights
44
+
45
+ - Drop-in `GifReader` and `GifWriter` APIs for core omggif usage, including
46
+ plain arrays, typed arrays, Node buffers, CommonJS, ESM, and TypeScript.
47
+ - Portable JavaScript fallback plus packaged WebAssembly builds for Node and
48
+ browsers.
49
+ - Higher-level helpers for indexed and RGBA frame encoding, composited frame
50
+ preparation, delta frames, and configurable compression.
51
+ - Pixel-parity tests against omggif and installed-tarball validation across
52
+ CommonJS, ESM, browser exports, and WebAssembly loading.
53
+ - Experimental injectable Node addon for one-off decode and pixel-perfect
54
+ reencoding research. Prebuilt native binaries are not part of 1.0.0.
@@ -0,0 +1,27 @@
1
+ # Contributing
2
+
3
+ Bug reports and focused pull requests are welcome.
4
+
5
+ ## Setup
6
+
7
+ wtfgif requires Node 20.16 or newer and the stable Rust toolchain. Install
8
+ `wasm-pack`, then install dependencies:
9
+
10
+ ```bash
11
+ cargo install wasm-pack --locked
12
+ npm ci
13
+ ```
14
+
15
+ ## Before opening a pull request
16
+
17
+ Run the complete release gate:
18
+
19
+ ```bash
20
+ npm run check
21
+ ```
22
+
23
+ Changes to decoding or encoding must include a regression test. Compatibility
24
+ changes should compare behavior with omggif when applicable. Performance
25
+ changes should preserve decoded pixels and report the command, fixture set, and
26
+ whether the measurement used a warm process, cold process, WebAssembly, or the
27
+ experimental native addon.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Matt Popovich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # wtfgif
2
+
3
+ An `omggif`-compatible GIF reader and writer, plus pixel-perfect fast paths
4
+ that avoid rebuilding pixels when the requested edit does not require it.
5
+
6
+ **[Run the browser benchmark →](https://mpopv.github.io/wtfgif/)**
7
+
8
+ ```bash
9
+ npm install wtfgif
10
+ ```
11
+
12
+ ## Replace omggif
13
+
14
+ For the core `GifReader` and `GifWriter` API, change the import:
15
+
16
+ ```diff
17
+ - import { GifReader, GifWriter } from "omggif";
18
+ + import { GifReader, GifWriter } from "wtfgif";
19
+ ```
20
+
21
+ CommonJS and TypeScript are supported:
22
+
23
+ ```js
24
+ const { GifReader, GifWriter } = require("wtfgif");
25
+ ```
26
+
27
+ This is the compatibility path. It accepts the same indexed pixels, palettes,
28
+ frame rectangles, delays, transparency, and disposal options used by omggif.
29
+ It does not promise a 100x speedup for arbitrary new pixels.
30
+
31
+ ## The 100x fast paths
32
+
33
+ wtfgif is fastest when pixels are already valid GIF data and the edit can be
34
+ expressed as a container change:
35
+
36
+ | Operation | Fixtures | Slowest speedup | Correctness |
37
+ | --- | ---: | ---: | --- |
38
+ | Compiled timing edit | 12/12 | **951.5x** | Exact pixels, delays, and original LZW payloads |
39
+ | Prepared Wasm remux | 12/12 | **244.09x** | Exact decoded animation |
40
+ | Strict frame reverse | 2/12 eligible | **5,329.5x** | Exact mapped pixels and delays |
41
+ | Strict boomerang | 2/12 eligible | **6,866.7x** | Exact mapped pixels and delays |
42
+
43
+ These are not claims that every wtfgif call is 100x faster. They are results
44
+ for the named operations across the accepted benchmark fixtures. Timing,
45
+ reverse, and boomerang are compared with MakeEmoji's `image-q` + `omggif`
46
+ export; remux is compared with `omggif` decode + fresh encode. See
47
+ [BENCHMARKS.md](BENCHMARKS.md) for commands, fixtures, sample counts, cold
48
+ results, and correctness gates.
49
+
50
+ ## Edit timing without touching pixels
51
+
52
+ Compile once when a GIF is loaded:
53
+
54
+ ```ts
55
+ import { compileGif } from "wtfgif";
56
+
57
+ const gif = compileGif(inputGif);
58
+ ```
59
+
60
+ Export new frame delays later. GIF delays are measured in centiseconds:
61
+
62
+ ```ts
63
+ const outputGif = gif
64
+ .withDelays(Uint16Array.of(4, 8, 12, 8))
65
+ .toUint8Array();
66
+ ```
67
+
68
+ `compileGif()` retains the original GIF structure and compressed image data.
69
+ `withDelays()` changes only timing metadata, inserting a graphics control
70
+ extension when one is missing. It never decodes, quantizes, or recompresses a
71
+ frame.
72
+
73
+ For a one-shot timing edit:
74
+
75
+ ```ts
76
+ import { retimeGifPixelPerfect } from "wtfgif";
77
+
78
+ const outputGif = retimeGifPixelPerfect(inputGif, delaysInCentiseconds);
79
+ ```
80
+
81
+ These timing APIs are synchronous JavaScript. They need no Wasm initialization
82
+ and run in Node, browsers, Web Workers, Cloudflare Workers, and Vercel Edge.
83
+
84
+ ## Reverse or boomerang proven-independent frames
85
+
86
+ ```ts
87
+ const gif = compileGif(inputGif);
88
+
89
+ if (gif.canReorderFrames) {
90
+ const reversed = gif.reverseFrames();
91
+ const boomerang = gif.boomerangFrames();
92
+ // A, B, C becomes A, B, C, C, B, A
93
+ }
94
+ ```
95
+
96
+ This path reorders complete compressed frame records. It deliberately rejects
97
+ GIFs whose appearance can depend on the preceding frame, including unsafe
98
+ partial-frame composition or metadata anchored between frames. A rejection is
99
+ a request to use a real decode-and-render pipeline, not a malformed output.
100
+
101
+ ## Remux with Wasm
102
+
103
+ Initialize Wasm once per JavaScript realm during page or worker startup:
104
+
105
+ ```ts
106
+ import {
107
+ initializeWasmGlobally,
108
+ remuxGifPixelPerfect,
109
+ } from "wtfgif";
110
+
111
+ await initializeWasmGlobally();
112
+
113
+ const outputGif = remuxGifPixelPerfect(inputGif);
114
+ ```
115
+
116
+ `remuxGifPixelPerfect()` validates and rewrites the GIF container while
117
+ preserving palettes and compressed frame payloads. It guarantees the displayed
118
+ animation: dimensions, frames, timing, loop behavior, disposal, transparency,
119
+ and decoded pixels. Non-rendering extension metadata such as comments is not
120
+ part of its contract.
121
+
122
+ Cloudflare Workers and Vercel Edge require static Wasm imports:
123
+
124
+ ```ts
125
+ import {
126
+ initializeWasmModule,
127
+ remuxGifPixelPerfect,
128
+ } from "wtfgif";
129
+ import init, * as rust from "wtfgif/wasm-web";
130
+ import wasm from "wtfgif/wasm-web/wasm";
131
+
132
+ await initializeWasmModule({ ...rust, default: init }, wasm);
133
+ ```
134
+
135
+ Initialization is not global across a website: the window and each Worker,
136
+ Node process, or edge isolate are separate realms and must initialize their
137
+ own module.
138
+
139
+ ## What is not 100x
140
+
141
+ - `GifReader` and `GifWriter` are drop-in compatibility APIs, not universal
142
+ 100x APIs.
143
+ - Encoding arbitrary new pixels still requires palette selection and LZW
144
+ compression. Wasm initialization cannot make that work disappear.
145
+ - `reencodeGifPixelPerfect()` performs fresh LZW encoding and is substantially
146
+ slower than remuxing.
147
+ - Cold startup is excluded from the prepared-Wasm and compiled-edit results.
148
+ The measured Wasm initialization and preparation cost was 8.64 ms median;
149
+ one tiny-GIF timing run also produced a 59.3x cold one-shot outlier.
150
+ - Reverse and boomerang are fast only when wtfgif can prove that raw frame
151
+ reordering preserves the rendered animation.
152
+
153
+ The speedup comes from recognizing when an edit does not need a pixel pipeline.
154
+ Instead of decode → quantize → recompress, wtfgif validates the structure and
155
+ copies already-correct compressed bytes.
156
+
157
+ ## Verify
158
+
159
+ ```bash
160
+ npm run check
161
+
162
+ BENCH_ITERATIONS=5 BENCH_WARMUP_ITERATIONS=2 \
163
+ npm run bench:makeemoji-retime
164
+ ```
165
+
166
+ [MIT](LICENSE)
@@ -0,0 +1,77 @@
1
+ import type { GifBinary } from "./types";
2
+ /**
3
+ * A delay in GIF centiseconds, or one delay for every image frame.
4
+ */
5
+ export type GifFrameDelays = number | ArrayLike<number>;
6
+ /**
7
+ * A parsed GIF whose compressed image data and unrelated metadata can be
8
+ * copied without decoding or re-encoding pixels.
9
+ *
10
+ * Instances are immutable. Transformations share the parsed source layout;
11
+ * bytes are copied only when `toUint8Array()` is called.
12
+ */
13
+ export declare class CompiledGif {
14
+ readonly width: number;
15
+ readonly height: number;
16
+ readonly frameCount: number;
17
+ readonly delays: readonly number[];
18
+ readonly loopCount: number | null;
19
+ readonly canReorderFrames: boolean;
20
+ private readonly layout;
21
+ private readonly requestedDelays;
22
+ private readonly requestedLoopCount;
23
+ private readonly hasLoopOverride;
24
+ private constructor();
25
+ /** Parse a GIF into an immutable compiled representation. */
26
+ static from(input: GifBinary): CompiledGif;
27
+ /**
28
+ * Return a compiled view with new per-frame delays.
29
+ *
30
+ * A scalar applies to every frame. An array-like value must have exactly
31
+ * one entry per image frame. Values are integer centiseconds in
32
+ * the GIF range 0...65535.
33
+ */
34
+ withDelays(delays: GifFrameDelays): CompiledGif;
35
+ /**
36
+ * Return a compiled view with a Netscape loop count.
37
+ *
38
+ * `0` means loop forever. If the source has no Netscape extension, a
39
+ * canonical extension is inserted without touching image data.
40
+ */
41
+ withLoop(loopCount: number): CompiledGif;
42
+ /**
43
+ * Reorder or duplicate structurally independent frames without decoding.
44
+ *
45
+ * This strict fast path accepts full-canvas frames that are either all
46
+ * opaque or all restore a canonical base with disposal 2/3. It throws for
47
+ * GIFs whose frame composition could depend on the original order.
48
+ */
49
+ reorderFrames(order: ArrayLike<number>): Uint8Array;
50
+ /** Reverse all frames through the proven-safe structural fast path. */
51
+ reverseFrames(): Uint8Array;
52
+ /**
53
+ * Append the complete reverse sequence, matching MakeEmoji's boomerang
54
+ * semantics: `[A, B, C, C, B, A]`.
55
+ */
56
+ boomerangFrames(): Uint8Array;
57
+ /**
58
+ * Materialize the transformed GIF.
59
+ *
60
+ * Existing delays and loop counts are patched in place. A frame without a
61
+ * graphic-control extension receives the minimal eight-byte extension.
62
+ */
63
+ toUint8Array(): Uint8Array;
64
+ }
65
+ /**
66
+ * Parse a GIF once so metadata-only transformations can reuse its original
67
+ * palettes, image descriptors, extensions, and compressed LZW sub-blocks.
68
+ */
69
+ export declare function compileGif(input: GifBinary): CompiledGif;
70
+ /**
71
+ * Rewrite frame delays without decoding or recompressing a single pixel.
72
+ */
73
+ export declare function retimeGifPixelPerfect(input: GifBinary, delays: GifFrameDelays): Uint8Array;
74
+ /** Reverse structurally independent GIF frames without touching pixel data. */
75
+ export declare function reverseGifPixelPerfect(input: GifBinary): Uint8Array;
76
+ /** Create a MakeEmoji-style forward-then-reverse GIF without pixel work. */
77
+ export declare function boomerangGifPixelPerfect(input: GifBinary): Uint8Array;
@@ -0,0 +1,17 @@
1
+ export declare const enum GIF {
2
+ G = 71,
3
+ I = 73,
4
+ F = 70,
5
+ _8 = 56,
6
+ _9 = 57,
7
+ A = 97,
8
+ EXT = 33,
9
+ IMG = 44,
10
+ TRAILER = 59,
11
+ GCE = 249,
12
+ APPLICATION = 255,
13
+ PLAINTEXT = 1,
14
+ COMMENT = 254,
15
+ NETSCAPE_LEN = 11,
16
+ MAX_CODE = 4096
17
+ }
@@ -0,0 +1,2 @@
1
+ import { PooledDecoderTables } from "../types";
2
+ export declare function createDecoderTables(): PooledDecoderTables;
@@ -0,0 +1,80 @@
1
+ import { FrameInfo, GifBinary, GifDecodeBackend, GifDecodeBackendStatus, GifPixelBuffer, PreparedGifFrames, PrepareFramesOptions } from "../types";
2
+ export declare class GifReader {
3
+ private buf;
4
+ private width_;
5
+ private height_;
6
+ private globalPaletteOffset;
7
+ private globalPaletteSize;
8
+ private frames;
9
+ private loop_count;
10
+ private pooledTables;
11
+ private decTable;
12
+ private stack;
13
+ private firstByte;
14
+ private activePreparedFrames;
15
+ private static decodeBackend;
16
+ static createPooled(buf: GifBinary): GifReader;
17
+ static createUnpooled(buf: GifBinary): GifReader;
18
+ static setDecodeBackend(backend: GifDecodeBackend | null): void;
19
+ static getDecodeBackendStatus(): GifDecodeBackendStatus;
20
+ constructor(buf: GifBinary, _usePooling?: boolean);
21
+ get width(): number;
22
+ get height(): number;
23
+ /**
24
+ * Simplified LZW decoder that outputs palette indices instead of RGBA
25
+ */
26
+ private lzwDecodeToIndices;
27
+ numFrames(): number;
28
+ loopCount(): number | null;
29
+ frameInfo(i: number): FrameInfo;
30
+ private ensureDecoderTables;
31
+ private getFrameCodes;
32
+ private getFramePalette;
33
+ private getFrameIndices;
34
+ decodeAndBlitFrameBGRA(frameNum: number, pixels: GifPixelBuffer): void;
35
+ decodeAndBlitFrameRGBA(frameNum: number, pixels: GifPixelBuffer): void;
36
+ decodeFrameToTransferableRGBA(frameNum: number): ArrayBuffer;
37
+ decodeFrameToTransferableBGRA(frameNum: number): ArrayBuffer;
38
+ decodeFrameIntoBuffer(frameNum: number, buffer: ArrayBuffer, format?: "rgba" | "bgra"): void;
39
+ preparePlayback(options?: Omit<PrepareFramesOptions, "composited">): PreparedGifFrames;
40
+ prepareFrames(options?: PrepareFramesOptions): PreparedGifFrames;
41
+ prepareFramesAsync(options?: PrepareFramesOptions): Promise<PreparedGifFrames>;
42
+ decodeAndBlitCompositedFrameRGBA(frameNum: number, pixels: Uint8Array): void;
43
+ decodeAndBlitCompositedFrameBGRA(frameNum: number, pixels: Uint8Array): void;
44
+ private normalizePrepareFramesOptions;
45
+ private normalizeFrameIndices;
46
+ private prepareFramesWithBackend;
47
+ private createBackendPreparedFramesResult;
48
+ private prepareCompositedFrames;
49
+ private prepareUncompositedFrames;
50
+ private createPreparedFramesResult;
51
+ private createPreparedPlayer;
52
+ private copyPreparedFrame;
53
+ private copyPreparedFrame32;
54
+ private blitPreparedColors;
55
+ private blitPreparedIndices;
56
+ private getPreparedTarget32;
57
+ private applyFrameDisposal;
58
+ private clearFrameRect;
59
+ private static findChangedRect;
60
+ private static copyRectPixels;
61
+ private static blitRectPixels;
62
+ private enforcePreparedByteBudget;
63
+ private static hashPixels;
64
+ private static findMatchingPixels;
65
+ private static pixelsEqual;
66
+ dispose(): void;
67
+ private clearFrameCaches;
68
+ returnToPool(): void;
69
+ static getPoolStats(): {
70
+ available: number;
71
+ totalCreated: number;
72
+ hits: number;
73
+ misses: number;
74
+ };
75
+ private decodeAndBlitFrame32;
76
+ private decodeAndBlitCompatibleFrame;
77
+ private getFrameColors;
78
+ private blitFrameIndicesToCanvas;
79
+ private lzwDecodeToPixels;
80
+ }
@@ -0,0 +1,59 @@
1
+ import { FrameOptions, GifBinary, GifOptions, PaletteRGB } from "../types";
2
+ export type IndexedGifFrame = Uint8Array | number[];
3
+ export type IndexedGifFrames = Uint8Array | IndexedGifFrame[];
4
+ export type EncodeIndexedGifFramesBackend = "auto" | "rust" | "wasm" | "native" | "javascript";
5
+ export type RgbaGifFrame = Uint8Array | Uint8ClampedArray;
6
+ export type RgbaGifFrames = Uint8Array | Uint8ClampedArray | RgbaGifFrame[];
7
+ export type GifFrameDelay = number | readonly number[] | Uint16Array;
8
+ export type GifCompressionMode = "balanced" | "fast";
9
+ export interface EncodeIndexedGifFramesOptions {
10
+ width: number;
11
+ height: number;
12
+ frames: IndexedGifFrames;
13
+ frameCount?: number;
14
+ palette: PaletteRGB;
15
+ delay?: GifFrameDelay;
16
+ loop?: number | null;
17
+ backend?: EncodeIndexedGifFramesBackend;
18
+ delta?: boolean;
19
+ compression?: GifCompressionMode;
20
+ }
21
+ export interface EncodeRgbaGifFramesOptions {
22
+ width: number;
23
+ height: number;
24
+ frames: RgbaGifFrames;
25
+ frameCount?: number;
26
+ palette?: PaletteRGB;
27
+ delay?: GifFrameDelay;
28
+ loop?: number | null;
29
+ backend?: EncodeIndexedGifFramesBackend;
30
+ delta?: boolean;
31
+ alphaThreshold?: number;
32
+ compression?: GifCompressionMode;
33
+ }
34
+ type IndexedPixels = IndexedGifFrame;
35
+ export declare function encodeIndexedGifFrames(options: EncodeIndexedGifFramesOptions): Uint8Array;
36
+ export declare function encodeRgbaGifFrames(options: EncodeRgbaGifFramesOptions): Uint8Array;
37
+ export declare class GifWriter {
38
+ private buf;
39
+ readonly width: number;
40
+ readonly height: number;
41
+ private p;
42
+ private ended;
43
+ private loopCount;
44
+ private globalPalette;
45
+ private background;
46
+ private globalColorCount;
47
+ private previousIndexedFrame;
48
+ constructor(buf: GifBinary, width: number, height: number, gopts?: GifOptions);
49
+ addFrame(x: number, y: number, w: number, h: number, indexedPixels: IndexedPixels, opts?: FrameOptions): number;
50
+ addFrameDelta(indexedPixels: IndexedPixels, opts?: FrameOptions): number;
51
+ private writeFrame;
52
+ end(): number;
53
+ getOutputBuffer(): GifBinary;
54
+ setOutputBuffer(v: GifBinary): void;
55
+ getOutputBufferPosition(): number;
56
+ setOutputBufferPosition(v: number): void;
57
+ private updatePreviousIndexedFrame;
58
+ }
59
+ export {};