@cjser/sindresorhus__gifkit 0.1.0-cjser.2

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/index.js ADDED
@@ -0,0 +1,118 @@
1
+ import {decodeGIF} from './source/decode.js';
2
+ import {encodeGIF} from './source/encode.js';
3
+ import {renderGIFFrames} from './source/render.js';
4
+ import {buildQuantizedIndexedImage} from './source/quantize.js';
5
+ import {
6
+ isUint8Array,
7
+ isUint8ClampedArray,
8
+ requireNonZeroUnsigned16,
9
+ requireFiniteNumberInRange,
10
+ requirePositiveFiniteNumber,
11
+ normalizeRedGreenBlueAlphaPixels,
12
+ } from './source/validate.js';
13
+ import {rejectRenamedLoopCount} from './source/loop-count.js';
14
+
15
+ export {decodeGIF} from './source/decode.js';
16
+ export {encodeGIF} from './source/encode.js';
17
+ export {renderGIFFrameSequence, renderGIFFrames} from './source/render.js';
18
+ export {indexedImage} from './source/quantize.js';
19
+
20
+ export function decodeAnimatedGIF(inputBytes, options = {}) {
21
+ const gif = decodeGIF(inputBytes, options);
22
+ const rendered = renderGIFFrames(gif, {
23
+ background: options.background ?? 'transparent',
24
+ strict: options.strict,
25
+ });
26
+
27
+ return {
28
+ width: rendered.width,
29
+ height: rendered.height,
30
+ ...(rendered.playCount !== undefined && {playCount: rendered.playCount}),
31
+ frames: rendered.frames.map(frame => ({
32
+ pixels: frame.pixels,
33
+ delay: frame.delay,
34
+ })),
35
+ };
36
+ }
37
+
38
+ export function encodeAnimatedGIF(frames, options = {}) {
39
+ if (!Array.isArray(frames) || frames.length === 0) {
40
+ throw new TypeError('frames must be a non-empty array');
41
+ }
42
+
43
+ const width = requireNonZeroUnsigned16(options.width, 'width');
44
+ const height = requireNonZeroUnsigned16(options.height, 'height');
45
+ const quality = requireFiniteNumberInRange(options.quality ?? 0.8, 0, 1, 'quality');
46
+ rejectRenamedLoopCount(options);
47
+ const hasFrameDelays = frames.some(frame => isAnimationFrameObject(frame) && frame.delay !== undefined);
48
+ if (options.fps !== undefined && hasFrameDelays) {
49
+ throw new Error('fps cannot be combined with per-frame delay');
50
+ }
51
+
52
+ if (options.fps === undefined && frames.some(frame => !isAnimationFrameObject(frame) || frame.delay === undefined)) {
53
+ throw new Error('fps is required unless every frame has a delay');
54
+ }
55
+
56
+ const frameDelay = options.fps === undefined
57
+ ? undefined
58
+ : 1 / requirePositiveFiniteNumber(options.fps, 'fps');
59
+
60
+ const blocks = frames.map((frame, index) => {
61
+ const pixels = normalizeAnimationFramePixels(frame, width * height * 4, `frames[${index}]`);
62
+ const delay = frameDelay ?? requireFiniteNumberInRange(frame.delay, 0, 655.35, `frames[${index}].delay`);
63
+ const imageBlock = {
64
+ type: 'rgbaImage',
65
+ width,
66
+ height,
67
+ graphicControlExtension: {
68
+ delay,
69
+ },
70
+ };
71
+
72
+ if (quality === 1) {
73
+ return {
74
+ ...imageBlock,
75
+ pixels,
76
+ };
77
+ }
78
+
79
+ const quantizedImage = buildQuantizedIndexedImage(pixels, {quality});
80
+ return {
81
+ type: 'image',
82
+ width,
83
+ height,
84
+ pixels: quantizedImage.pixels,
85
+ colorTable: quantizedImage.colorTable,
86
+ graphicControlExtension: {
87
+ delay,
88
+ transparentColorIndex: quantizedImage.transparentColorIndex,
89
+ },
90
+ };
91
+ });
92
+
93
+ const gif = {
94
+ width,
95
+ height,
96
+ blocks,
97
+ };
98
+ if (options.playCount !== undefined) {
99
+ gif.playCount = options.playCount;
100
+ }
101
+
102
+ return encodeGIF(gif);
103
+ }
104
+
105
+ function normalizeAnimationFramePixels(frame, expectedLength, fieldName) {
106
+ const pixels = isAnimationFrameObject(frame)
107
+ ? frame.pixels
108
+ : frame;
109
+ return normalizeRedGreenBlueAlphaPixels(pixels, expectedLength, fieldName);
110
+ }
111
+
112
+ function isAnimationFrameObject(frame) {
113
+ return frame !== null
114
+ && typeof frame === 'object'
115
+ && !isUint8Array(frame)
116
+ && !isUint8ClampedArray(frame)
117
+ && !Array.isArray(frame);
118
+ }
package/license ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@cjser/sindresorhus__gifkit",
3
+ "version": "0.1.0-cjser.2",
4
+ "description": "Encode, decode, and render GIF files",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://code.moenext.com/3rdeye/cjser.git"
9
+ },
10
+ "funding": "https://github.com/sponsors/sindresorhus",
11
+ "author": {
12
+ "name": "Sindre Sorhus",
13
+ "email": "sindresorhus@gmail.com",
14
+ "url": "https://sindresorhus.com"
15
+ },
16
+ "type": "module",
17
+ "exports": {
18
+ "types": "./index.d.ts",
19
+ "require": "./dist-cjser/index.cjs",
20
+ "default": "./index.js"
21
+ },
22
+ "sideEffects": false,
23
+ "engines": {
24
+ "node": ">=22"
25
+ },
26
+ "scripts": {
27
+ "benchmark": "node benchmark.js",
28
+ "test": "xo && node --test && tsd"
29
+ },
30
+ "files": [
31
+ "index.js",
32
+ "index.d.ts",
33
+ "source",
34
+ "dist-cjser"
35
+ ],
36
+ "keywords": [
37
+ "gif",
38
+ "graphics",
39
+ "image",
40
+ "encoder",
41
+ "decoder",
42
+ "render"
43
+ ],
44
+ "devDependencies": {
45
+ "jimp": "^1.6.1",
46
+ "tempy": "^3.2.0",
47
+ "tsd": "^0.33.0",
48
+ "xo": "^4.0.0"
49
+ },
50
+ "xo": {
51
+ "rules": {
52
+ "no-bitwise": "off",
53
+ "max-lines": "off",
54
+ "unicorn/no-break-in-nested-loop": "off",
55
+ "unicorn/no-useless-spread": "off",
56
+ "node-test/no-conditional-assertion": "off"
57
+ }
58
+ },
59
+ "types": "./index.d.ts",
60
+ "main": "./dist-cjser/index.cjs",
61
+ "cjser": {
62
+ "sourceVersion": "0.1.0",
63
+ "cjserVersion": 2,
64
+ "original": {
65
+ "name": "@sindresorhus/gifkit",
66
+ "version": "0.1.0",
67
+ "exports": {
68
+ "types": "./index.d.ts",
69
+ "default": "./index.js"
70
+ },
71
+ "repository": "sindresorhus/gifkit",
72
+ "files": [
73
+ "index.js",
74
+ "index.d.ts",
75
+ "source"
76
+ ],
77
+ "scripts": {
78
+ "benchmark": "node benchmark.js",
79
+ "test": "xo && node --test && tsd"
80
+ }
81
+ }
82
+ }
83
+ }
package/readme.md ADDED
@@ -0,0 +1,294 @@
1
+ # gifkit
2
+
3
+ > Encode, decode, and render GIF files
4
+
5
+ This package is a small JavaScript GIF toolkit. It can decode GIF87a/GIF89a files into structured blocks, encode structured GIF descriptions back to bytes, and render image blocks to RGBA pixels.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @sindresorhus/gifkit
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```js
16
+ import {decodeAnimatedGIF, encodeAnimatedGIF} from '@sindresorhus/gifkit';
17
+
18
+ const bytes = encodeAnimatedGIF(frames, {
19
+ width: 640,
20
+ height: 480,
21
+ fps: 14,
22
+ playCount: 5,
23
+ quality: 0.7,
24
+ });
25
+
26
+ const animation = decodeAnimatedGIF(bytes);
27
+
28
+ console.log(animation.frames[0].pixels);
29
+ console.log(animation.frames[0].delay);
30
+ ```
31
+
32
+ > [!NOTE]
33
+ > If you accept untrusted input in a server context, it's up to you to enforce limits like timeouts and memory usage.
34
+
35
+ ## Common recipes
36
+
37
+ ### Encode RGBA frames to a GIF
38
+
39
+ ```js
40
+ import {encodeAnimatedGIF} from '@sindresorhus/gifkit';
41
+
42
+ const bytes = encodeAnimatedGIF(frames, {
43
+ width: 640,
44
+ height: 480,
45
+ fps: 14,
46
+ playCount: 5,
47
+ quality: 0.7,
48
+ });
49
+ ```
50
+
51
+ GIF stores frame timing in 0.01 second increments, so `fps` and `delay` values are rounded.
52
+
53
+ For photos and screenshots, keep the default `quality` or lower it. Use `quality: 1` only when every frame already has at most 256 exact colors.
54
+
55
+ ### Decode a GIF to RGBA frames
56
+
57
+ ```js
58
+ import {decodeAnimatedGIF} from '@sindresorhus/gifkit';
59
+
60
+ const animation = decodeAnimatedGIF(bytes);
61
+
62
+ console.log(animation.width);
63
+ console.log(animation.height);
64
+ console.log(animation.frames[0].pixels);
65
+ console.log(animation.frames[0].delay);
66
+ ```
67
+
68
+ ### Decode and encode again
69
+
70
+ ```js
71
+ import {decodeAnimatedGIF, encodeAnimatedGIF} from '@sindresorhus/gifkit';
72
+
73
+ const animation = decodeAnimatedGIF(bytes);
74
+
75
+ const options = {
76
+ width: animation.width,
77
+ height: animation.height,
78
+ };
79
+
80
+ if (animation.playCount !== undefined) {
81
+ options.playCount = animation.playCount;
82
+ }
83
+
84
+ const newBytes = encodeAnimatedGIF(animation.frames, options);
85
+ ```
86
+
87
+ ### Use per-frame delays
88
+
89
+ ```js
90
+ import {encodeAnimatedGIF} from '@sindresorhus/gifkit';
91
+
92
+ const bytes = encodeAnimatedGIF([
93
+ {pixels: frame1, delay: 0.1},
94
+ {pixels: frame2, delay: 0.2},
95
+ ], {
96
+ width: 640,
97
+ height: 480,
98
+ playCount: 5,
99
+ quality: 0.7,
100
+ });
101
+ ```
102
+
103
+ ## API
104
+
105
+ ### `decodeGIF(inputBytes, options?)`
106
+
107
+ Decodes a `Uint8Array` containing a GIF file and returns a structured GIF object with logical-screen metadata, extension blocks, image blocks, color tables, and decoded indexed pixels.
108
+
109
+ Options:
110
+
111
+ - `strict` - Default: `true`. Reject reserved bits, malformed extension sequencing, and trailing bytes. Use `false` for best-effort decoding.
112
+
113
+ Decoding enforces internal pixel, block-count, data sub-block-count, and data payload byte limits to avoid resource exhaustion.
114
+
115
+ ### `decodeAnimatedGIF(inputBytes, options?)`
116
+
117
+ Decodes an animated GIF to rendered full-frame RGBA frames. This is the easiest API when you want image buffers and frame timing instead of GIF internals.
118
+
119
+ ```js
120
+ import {decodeAnimatedGIF} from '@sindresorhus/gifkit';
121
+
122
+ const animation = decodeAnimatedGIF(bytes);
123
+
124
+ console.log(animation.width);
125
+ console.log(animation.height);
126
+ console.log(animation.playCount);
127
+ console.log(animation.frames[0].pixels);
128
+ console.log(animation.frames[0].delay);
129
+ ```
130
+
131
+ Returns:
132
+
133
+ - `width` / `height` - Logical screen size.
134
+ - `playCount` - Total animation plays. `'forever'` means infinite playback. Omitted when no loop extension was present.
135
+ - `frames` - Rendered full-frame RGBA frames.
136
+ - `frames[].pixels` - `Uint8ClampedArray` of flat RGBA bytes: `[red, green, blue, alpha, ...]`.
137
+ - `frames[].delay` - Frame delay in seconds. GIF stores delays in 0.01 second increments.
138
+
139
+ Options:
140
+
141
+ - `background` - Default: `'transparent'`. Use `'gif'` to render the logical-screen background color.
142
+ - `strict` - Default: `true`. Reject malformed decode data and render data like color indexes outside the active color table. Use `false` for best-effort decoding and rendering.
143
+
144
+ ### `encodeAnimatedGIF(frames, options)`
145
+
146
+ Encodes RGBA frames as an animated GIF. This is the easiest API when you have a list of image buffers and want a normal animation.
147
+
148
+ ```js
149
+ import {encodeAnimatedGIF} from '@sindresorhus/gifkit';
150
+
151
+ const bytes = encodeAnimatedGIF(frames, {
152
+ width: 640,
153
+ height: 480,
154
+ fps: 14,
155
+ playCount: 5,
156
+ quality: 0.7,
157
+ });
158
+ ```
159
+
160
+ Options:
161
+
162
+ - `width` / `height` - Required. Frame size.
163
+ - `fps` - Frames per second for uniform timing. Cannot be combined with per-frame `delay`. GIF stores delays in 0.01 second increments, so timing is rounded to the nearest increment.
164
+ - `playCount` - Total animation plays. Finite values must be integers from `1` to `65_536`. Use `'forever'` for infinite playback. Omit it to omit the loop extension. `playCount: 1` also omits the loop extension because that matches default GIF playback.
165
+ - `quality` - Default: `0.8`. `0...1`, where lower values quantize each frame more aggressively to fit GIF’s 256-color palette. Quantization is per-frame and does not dither. For photos and screenshots, keep the default or lower it. Use `1` only when every frame already has at most 256 exact colors.
166
+
167
+ Frames can be `Uint8Array` or `Uint8ClampedArray` RGBA pixels. Pixels are flat RGBA bytes: `[red, green, blue, alpha, ...]`.
168
+
169
+ For per-frame timing, use frame objects with `pixels` and `delay` in seconds. Delays are rounded to GIF’s 0.01 second increments:
170
+
171
+ ```js
172
+ const bytes = encodeAnimatedGIF([
173
+ {pixels: frame1, delay: 0.1},
174
+ {pixels: frame2, delay: 0.2},
175
+ ], {
176
+ width: 640,
177
+ height: 480,
178
+ playCount: 5,
179
+ quality: 0.7,
180
+ });
181
+ ```
182
+
183
+ ### `encodeGIF(gif)`
184
+
185
+ Encodes a structured GIF object to a `Uint8Array`. Use `image` blocks when you already have indexed pixels and a palette. Use `rgbaImage` blocks when you have flat RGBA bytes that fit GIF’s 256-color model.
186
+
187
+ The `gif` object has this shape:
188
+
189
+ - `width` / `height` - Required. Logical screen size.
190
+ - `globalColorTable` - Optional. Global palette as flat RGB bytes (`[red, green, blue, ...]`), a `Uint8Array`, or RGB triplets (`[[red, green, blue], ...]`).
191
+ - `backgroundColorIndex` - Optional. Palette index used as the logical-screen background color. Requires `globalColorTable`.
192
+ - `playCount` - Total animation plays. Finite values must be integers from `1` to `65_536`. Use `'forever'` for infinite playback. Omit it to omit the loop extension. `playCount: 1` also omits the loop extension because that matches default GIF playback.
193
+ - `blocks` - Structured GIF blocks in file order.
194
+
195
+ Encoded GIFs are always written as GIF89a.
196
+
197
+ Block types:
198
+
199
+ - Indexed image block: `{type: 'image', width, height, pixels, colorTable?}`. `pixels` is one palette index per pixel and uses the image `colorTable` or GIF `globalColorTable`. `left` and `top` are optional image offsets and default to `0`. Advanced fields include `isInterlaced`.
200
+ - RGBA image block: `{type: 'rgbaImage', width, height, pixels}`. `pixels` is flat RGBA bytes: `[red, green, blue, alpha, ...]`. gifkit builds a color table from the pixels, so this only works when the image has at most 256 colors and alpha values are fully transparent or fully opaque. `transparentColor` is the RGB palette value stored for transparent pixels.
201
+ - Graphic control metadata for an image block: `graphicControlExtension: {disposalMethod?, delay?, transparentColorIndex?}`. `delay` is in seconds. `transparentColorIndex` is a palette index, or `undefined` for no transparent color. `disposalMethod` is exposed so structured GIF data can be preserved or re-encoded. It controls what happens to the current frame’s pixels before the next frame is drawn: `'unspecified'` leaves the choice to the decoder, `'keep'` keeps the pixels, `'restoreBackground'` clears them to the background, and `'restorePrevious'` restores the previous canvas. Rendered pixels already have this applied, so most users do not need to read or set it.
202
+ - Comment extension: `{type: 'commentExtension', data}`. Strings must be ASCII.
203
+ - Application extension: `{type: 'applicationExtension', identifier, authenticationCode, data?}`. `authenticationCode` accepts a 3-byte string or byte array. Decoded Netscape loop extensions also expose `isNetscapeLoopingExtension` and `playCount`. When encoding an explicit Netscape application extension block, finite `playCount` values must be from `2` to `65_536` because `1` is represented by omitting the loop extension.
204
+
205
+ Encoding enforces internal pixel, block-count, encode work cost, data payload byte, and total encoded byte limits to avoid resource exhaustion.
206
+
207
+ Extension payload strings must contain only ASCII characters. Use `Uint8Array` for binary extension data.
208
+
209
+ ### `renderGIFFrameSequence(gif, options?)`
210
+
211
+ Renders image blocks as an iterable sequence of full logical-screen RGBA frames while applying transparency and disposal methods. Use this for playback or large GIFs where materializing every rendered frame would be wasteful.
212
+
213
+ ```js
214
+ import {decodeGIF, renderGIFFrameSequence} from '@sindresorhus/gifkit';
215
+
216
+ const gif = decodeGIF(bytes, {strict: false});
217
+
218
+ for (const frame of renderGIFFrameSequence(gif, {strict: false})) {
219
+ console.log(frame.pixels);
220
+ console.log(frame.delay);
221
+ }
222
+ ```
223
+
224
+ Options:
225
+
226
+ - `background` - Default: `'gif'`. Use `'transparent'` to render the logical-screen background as transparent.
227
+ - `strict` - Default: `true`. Reject malformed render data like color indexes outside the active color table.
228
+ - `repeat` - Default: `true`. Repeat according to GIF `playCount` metadata. Missing metadata means one pass, a finite number means that many total passes, and `'forever'` means infinite playback. Set to `false` to render exactly one pass.
229
+ - `signal` - Optional abort signal. When aborted, iteration ends.
230
+
231
+ Yields:
232
+
233
+ - `left` / `top` - Frame source offset in logical-screen pixels.
234
+ - `width` / `height` - Frame source size.
235
+ - `delay` - Frame delay in seconds.
236
+ - `disposalMethod` - Advanced GIF compositing metadata kept so rendered frames can still be related back to the original GIF structure.
237
+ - `pixels` - `Uint8ClampedArray` of rendered full logical-screen RGBA bytes.
238
+ - `index` - Zero-based image-frame index within the current pass.
239
+ - `loopIndex` - Zero-based repetition index for this frame.
240
+
241
+ ### `renderGIFFrames(gif, options?)`
242
+
243
+ Renders image blocks to full logical-screen RGBA frames while applying transparency and disposal methods. Most users should use `decodeAnimatedGIF()` instead; use this when you need decoded GIF structure and rendered pixels.
244
+
245
+ Options:
246
+
247
+ - `background` - Default: `'gif'`. Use `'transparent'` to render the logical-screen background as transparent.
248
+ - `strict` - Default: `true`. Reject malformed render data like color indexes outside the active color table.
249
+
250
+ Returns:
251
+
252
+ - `width` / `height` - Logical screen size.
253
+ - `playCount` - Total animation plays. `'forever'` means infinite playback. Omitted when no loop extension was present.
254
+ - `frames` - Rendered full logical-screen RGBA frames.
255
+ - `frames[].left` / `frames[].top` - Frame source offset in logical-screen pixels.
256
+ - `frames[].width` / `frames[].height` - Frame source size.
257
+ - `frames[].delay` - Frame delay in seconds.
258
+ - `frames[].disposalMethod` - Advanced GIF compositing metadata kept so rendered frames can still be related back to the original GIF structure. `'unspecified'` leaves the choice to the decoder, `'keep'` keeps the pixels, `'restoreBackground'` clears them to the background, and `'restorePrevious'` restores the previous canvas before the next frame is drawn. gifkit already applies this when producing `frames[].pixels`, so use it only if you need to inspect or preserve the original animation metadata.
259
+ - `frames[].pixels` - `Uint8ClampedArray` of rendered full logical-screen RGBA bytes.
260
+
261
+ Rendering enforces internal logical-screen, rendered-output, render block-count, and image-block pixel limits to avoid huge allocations.
262
+
263
+ ### `indexedImage(pixels, options?)`
264
+
265
+ Use this when you have raw RGBA pixels, for example from a canvas or image decoder, and want the `pixels` and `colorTable` needed for an image block. It only works when the pixels already fit GIF’s palette model.
266
+
267
+ Converts RGBA pixels to GIF indexed pixels and a power-of-two color table without quantizing or dithering. Each unique opaque RGB color becomes a palette entry. All fully transparent pixels share one palette entry and set `transparentColorIndex`.
268
+
269
+ Throws if the image exceeds the internal pixel limit, uses more than 256 palette entries, or uses partial alpha. GIF only supports fully transparent or fully opaque pixels.
270
+
271
+ Options:
272
+
273
+ - `transparentColor` - Default: `[0, 0, 0]`. RGB value stored in the palette entry used for transparent pixels.
274
+
275
+ ```js
276
+ import {indexedImage} from '@sindresorhus/gifkit';
277
+
278
+ const image = indexedImage(new Uint8ClampedArray([
279
+ 255, 0, 0, 255,
280
+ 0, 0, 0, 0,
281
+ ]));
282
+
283
+ console.log(image.pixels);
284
+ console.log(image.colorTable);
285
+ ```
286
+
287
+ ## Intentionally unsupported GIF spec features
288
+
289
+ gifkit focuses on GIF features that are useful in modern JavaScript workflows. It intentionally does not expose the GIF user-input flag, pixel aspect ratio byte, color-resolution metadata, color-table sort flags, or Plain Text Extension rendering/encoding. These are legacy display-era features, are rarely present in real GIFs, and are easy to misunderstand. Unknown extensions, including Plain Text Extensions, are preserved as `unknownExtension` blocks.
290
+
291
+ ## cjser
292
+
293
+ This package is a CommonJS-compatible build generated by cjser for projects that still need `require()` support. The source version matches the original npm package version, with a cjser prerelease suffix for this generated build.
294
+ Original repository: https://github.com/sindresorhus/gifkit
@@ -0,0 +1,141 @@
1
+ import {
2
+ asciiTextEncoder,
3
+ asciiTextDecoder,
4
+ uint8ArraySubarray,
5
+ defaultMaximumEncodedByteLength,
6
+ } from './constants.js';
7
+ import {
8
+ isUint8Array,
9
+ toUint8ArrayView,
10
+ requireByte,
11
+ requireUnsigned16,
12
+ requireByteLengthWithinLimit,
13
+ } from './validate.js';
14
+
15
+ export class GIFByteReader {
16
+ #inputBytes;
17
+ #offset = 0;
18
+ #length;
19
+
20
+ constructor(inputBytes) {
21
+ if (!isUint8Array(inputBytes)) {
22
+ throw new TypeError('Expected inputBytes to be a Uint8Array');
23
+ }
24
+
25
+ this.#inputBytes = toUint8ArrayView(inputBytes);
26
+ this.#length = this.#inputBytes.length;
27
+ }
28
+
29
+ get offset() {
30
+ return this.#offset;
31
+ }
32
+
33
+ get length() {
34
+ return this.#length;
35
+ }
36
+
37
+ readByte() {
38
+ if (this.#offset >= this.#length) {
39
+ throw new Error('Unexpected end of data');
40
+ }
41
+
42
+ const value = this.#inputBytes[this.#offset];
43
+ this.#offset += 1;
44
+ return value;
45
+ }
46
+
47
+ readBytes(length) {
48
+ if (!Number.isSafeInteger(length) || length < 0) {
49
+ throw new Error(`Invalid byte length ${length}`);
50
+ }
51
+
52
+ if (this.#offset + length > this.#length) {
53
+ throw new Error('Unexpected end of data');
54
+ }
55
+
56
+ const value = Uint8Array.from(uint8ArraySubarray.call(this.#inputBytes, this.#offset, this.#offset + length));
57
+ this.#offset += length;
58
+ return value;
59
+ }
60
+
61
+ readBytesInto(target, targetOffset, length) {
62
+ if (!Number.isSafeInteger(length) || length < 0) {
63
+ throw new Error(`Invalid byte length ${length}`);
64
+ }
65
+
66
+ if (this.#offset + length > this.#length) {
67
+ throw new Error('Unexpected end of data');
68
+ }
69
+
70
+ target.set(uint8ArraySubarray.call(this.#inputBytes, this.#offset, this.#offset + length), targetOffset);
71
+ this.#offset += length;
72
+ }
73
+
74
+ readUnsignedLittleEndian16() {
75
+ const leastSignificantByte = this.readByte();
76
+ const mostSignificantByte = this.readByte();
77
+ return leastSignificantByte | (mostSignificantByte << 8);
78
+ }
79
+
80
+ readAsciiString(length) {
81
+ return asciiTextDecoder.decode(this.readBytes(length));
82
+ }
83
+ }
84
+
85
+ export class GIFByteWriter {
86
+ #bytes = new Uint8Array(1024);
87
+ #byteLength = 0;
88
+
89
+ #ensureCapacity(requiredCapacity) {
90
+ if (requiredCapacity <= this.#bytes.length) {
91
+ return;
92
+ }
93
+
94
+ const nextCapacity = Math.min(defaultMaximumEncodedByteLength, Math.max(requiredCapacity, this.#bytes.length * 2));
95
+ const nextBytes = new Uint8Array(nextCapacity);
96
+ nextBytes.set(uint8ArraySubarray.call(this.#bytes, 0, this.#byteLength));
97
+ this.#bytes = nextBytes;
98
+ }
99
+
100
+ writeByte(value) {
101
+ requireByteLengthWithinLimit(this.#byteLength + 1, defaultMaximumEncodedByteLength, 'encoded GIF');
102
+ this.#ensureCapacity(this.#byteLength + 1);
103
+ this.#bytes[this.#byteLength] = requireByte(value, 'byte');
104
+ this.#byteLength += 1;
105
+ }
106
+
107
+ writeBytes(values) {
108
+ if (isUint8Array(values)) {
109
+ const bytes = toUint8ArrayView(values);
110
+ requireByteLengthWithinLimit(this.#byteLength + bytes.length, defaultMaximumEncodedByteLength, 'encoded GIF');
111
+ this.#ensureCapacity(this.#byteLength + bytes.length);
112
+ this.#bytes.set(bytes, this.#byteLength);
113
+ this.#byteLength += bytes.length;
114
+ return;
115
+ }
116
+
117
+ requireByteLengthWithinLimit(this.#byteLength + values.length, defaultMaximumEncodedByteLength, 'encoded GIF');
118
+ this.#ensureCapacity(this.#byteLength + values.length);
119
+ for (const value of values) {
120
+ this.writeByte(value);
121
+ }
122
+ }
123
+
124
+ writeUnsignedLittleEndian16(value) {
125
+ const normalizedValue = requireUnsigned16(value, 'unsigned16');
126
+ this.writeByte(normalizedValue & 0xFF);
127
+ this.writeByte((normalizedValue >> 8) & 0xFF);
128
+ }
129
+
130
+ writeAsciiString(value) {
131
+ const encodedValue = asciiTextEncoder.encode(value);
132
+
133
+ for (const byte of encodedValue) {
134
+ this.writeByte(byte);
135
+ }
136
+ }
137
+
138
+ toUint8Array() {
139
+ return this.#bytes.slice(0, this.#byteLength);
140
+ }
141
+ }
@@ -0,0 +1,36 @@
1
+ export const asciiTextEncoder = new TextEncoder();
2
+ export const asciiTextDecoder = new TextDecoder('ascii', {fatal: false});
3
+ export const uint8ArraySubarray = Uint8Array.prototype.subarray;
4
+
5
+ export const extensionIntroducer = 0x21;
6
+ export const imageSeparator = 0x2C;
7
+ export const trailerByte = 0x3B;
8
+
9
+ export const graphicControlExtensionLabel = 0xF9;
10
+ export const commentExtensionLabel = 0xFE;
11
+ export const plainTextExtensionLabel = 0x01;
12
+ export const appExtensionLabel = 0xFF;
13
+
14
+ export const netscapeLoopingAppIdentifier = 'NETSCAPE';
15
+ export const netscapeLoopingAppAuthenticationCode = '2.0';
16
+ export const netscapeLoopingAppAuthenticationCodeBytes = asciiTextEncoder.encode(netscapeLoopingAppAuthenticationCode);
17
+ export const defaultMaximumPixelCount = 100_000_000;
18
+ export const defaultMaximumRenderPixelCount = 16_777_216;
19
+ export const defaultMaximumIndexedImagePixelCount = 16_777_216;
20
+ export const defaultMaximumBlockCount = 100_000;
21
+ export const defaultMaximumDataSubBlockCount = 300_000;
22
+ export const defaultMaximumDataPayloadByteLength = 64 * 1024 * 1024;
23
+ export const defaultMaximumEncodeWorkCost = defaultMaximumPixelCount;
24
+ export const defaultMaximumEncodedByteLength = 256 * 1024 * 1024;
25
+
26
+ // The fast render path writes packed 32-bit RGBA values, which only maps to Uint8ClampedArray byte order on little-endian platforms.
27
+ const endiannessProbe = new Uint32Array([0x0A_0B_0C_0D]);
28
+
29
+ export const isLittleEndian = new Uint8Array(endiannessProbe.buffer, endiannessProbe.byteOffset, endiannessProbe.byteLength)[0] === 0x0D;
30
+
31
+ export const disposalMethodNames = [
32
+ 'unspecified',
33
+ 'keep',
34
+ 'restoreBackground',
35
+ 'restorePrevious',
36
+ ];