@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/dist-cjser/index.cjs +2168 -0
- package/index.d.ts +838 -0
- package/index.js +118 -0
- package/license +9 -0
- package/package.json +83 -0
- package/readme.md +294 -0
- package/source/byte-stream.js +141 -0
- package/source/constants.js +36 -0
- package/source/decode.js +453 -0
- package/source/encode.js +536 -0
- package/source/loop-count.js +72 -0
- package/source/lzw.js +322 -0
- package/source/quantize.js +185 -0
- package/source/render.js +470 -0
- package/source/validate.js +485 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import {types as utilTypes} from 'node:util';
|
|
2
|
+
import {
|
|
3
|
+
asciiTextEncoder,
|
|
4
|
+
disposalMethodNames,
|
|
5
|
+
defaultMaximumDataPayloadByteLength,
|
|
6
|
+
defaultMaximumPixelCount,
|
|
7
|
+
} from './constants.js';
|
|
8
|
+
|
|
9
|
+
const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
10
|
+
const typedArrayBufferGetter = Object.getOwnPropertyDescriptor(typedArrayPrototype, 'buffer').get;
|
|
11
|
+
const typedArrayByteOffsetGetter = Object.getOwnPropertyDescriptor(typedArrayPrototype, 'byteOffset').get;
|
|
12
|
+
const typedArrayByteLengthGetter = Object.getOwnPropertyDescriptor(typedArrayPrototype, 'byteLength').get;
|
|
13
|
+
|
|
14
|
+
export function isUint8Array(value) {
|
|
15
|
+
return utilTypes.isUint8Array(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isUint8ClampedArray(value) {
|
|
19
|
+
return utilTypes.isUint8ClampedArray(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function toUint8ArrayView(value) {
|
|
23
|
+
return new Uint8Array(
|
|
24
|
+
typedArrayBufferGetter.call(value),
|
|
25
|
+
typedArrayByteOffsetGetter.call(value),
|
|
26
|
+
typedArrayByteLengthGetter.call(value),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeByteArray(value, fieldName) {
|
|
31
|
+
const byteLength = value.length;
|
|
32
|
+
const bytes = new Uint8Array(byteLength);
|
|
33
|
+
|
|
34
|
+
let index = 0;
|
|
35
|
+
while (index < byteLength) {
|
|
36
|
+
bytes[index] = requireByte(value[index], `${fieldName}[${index}]`);
|
|
37
|
+
index += 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return bytes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function areBytesEqual(leftBytes, rightBytes) {
|
|
44
|
+
if (leftBytes.length !== rightBytes.length) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let index = 0;
|
|
49
|
+
while (index < leftBytes.length) {
|
|
50
|
+
if (leftBytes[index] !== rightBytes[index]) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
index += 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isPowerOfTwo(value) {
|
|
61
|
+
return (value & (value - 1)) === 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function requireIntegerInRange(value, minimum, maximum, fieldName) {
|
|
65
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
66
|
+
throw new Error(`${fieldName} must be an integer between ${minimum} and ${maximum}, got ${value}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function requireFiniteNumberInRange(value, minimum, maximum, fieldName) {
|
|
73
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < minimum || value > maximum) {
|
|
74
|
+
throw new Error(`${fieldName} must be a number between ${minimum} and ${maximum}, got ${value}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function requirePositiveFiniteNumber(value, fieldName) {
|
|
81
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
82
|
+
throw new Error(`${fieldName} must be a number greater than 0, got ${value}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function requireByte(value, fieldName) {
|
|
89
|
+
return requireIntegerInRange(value, 0, 255, fieldName);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function requireUnsigned16(value, fieldName) {
|
|
93
|
+
return requireIntegerInRange(value, 0, 65_535, fieldName);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function requireNonZeroUnsigned16(value, fieldName) {
|
|
97
|
+
return requireIntegerInRange(value, 1, 65_535, fieldName);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function requirePixelCountWithinLimit(width, height, maximumPixelCount, description) {
|
|
101
|
+
const pixelCount = width * height;
|
|
102
|
+
requirePixelCountValueWithinLimit(pixelCount, maximumPixelCount, description);
|
|
103
|
+
return pixelCount;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function requirePixelCountValueWithinLimit(pixelCount, maximumPixelCount, description) {
|
|
107
|
+
if (pixelCount > maximumPixelCount) {
|
|
108
|
+
throw new Error(`${description} has ${pixelCount} pixels, which exceeds the limit of ${maximumPixelCount}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function requireCountWithinLimit(count, maximumCount, description) {
|
|
113
|
+
if (count > maximumCount) {
|
|
114
|
+
throw new Error(`${description} ${count} exceeds the limit of ${maximumCount}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function requireByteLengthWithinLimit(byteLength, maximumByteLength, description) {
|
|
119
|
+
if (byteLength > maximumByteLength) {
|
|
120
|
+
throw new Error(`${description} has ${byteLength} bytes, which exceeds the limit of ${maximumByteLength}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function requireBlockObject(block) {
|
|
125
|
+
if (!block || typeof block !== 'object') {
|
|
126
|
+
throw new TypeError('Each block must be an object');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function normalizeColorTable(value, fieldName) {
|
|
131
|
+
if (value === undefined) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (isUint8Array(value)) {
|
|
136
|
+
const bytes = toUint8ArrayView(value);
|
|
137
|
+
validateColorTableBytes(bytes, fieldName);
|
|
138
|
+
return bytes;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
const flatBytes = normalizeColorTableArray(value, fieldName);
|
|
143
|
+
validateColorTableBytes(flatBytes, fieldName);
|
|
144
|
+
return flatBytes;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new TypeError(`${fieldName} must be undefined, a Uint8Array, a flat byte array, or an array of RGB triplets`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function normalizeColorTableArray(value, fieldName) {
|
|
151
|
+
const colorTableLength = value.length;
|
|
152
|
+
if (colorTableLength === 0) {
|
|
153
|
+
throw new Error(`${fieldName} cannot be empty`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (colorTableLength > 768) {
|
|
157
|
+
throw new Error(`${fieldName} cannot contain more than 768 flat bytes or 256 RGB triplets`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const flatEntryCount = colorTableLength / 3;
|
|
161
|
+
|
|
162
|
+
const canBeFlatByteArray = colorTableLength % 3 === 0
|
|
163
|
+
&& flatEntryCount >= 2
|
|
164
|
+
&& flatEntryCount <= 256
|
|
165
|
+
&& isPowerOfTwo(flatEntryCount);
|
|
166
|
+
|
|
167
|
+
const canBeTripletArray = colorTableLength >= 2
|
|
168
|
+
&& colorTableLength <= 256
|
|
169
|
+
&& isPowerOfTwo(colorTableLength);
|
|
170
|
+
|
|
171
|
+
const firstElementDescriptor = canBeTripletArray && !canBeFlatByteArray
|
|
172
|
+
? Object.getOwnPropertyDescriptor(value, 0)
|
|
173
|
+
: undefined;
|
|
174
|
+
|
|
175
|
+
const isTripletArray = firstElementDescriptor !== undefined
|
|
176
|
+
&& 'value' in firstElementDescriptor
|
|
177
|
+
&& Array.isArray(firstElementDescriptor.value);
|
|
178
|
+
|
|
179
|
+
if (isTripletArray) {
|
|
180
|
+
const flatArray = [];
|
|
181
|
+
let paletteIndex = 0;
|
|
182
|
+
while (paletteIndex < colorTableLength) {
|
|
183
|
+
const element = value[paletteIndex];
|
|
184
|
+
const triplet = normalizeSingleColorTriplet(element, `${fieldName}[${paletteIndex}]`);
|
|
185
|
+
flatArray.push(triplet[0], triplet[1], triplet[2]);
|
|
186
|
+
paletteIndex += 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return Uint8Array.from(flatArray);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!canBeFlatByteArray) {
|
|
193
|
+
validateColorTableEntryCountFromFlatLength(colorTableLength, fieldName);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return normalizeByteArray(value, fieldName);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function validateColorTableBytes(flatBytes, fieldName) {
|
|
200
|
+
if (flatBytes.length % 3 !== 0) {
|
|
201
|
+
throw new Error(`${fieldName} length must be divisible by 3`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const entryCount = flatBytes.length / 3;
|
|
205
|
+
if (entryCount < 2 || entryCount > 256) {
|
|
206
|
+
throw new Error(`${fieldName} must contain between 2 and 256 entries, got ${entryCount}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!isPowerOfTwo(entryCount)) {
|
|
210
|
+
throw new Error(`${fieldName} must contain a power-of-two number of entries, got ${entryCount}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function validateColorTableEntryCountFromFlatLength(byteLength, fieldName) {
|
|
215
|
+
if (byteLength % 3 !== 0) {
|
|
216
|
+
throw new Error(`${fieldName} length must be divisible by 3`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const entryCount = byteLength / 3;
|
|
220
|
+
if (entryCount < 2 || entryCount > 256) {
|
|
221
|
+
throw new Error(`${fieldName} must contain between 2 and 256 entries, got ${entryCount}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
throw new Error(`${fieldName} must contain a power-of-two number of entries, got ${entryCount}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function normalizeSingleColorTriplet(value, fieldName) {
|
|
228
|
+
if (!Array.isArray(value) || value.length !== 3) {
|
|
229
|
+
throw new TypeError(`${fieldName} must be an array of exactly 3 bytes`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return [
|
|
233
|
+
requireByte(value[0], `${fieldName}[0]`),
|
|
234
|
+
requireByte(value[1], `${fieldName}[1]`),
|
|
235
|
+
requireByte(value[2], `${fieldName}[2]`),
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function padColorTableToPowerOfTwo(colorTable) {
|
|
240
|
+
const entryCount = colorTable.length / 3;
|
|
241
|
+
if (entryCount === 0) {
|
|
242
|
+
throw new Error('Color table must contain at least one entry');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let paddedEntryCount = 2;
|
|
246
|
+
while (paddedEntryCount < entryCount) {
|
|
247
|
+
paddedEntryCount *= 2;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (paddedEntryCount > 256) {
|
|
251
|
+
throw new Error('Color table cannot exceed 256 entries');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (paddedEntryCount === entryCount) {
|
|
255
|
+
return colorTable;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const paddedColorTable = new Uint8Array(paddedEntryCount * 3);
|
|
259
|
+
paddedColorTable.set(colorTable);
|
|
260
|
+
const lastTripletOffset = Math.max(0, colorTable.length - 3);
|
|
261
|
+
for (let offset = colorTable.length; offset < paddedColorTable.length; offset += 3) {
|
|
262
|
+
paddedColorTable[offset] = colorTable[lastTripletOffset];
|
|
263
|
+
paddedColorTable[offset + 1] = colorTable[lastTripletOffset + 1];
|
|
264
|
+
paddedColorTable[offset + 2] = colorTable[lastTripletOffset + 2];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return paddedColorTable;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function calculateColorTableSizeField(colorTableEntryCount) {
|
|
271
|
+
if (!Number.isSafeInteger(colorTableEntryCount) || colorTableEntryCount < 2 || colorTableEntryCount > 256) {
|
|
272
|
+
throw new Error(`Color table must contain between 2 and 256 entries, got ${colorTableEntryCount}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if ((colorTableEntryCount & (colorTableEntryCount - 1)) !== 0) {
|
|
276
|
+
throw new Error(`Color table entry count must be a power of two, got ${colorTableEntryCount}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return Math.round(Math.log2(colorTableEntryCount)) - 1;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function deriveColorResolution(globalColorTable) {
|
|
283
|
+
if (globalColorTable === undefined) {
|
|
284
|
+
return 8;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const entryCount = globalColorTable.length / 3;
|
|
288
|
+
return Math.max(1, Math.ceil(Math.log2(entryCount)));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function deriveMinimumCodeSize(colorTableEntryCount) {
|
|
292
|
+
const clampedEntryCount = Math.max(2, colorTableEntryCount);
|
|
293
|
+
return Math.max(2, Math.ceil(Math.log2(clampedEntryCount)));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function normalizeIndexedPixels(value, expectedLength, fieldName) {
|
|
297
|
+
if (value === undefined) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let indexedPixels;
|
|
302
|
+
if (isUint8Array(value)) {
|
|
303
|
+
indexedPixels = toUint8ArrayView(value);
|
|
304
|
+
} else if (Array.isArray(value)) {
|
|
305
|
+
if (value.length !== expectedLength) {
|
|
306
|
+
throw new Error(`${fieldName} length must be ${expectedLength}, got ${value.length}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
indexedPixels = normalizeByteArray(value, fieldName);
|
|
310
|
+
} else {
|
|
311
|
+
throw new TypeError(`${fieldName} must be a Uint8Array or array of bytes`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (indexedPixels.length !== expectedLength) {
|
|
315
|
+
throw new Error(`${fieldName} length must be ${expectedLength}, got ${indexedPixels.length}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return indexedPixels;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function normalizeRedGreenBlueAlphaPixels(value, expectedLength, fieldName) {
|
|
322
|
+
let pixels;
|
|
323
|
+
if (isUint8Array(value) || isUint8ClampedArray(value)) {
|
|
324
|
+
pixels = toUint8ArrayView(value);
|
|
325
|
+
} else if (Array.isArray(value)) {
|
|
326
|
+
if (value.length !== expectedLength) {
|
|
327
|
+
throw new Error(`${fieldName} length must be ${expectedLength}, got ${value.length}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
pixels = Uint8ClampedArray.from(normalizeByteArray(value, fieldName));
|
|
331
|
+
} else {
|
|
332
|
+
throw new TypeError(`${fieldName} must be a Uint8Array, Uint8ClampedArray, or array of bytes`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (pixels.length !== expectedLength) {
|
|
336
|
+
throw new Error(`${fieldName} length must be ${expectedLength}, got ${pixels.length}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return pixels;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function normalizeExtensionPayload(value, fieldName) {
|
|
343
|
+
if (typeof value === 'string') {
|
|
344
|
+
return normalizeAsciiPayloadString(value, fieldName);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (isUint8Array(value)) {
|
|
348
|
+
const bytes = toUint8ArrayView(value);
|
|
349
|
+
requireByteLengthWithinLimit(bytes.length, defaultMaximumDataPayloadByteLength, fieldName);
|
|
350
|
+
return bytes;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (Array.isArray(value)) {
|
|
354
|
+
requireByteLengthWithinLimit(value.length, defaultMaximumDataPayloadByteLength, fieldName);
|
|
355
|
+
return normalizeByteArray(value, fieldName);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
throw new TypeError(`${fieldName} must be a string, Uint8Array, or array of bytes`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function normalizeAsciiPayloadString(value, fieldName) {
|
|
362
|
+
requireByteLengthWithinLimit(value.length, defaultMaximumDataPayloadByteLength, fieldName);
|
|
363
|
+
const bytes = new Uint8Array(value.length);
|
|
364
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
365
|
+
const codeUnit = value.codePointAt(index);
|
|
366
|
+
if (codeUnit > 0x7F) {
|
|
367
|
+
throw new Error(`${fieldName} must contain only ASCII characters`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
bytes[index] = codeUnit;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return bytes;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function normalizeFixedAsciiField(value, expectedLength, fieldName) {
|
|
377
|
+
if (typeof value !== 'string') {
|
|
378
|
+
throw new TypeError(`${fieldName} must be a string`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (value.length !== expectedLength) {
|
|
382
|
+
throw new Error(`${fieldName} must be exactly ${expectedLength} ASCII bytes long`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const encodedValue = asciiTextEncoder.encode(value);
|
|
386
|
+
if (encodedValue.length !== expectedLength) {
|
|
387
|
+
throw new Error(`${fieldName} must be exactly ${expectedLength} ASCII bytes long`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
for (const byte of encodedValue) {
|
|
391
|
+
if (byte < 0x20 || byte > 0x7E) {
|
|
392
|
+
throw new Error(`${fieldName} must contain only printable ASCII characters`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function normalizeFixedByteField(value, expectedLength, fieldName) {
|
|
400
|
+
if (typeof value === 'string') {
|
|
401
|
+
if (value.length !== expectedLength) {
|
|
402
|
+
throw new Error(`${fieldName} must be exactly ${expectedLength} bytes long`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const encodedValue = asciiTextEncoder.encode(value);
|
|
406
|
+
if (encodedValue.length !== expectedLength) {
|
|
407
|
+
throw new Error(`${fieldName} must be exactly ${expectedLength} bytes long`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return encodedValue;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (isUint8Array(value)) {
|
|
414
|
+
const bytes = toUint8ArrayView(value);
|
|
415
|
+
if (bytes.length !== expectedLength) {
|
|
416
|
+
throw new Error(`${fieldName} must be exactly ${expectedLength} bytes long`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return bytes;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (Array.isArray(value)) {
|
|
423
|
+
if (value.length !== expectedLength) {
|
|
424
|
+
throw new Error(`${fieldName} must be exactly ${expectedLength} bytes long`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return normalizeByteArray(value, fieldName);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
throw new TypeError(`${fieldName} must be a string, Uint8Array, or array of bytes`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function normalizeImageGeometry(block, {logicalScreenWidth, logicalScreenHeight}) {
|
|
434
|
+
const left = requireUnsigned16(block.left ?? 0, 'image.left');
|
|
435
|
+
const top = requireUnsigned16(block.top ?? 0, 'image.top');
|
|
436
|
+
const width = requireNonZeroUnsigned16(block.width, 'image.width');
|
|
437
|
+
const height = requireNonZeroUnsigned16(block.height, 'image.height');
|
|
438
|
+
if (left + width > logicalScreenWidth || top + height > logicalScreenHeight) {
|
|
439
|
+
throw new Error('Image block extends beyond the logical screen bounds');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
requirePixelCountWithinLimit(width, height, defaultMaximumPixelCount, 'image block');
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
left,
|
|
446
|
+
top,
|
|
447
|
+
width,
|
|
448
|
+
height,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function secondsToHundredthsOfASecond(seconds, fieldName) {
|
|
453
|
+
const value = requireFiniteNumberInRange(seconds, 0, 655.35, fieldName);
|
|
454
|
+
return requireUnsigned16(Math.round(value * 100), `${fieldName} in hundredths of a second`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function normalizeGraphicControlExtension(graphicControlExtension) {
|
|
458
|
+
if (graphicControlExtension === undefined) {
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (typeof graphicControlExtension !== 'object' || graphicControlExtension === null) {
|
|
463
|
+
throw new TypeError('graphicControlExtension must be an object or undefined');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
disposalMethod: normalizeDisposalMethod(graphicControlExtension.disposalMethod ?? 'unspecified'),
|
|
468
|
+
delayInHundredthsOfASecond: secondsToHundredthsOfASecond(graphicControlExtension.delay ?? 0, 'graphicControlExtension.delay'),
|
|
469
|
+
transparentColorIndex: graphicControlExtension.transparentColorIndex === undefined
|
|
470
|
+
? undefined
|
|
471
|
+
: requireByte(graphicControlExtension.transparentColorIndex, 'graphicControlExtension.transparentColorIndex'),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function normalizeDisposalMethod(disposalMethod) {
|
|
476
|
+
if (!disposalMethodNames.includes(disposalMethod)) {
|
|
477
|
+
throw new Error(`graphicControlExtension.disposalMethod must be one of ${disposalMethodNames.map(value => JSON.stringify(value)).join(', ')}, got ${JSON.stringify(disposalMethod)}`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return disposalMethod;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function disposalMethodByte(disposalMethod) {
|
|
484
|
+
return disposalMethodNames.indexOf(disposalMethod);
|
|
485
|
+
}
|