imagescript 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.
@@ -13,6 +13,9 @@ const eXIf = 0x65584966;
13
13
  const tEXt = 0x74455874;
14
14
  const zTXt = 0x7a545874;
15
15
  const iTXt = 0x69545874;
16
+ const acTL = 0x6163544c;
17
+ const fcTL = 0x6663544c;
18
+ const fdAT = 0x66644154;
16
19
  const COLOR_TYPE_CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
17
20
  const CHANNELS_TO_COLOR_TYPE = { 1: 0, 2: 4, 3: 2, 4: 6 };
18
21
  const VALID_DEPTHS = { 0: [1, 2, 4, 8, 16], 2: [8, 16], 3: [1, 2, 4, 8], 4: [8, 16], 6: [8, 16] };
@@ -77,32 +80,30 @@ function chunk(type, body) {
77
80
  view.setUint32(body.length + 8, crc32(out.subarray(4, body.length + 8)));
78
81
  return out;
79
82
  }
80
- export async function encodeRGBA(rgba, width, height, level, text) {
81
- const count = width * height;
82
- if (rgba.byteOffset % 4)
83
- rgba = rgba.slice(0, count * 4);
84
- const pixels = new Uint32Array(rgba.buffer, rgba.byteOffset, count);
83
+ function pixelFormat(images) {
85
84
  let opaque = true, grey = true;
86
85
  let colors = new Map();
87
- let previous = -1;
88
- for (let i = 0; i < count; i++) {
89
- const value = pixels[i];
90
- if (value === previous)
91
- continue;
92
- previous = value;
93
- const o = i * 4;
94
- if (rgba[o + 3] !== 255)
95
- opaque = false;
96
- if (rgba[o] !== rgba[o + 1] || rgba[o] !== rgba[o + 2])
97
- grey = false;
98
- if (colors && !colors.has(value)) {
99
- if (colors.size === 256)
100
- colors = null;
101
- else
102
- colors.set(value, colors.size);
86
+ for (const rgba of images) {
87
+ const pixels = new Uint32Array(rgba.buffer, rgba.byteOffset, rgba.length >> 2);
88
+ let previous = -1;
89
+ for (let i = 0; i < pixels.length; i++) {
90
+ const value = pixels[i];
91
+ if (value === previous)
92
+ continue;
93
+ previous = value;
94
+ const o = i * 4;
95
+ if (rgba[o + 3] !== 255)
96
+ opaque = false;
97
+ if (rgba[o] !== rgba[o + 1] || rgba[o] !== rgba[o + 2])
98
+ grey = false;
99
+ if (colors && !colors.has(value)) {
100
+ if (colors.size === 256)
101
+ colors = null;
102
+ else
103
+ colors.set(value, colors.size);
104
+ }
103
105
  }
104
106
  }
105
- const options = { width, height, level, text, filter: level > 0 };
106
107
  if (colors) {
107
108
  const entries = [...colors.keys()].map(value => new Uint8Array(new Uint32Array([value]).buffer));
108
109
  entries.sort((a, b) => a[3] - b[3]);
@@ -113,41 +114,140 @@ export async function encodeRGBA(rgba, width, height, level, text) {
113
114
  const translucent = entries.filter(bytes => bytes[3] !== 255).length;
114
115
  const transparency = translucent ? Uint8Array.from(entries.slice(0, translucent), bytes => bytes[3]) : undefined;
115
116
  const depth = entries.length <= 2 ? 1 : entries.length <= 4 ? 2 : entries.length <= 16 ? 4 : 8;
116
- const rowBytes = Math.ceil(width * depth / 8);
117
- const perByte = 8 / depth;
118
- const data = new Uint8Array(rowBytes * height);
119
- let last = -1, lastIndex = 0;
120
- for (let y = 0; y < height; y++) {
121
- for (let x = 0; x < width; x++) {
122
- const value = pixels[y * width + x];
123
- if (value !== last) {
124
- last = value;
125
- lastIndex = index.get(value);
117
+ return {
118
+ channels: 1, depth, palette, transparency,
119
+ convert(rgba, width, height) {
120
+ const pixels = new Uint32Array(rgba.buffer, rgba.byteOffset, width * height);
121
+ const rowBytes = Math.ceil(width * depth / 8);
122
+ const perByte = 8 / depth;
123
+ const data = new Uint8Array(rowBytes * height);
124
+ let last = -1, lastIndex = 0;
125
+ for (let y = 0; y < height; y++) {
126
+ for (let x = 0; x < width; x++) {
127
+ const value = pixels[y * width + x];
128
+ if (value !== last) {
129
+ last = value;
130
+ lastIndex = index.get(value);
131
+ }
132
+ data[y * rowBytes + (x / perByte | 0)] |= lastIndex << (8 - depth - (x % perByte) * depth);
133
+ }
126
134
  }
127
- data[y * rowBytes + (x / perByte | 0)] |= lastIndex << (8 - depth - (x % perByte) * depth);
135
+ return data;
128
136
  }
129
- }
130
- return encode(data, { ...options, channels: 1, depth, palette, transparency, filter: false });
137
+ };
131
138
  }
132
139
  const channels = (grey ? 1 : 3) + (opaque ? 0 : 1);
133
- if (channels === 4)
134
- return encode(rgba, { ...options, channels });
135
- const data = new Uint8Array(count * channels);
136
- for (let i = 0, o = 0; i < count; i++) {
137
- const s = i * 4;
138
- if (grey)
139
- data[o++] = rgba[s];
140
+ return {
141
+ channels, depth: 8,
142
+ convert(rgba, width, height) {
143
+ if (channels === 4)
144
+ return rgba;
145
+ const count = width * height;
146
+ const data = new Uint8Array(count * channels);
147
+ for (let i = 0, o = 0; i < count; i++) {
148
+ const s = i * 4;
149
+ if (grey)
150
+ data[o++] = rgba[s];
151
+ else {
152
+ data[o++] = rgba[s];
153
+ data[o++] = rgba[s + 1];
154
+ data[o++] = rgba[s + 2];
155
+ }
156
+ if (!opaque)
157
+ data[o++] = rgba[s + 3];
158
+ }
159
+ return data;
160
+ }
161
+ };
162
+ }
163
+ const aligned = (rgba) => rgba.byteOffset % 4 ? rgba.slice() : rgba;
164
+ export async function encodeRGBA(rgba, width, height, level, text) {
165
+ rgba = aligned(rgba.subarray(0, width * height * 4));
166
+ const format = pixelFormat([rgba]);
167
+ const { channels, depth, palette, transparency } = format;
168
+ return encode(format.convert(rgba, width, height), { width, height, level, text, channels, depth, palette, transparency, filter: level > 0 && !palette });
169
+ }
170
+ async function compressRows(data, width, height, channels, depth, level, filterRows) {
171
+ const rowBytes = Math.ceil(width * channels * depth / 8);
172
+ let filtered;
173
+ if (filterRows)
174
+ filtered = filter(data, rowBytes, height, Math.max(1, channels * depth / 8));
175
+ else {
176
+ filtered = new Uint8Array(height * (rowBytes + 1));
177
+ for (let y = 0; y < height; y++)
178
+ filtered.set(data.subarray(y * rowBytes, (y + 1) * rowBytes), y * (rowBytes + 1) + 1);
179
+ }
180
+ return compressAsync(filtered, level);
181
+ }
182
+ export async function encodeAnimation(frames, width, height, plays, level) {
183
+ if (!frames.length)
184
+ throw new Error('PNG: an animation needs at least one frame');
185
+ const first = frames[0];
186
+ if (first.x !== 0 || first.y !== 0 || first.width !== width || first.height !== height) {
187
+ const pixels = new Uint8Array(width * height * 4);
188
+ for (let y = 0; y < first.height && first.y + y < height; y++) {
189
+ const row = first.pixels.subarray(y * first.width * 4, (y * first.width + Math.min(first.width, width - first.x)) * 4);
190
+ pixels.set(row, ((first.y + y) * width + first.x) * 4);
191
+ }
192
+ frames = [{ ...first, x: 0, y: 0, width, height, pixels }, ...frames.slice(1)];
193
+ }
194
+ for (const f of frames)
195
+ if (f.x < 0 || f.y < 0 || f.x + f.width > width || f.y + f.height > height || !f.width || !f.height)
196
+ throw new RangeError('PNG: APNG frames have to lie within the canvas');
197
+ const images = frames.map(f => aligned(f.pixels.subarray(0, f.width * f.height * 4)));
198
+ const format = pixelFormat(images);
199
+ const { channels, depth, palette, transparency } = format;
200
+ const filterRows = level > 0 && !palette;
201
+ const header = new Uint8Array(13);
202
+ const hv = new DataView(header.buffer);
203
+ hv.setUint32(0, width);
204
+ hv.setUint32(4, height);
205
+ header[8] = depth;
206
+ header[9] = palette ? 3 : CHANNELS_TO_COLOR_TYPE[channels];
207
+ const actl = new Uint8Array(8);
208
+ new DataView(actl.buffer).setUint32(0, frames.length);
209
+ new DataView(actl.buffer).setUint32(4, plays);
210
+ const chunks = [chunk(IHDR, header), chunk(acTL, actl)];
211
+ if (palette)
212
+ chunks.push(chunk(PLTE, palette));
213
+ if (transparency)
214
+ chunks.push(chunk(tRNS, transparency));
215
+ let sequence = 0;
216
+ for (let i = 0; i < frames.length; i++) {
217
+ const f = frames[i];
218
+ const fctl = new Uint8Array(26);
219
+ const v = new DataView(fctl.buffer);
220
+ v.setUint32(0, sequence++);
221
+ v.setUint32(4, f.width);
222
+ v.setUint32(8, f.height);
223
+ v.setUint32(12, f.x);
224
+ v.setUint32(16, f.y);
225
+ const ms = Math.max(0, Math.round(f.duration));
226
+ if (ms <= 65535) {
227
+ v.setUint16(20, ms);
228
+ v.setUint16(22, 1000);
229
+ }
230
+ else {
231
+ v.setUint16(20, Math.min(65535, Math.round(ms / 10)));
232
+ v.setUint16(22, 100);
233
+ }
234
+ fctl[24] = f.dispose;
235
+ fctl[25] = 1;
236
+ chunks.push(chunk(fcTL, fctl));
237
+ const data = await compressRows(format.convert(images[i], f.width, f.height), f.width, f.height, channels, depth, level, filterRows);
238
+ if (i === 0)
239
+ chunks.push(chunk(IDAT, data));
140
240
  else {
141
- data[o++] = rgba[s];
142
- data[o++] = rgba[s + 1];
143
- data[o++] = rgba[s + 2];
241
+ const body = new Uint8Array(data.length + 4);
242
+ new DataView(body.buffer).setUint32(0, sequence++);
243
+ body.set(data, 4);
244
+ chunks.push(chunk(fdAT, body));
144
245
  }
145
- if (!opaque)
146
- data[o++] = rgba[s + 3];
147
246
  }
148
- return encode(data, { ...options, channels });
247
+ chunks.push(chunk(IEND, new Uint8Array(0)));
248
+ return concat([HEAD, ...chunks]);
149
249
  }
150
- export async function decode(array) {
250
+ function parse(array) {
151
251
  const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
152
252
  for (let i = 0; i < HEAD.length; i++)
153
253
  if (array[i] !== HEAD[i])
@@ -158,6 +258,11 @@ export async function decode(array) {
158
258
  const idat = [];
159
259
  let exif = null;
160
260
  const text = [];
261
+ let animation = null;
262
+ const frames = [];
263
+ let current = null;
264
+ let sequence = 0;
265
+ let animationError = false;
161
266
  let offset = 8;
162
267
  while (offset + 8 <= array.length) {
163
268
  const length = view.getUint32(offset);
@@ -193,8 +298,46 @@ export async function decode(array) {
193
298
  throw new Error('PNG: tRNS can only occur once in an image');
194
299
  transparency = chunk;
195
300
  }
196
- else if (type === IDAT)
301
+ else if (type === IDAT) {
197
302
  idat.push(chunk);
303
+ if (current && !frames.length)
304
+ frames.push(current);
305
+ }
306
+ else if (type === acTL) {
307
+ if (animation || idat.length || length < 8)
308
+ animationError = true;
309
+ else
310
+ animation = { frames: view.getUint32(start), plays: view.getUint32(start + 4) };
311
+ }
312
+ else if (type === fcTL) {
313
+ if (length < 26 || view.getUint32(start) !== sequence++)
314
+ animationError = true;
315
+ if (animationError) {
316
+ offset = end + 4;
317
+ continue;
318
+ }
319
+ const num = view.getUint16(start + 20), den = view.getUint16(start + 22) || 100;
320
+ current = {
321
+ control: {
322
+ width: view.getUint32(start + 4),
323
+ height: view.getUint32(start + 8),
324
+ x: view.getUint32(start + 12),
325
+ y: view.getUint32(start + 16),
326
+ delay: num * 1000 / den,
327
+ dispose: chunk[24],
328
+ blend: chunk[25]
329
+ },
330
+ data: idat.length ? [] : idat
331
+ };
332
+ if (idat.length)
333
+ frames.push(current);
334
+ }
335
+ else if (type === fdAT) {
336
+ if (length < 4 || view.getUint32(start) !== sequence++ || !current || current.data === idat)
337
+ animationError = true;
338
+ else
339
+ current.data.push(chunk.subarray(4));
340
+ }
198
341
  else if (type === eXIf)
199
342
  exif ??= chunk;
200
343
  else if (type === tEXt || type === zTXt || type === iTXt)
@@ -207,7 +350,7 @@ export async function decode(array) {
207
350
  throw new Error('PNG: missing IHDR');
208
351
  if (!idat.length)
209
352
  throw new Error('PNG: missing IDAT');
210
- const { width, height, depth, colorType, interlace } = header;
353
+ const { width, height, depth, colorType } = header;
211
354
  const channels = COLOR_TYPE_CHANNELS[colorType];
212
355
  if (!channels)
213
356
  throw new Error(`PNG: invalid color type ${colorType}`);
@@ -217,6 +360,17 @@ export async function decode(array) {
217
360
  throw new Error('PNG: invalid dimensions');
218
361
  if (colorType === 3 && !palette)
219
362
  throw new Error('PNG: indexed color image has no PLTE');
363
+ if (animation) {
364
+ if (frames.length > animation.frames)
365
+ frames.length = animation.frames;
366
+ if (!frames.length || frames.length < animation.frames || frames.some(f => !f.data.length))
367
+ animationError = true;
368
+ }
369
+ return { header, palette, transparency, idat, exif, text, animation, frames, animationError };
370
+ }
371
+ async function decodeImageData(p, chunks, width, height) {
372
+ const { depth, colorType, interlace } = p.header;
373
+ const channels = COLOR_TYPE_CHANNELS[colorType];
220
374
  const bitsPerPixel = channels * depth;
221
375
  const bpp = Math.max(1, bitsPerPixel >> 3);
222
376
  const passes = interlace ? ADAM7.map(([xs, ys, dx, dy]) => [xs, ys, dx, dy, Math.ceil((width - xs) / dx), Math.ceil((height - ys) / dy)]) : [[0, 0, 1, 1, width, height]];
@@ -226,11 +380,13 @@ export async function decode(array) {
226
380
  if (pw > 0 && ph > 0)
227
381
  expected += ph * (1 + Math.ceil(pw * bitsPerPixel / 8));
228
382
  }
229
- const data = await decompressAsync(idat.length === 1 ? idat[0] : concat(idat), expected);
383
+ if (!chunks.length)
384
+ throw new Error('PNG: missing image data');
385
+ const data = await decompressAsync(chunks.length === 1 ? chunks[0] : concat(chunks), expected);
230
386
  if (data.length < expected)
231
387
  throw new Error('PNG: image data is truncated');
232
388
  const rgba = new Uint8Array(width * height * 4);
233
- const toRGBA = createConverter(colorType, depth, palette, transparency);
389
+ const toRGBA = createConverter(colorType, depth, p.palette, p.transparency);
234
390
  let dataOffset = 0;
235
391
  for (const [xs, ys, dx, dy, pw, ph] of passes) {
236
392
  if (pw <= 0 || ph <= 0)
@@ -241,7 +397,73 @@ export async function decode(array) {
241
397
  dataOffset += size + ph;
242
398
  toRGBA(pixels, rowBytes, pw, ph, rgba, { row: y => ((ys + y * dy) * width + xs) * 4, step: dx * 4 });
243
399
  }
244
- return { width, height, pixels: rgba, orientation: exif ? exifOrientation(exif) : await textOrientation(text) };
400
+ return rgba;
401
+ }
402
+ export async function decode(array) {
403
+ const p = parse(array);
404
+ const { width, height } = p.header;
405
+ const pixels = await decodeImageData(p, p.idat, width, height);
406
+ return { width, height, pixels, orientation: p.exif ? exifOrientation(p.exif) : await textOrientation(p.text) };
407
+ }
408
+ export function isAnimated(array) {
409
+ try {
410
+ const p = parse(array);
411
+ return p.animation !== null && !p.animationError;
412
+ }
413
+ catch {
414
+ return false;
415
+ }
416
+ }
417
+ export async function decodeAnimation(array, onlyFirstFrame = false) {
418
+ const p = parse(array);
419
+ const { width, height } = p.header;
420
+ if (!p.animation || p.animationError) {
421
+ const pixels = await decodeImageData(p, p.idat, width, height);
422
+ return { width, height, plays: 0, frames: [{ x: 0, y: 0, width, height, duration: 0, dispose: 0, blend: 0, pixels }] };
423
+ }
424
+ const frames = [];
425
+ for (const { control: c, data } of p.frames) {
426
+ if (!c.width || !c.height || c.x + c.width > width || c.y + c.height > height)
427
+ throw new Error('PNG: invalid APNG frame region');
428
+ frames.push({
429
+ x: c.x, y: c.y, width: c.width, height: c.height, duration: c.delay,
430
+ dispose: !frames.length && c.dispose === 2 ? 1 : c.dispose,
431
+ blend: c.blend,
432
+ pixels: await decodeImageData(p, data, c.width, c.height)
433
+ });
434
+ if (onlyFirstFrame)
435
+ break;
436
+ }
437
+ return { width, height, plays: p.animation.plays, frames };
438
+ }
439
+ export function compositeAnimation({ width, height, frames }) {
440
+ const canvas = new Uint8Array(width * height * 4);
441
+ const output = [];
442
+ for (const f of frames) {
443
+ const previous = f.dispose === 2 ? canvas.slice() : null;
444
+ for (let y = 0; y < f.height; y++)
445
+ for (let x = 0; x < f.width; x++) {
446
+ const s = (y * f.width + x) * 4, d = ((f.y + y) * width + f.x + x) * 4;
447
+ const sa = f.pixels[s + 3], da = canvas[d + 3];
448
+ if (f.blend === 0 || sa === 255 || da === 0) {
449
+ if (f.blend === 0 || sa !== 0)
450
+ canvas.set(f.pixels.subarray(s, s + 4), d);
451
+ }
452
+ else if (sa !== 0) {
453
+ const u = sa * 255, v = (255 - sa) * da, al = u + v;
454
+ for (let c = 0; c < 3; c++)
455
+ canvas[d + c] = (f.pixels[s + c] * u + canvas[d + c] * v) / al | 0;
456
+ canvas[d + 3] = al / 255 | 0;
457
+ }
458
+ }
459
+ output.push({ pixels: canvas.slice(), duration: f.duration });
460
+ if (f.dispose === 1)
461
+ for (let y = 0; y < f.height; y++)
462
+ canvas.fill(0, ((f.y + y) * width + f.x) * 4, ((f.y + y) * width + f.x + f.width) * 4);
463
+ else if (previous)
464
+ canvas.set(previous);
465
+ }
466
+ return output;
245
467
  }
246
468
  const RAW_EXIF = /^Raw profile type (exif|APP1)$/;
247
469
  const XMP = 'XML:com.adobe.xmp';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Whether the data looks like a TGA. Older TGAs have no signature, so this checks that the header is consistent
3
+ */
4
+ export declare function isTGA(d: Uint8Array): boolean;
5
+ /**
6
+ * Decodes a TGA into RGBA
7
+ */
8
+ export declare function decode(d: Uint8Array): {
9
+ width: number;
10
+ height: number;
11
+ pixels: Uint8Array;
12
+ };
13
+ /**
14
+ * Encodes RGBA pixels into a run length encoded TGA (32 bit with alpha, 24 bit if opaque), top left origin
15
+ */
16
+ export declare function encode(rgba: Uint8Array, width: number, height: number): Uint8Array;
@@ -0,0 +1,203 @@
1
+ const SIGNATURE = 'TRUEVISION-XFILE.\0';
2
+ function readHeader(d) {
3
+ if (d.length < 18)
4
+ return null;
5
+ return {
6
+ idLength: d[0],
7
+ colorMapType: d[1],
8
+ imageType: d[2],
9
+ mapFirst: d[3] | d[4] << 8,
10
+ mapLength: d[5] | d[6] << 8,
11
+ mapEntrySize: d[7],
12
+ width: d[12] | d[13] << 8,
13
+ height: d[14] | d[15] << 8,
14
+ depth: d[16],
15
+ alphaBits: d[17] & 15,
16
+ rightToLeft: (d[17] & 16) !== 0,
17
+ topToBottom: (d[17] & 32) !== 0
18
+ };
19
+ }
20
+ function hasFooter(d) {
21
+ if (d.length < 44)
22
+ return false;
23
+ for (let i = 0; i < SIGNATURE.length; i++)
24
+ if (d[d.length - 18 + i] !== SIGNATURE.charCodeAt(i))
25
+ return false;
26
+ return true;
27
+ }
28
+ export function isTGA(d) {
29
+ const h = readHeader(d);
30
+ if (!h)
31
+ return false;
32
+ if (hasFooter(d))
33
+ return true;
34
+ const base = h.imageType & ~8;
35
+ if (h.colorMapType > 1 || ![1, 2, 3].includes(base) || h.imageType > 11 || (h.imageType & 4))
36
+ return false;
37
+ if (!h.width || !h.height || d[17] & 0xc0)
38
+ return false;
39
+ if (base === 1 && (h.colorMapType !== 1 || (h.depth !== 8 && h.depth !== 16)))
40
+ return false;
41
+ if (base === 2 && ![15, 16, 24, 32].includes(h.depth))
42
+ return false;
43
+ if (base === 3 && h.depth !== 8 && h.depth !== 16)
44
+ return false;
45
+ if (h.colorMapType === 1 && ![8, 15, 16, 24, 32].includes(h.mapEntrySize))
46
+ return false;
47
+ const mapBytes = h.colorMapType ? h.mapLength * Math.ceil(h.mapEntrySize / 8) : 0;
48
+ const dataStart = 18 + h.idLength + mapBytes;
49
+ const pixelBytes = h.width * h.height * Math.ceil(h.depth / 8);
50
+ return h.imageType & 8 ? d.length > dataStart : d.length >= dataStart + pixelBytes;
51
+ }
52
+ function readColor(d, o, bits, alphaBits, out, p) {
53
+ if (bits === 8) {
54
+ out[p] = out[p + 1] = out[p + 2] = d[o];
55
+ out[p + 3] = 255;
56
+ }
57
+ else if (bits === 15 || bits === 16) {
58
+ const v = d[o] | d[o + 1] << 8;
59
+ out[p] = (v >> 10 & 31) * 255 / 31 | 0;
60
+ out[p + 1] = (v >> 5 & 31) * 255 / 31 | 0;
61
+ out[p + 2] = (v & 31) * 255 / 31 | 0;
62
+ out[p + 3] = 255;
63
+ }
64
+ else {
65
+ out[p] = d[o + 2];
66
+ out[p + 1] = d[o + 1];
67
+ out[p + 2] = d[o];
68
+ out[p + 3] = bits === 32 && alphaBits ? d[o + 3] : 255;
69
+ }
70
+ }
71
+ export function decode(d) {
72
+ const h = readHeader(d);
73
+ if (!h || !isTGA(d))
74
+ throw new Error('TGA: invalid header');
75
+ const { width, height, depth } = h;
76
+ const base = h.imageType & ~8, rle = (h.imageType & 8) !== 0;
77
+ const bytesPerPixel = Math.ceil(depth / 8);
78
+ let offset = 18 + h.idLength;
79
+ let palette = null;
80
+ if (h.colorMapType === 1) {
81
+ const entryBytes = Math.ceil(h.mapEntrySize / 8);
82
+ palette = new Uint8Array((h.mapFirst + h.mapLength) * 4);
83
+ for (let i = 0; i < h.mapLength; i++) {
84
+ if (offset + entryBytes > d.length)
85
+ throw new Error('TGA: truncated color map');
86
+ readColor(d, offset, h.mapEntrySize, h.alphaBits, palette, (h.mapFirst + i) * 4);
87
+ offset += entryBytes;
88
+ }
89
+ }
90
+ if (base === 1 && !palette)
91
+ throw new Error('TGA: color mapped image without color map');
92
+ const count = width * height;
93
+ const stored = new Uint8Array(count * 4);
94
+ const pixel = (o, p) => {
95
+ if (base === 1) {
96
+ const index = depth === 16 ? d[o] | d[o + 1] << 8 : d[o];
97
+ const pal = palette;
98
+ if (index * 4 + 3 < pal.length)
99
+ stored.set(pal.subarray(index * 4, index * 4 + 4), p);
100
+ else
101
+ stored[p + 3] = 255;
102
+ }
103
+ else if (base === 3) {
104
+ stored[p] = stored[p + 1] = stored[p + 2] = d[o];
105
+ stored[p + 3] = depth === 16 ? d[o + 1] : 255;
106
+ }
107
+ else
108
+ readColor(d, o, depth, h.alphaBits, stored, p);
109
+ };
110
+ if (!rle) {
111
+ if (offset + count * bytesPerPixel > d.length)
112
+ throw new Error('TGA: truncated image data');
113
+ for (let i = 0; i < count; i++)
114
+ pixel(offset + i * bytesPerPixel, i * 4);
115
+ }
116
+ else {
117
+ for (let i = 0; i < count;) {
118
+ if (offset >= d.length)
119
+ throw new Error('TGA: truncated image data');
120
+ const header = d[offset++];
121
+ const n = (header & 127) + 1;
122
+ if (offset + (header & 128 ? 1 : n) * bytesPerPixel > d.length)
123
+ throw new Error('TGA: truncated image data');
124
+ if (header & 128) {
125
+ pixel(offset, i * 4);
126
+ for (let k = 1; k < n && i + k < count; k++)
127
+ stored.copyWithin((i + k) * 4, i * 4, i * 4 + 4);
128
+ offset += bytesPerPixel;
129
+ }
130
+ else {
131
+ for (let k = 0; k < n && i + k < count; k++)
132
+ pixel(offset + k * bytesPerPixel, (i + k) * 4);
133
+ offset += n * bytesPerPixel;
134
+ }
135
+ i += n;
136
+ }
137
+ }
138
+ if (!h.topToBottom || h.rightToLeft) {
139
+ const pixels = new Uint8Array(count * 4);
140
+ const src = new Uint32Array(stored.buffer), dst = new Uint32Array(pixels.buffer);
141
+ for (let y = 0; y < height; y++) {
142
+ const sy = h.topToBottom ? y : height - 1 - y;
143
+ for (let x = 0; x < width; x++)
144
+ dst[y * width + x] = src[sy * width + (h.rightToLeft ? width - 1 - x : x)];
145
+ }
146
+ return { width, height, pixels };
147
+ }
148
+ return { width, height, pixels: stored };
149
+ }
150
+ export function encode(rgba, width, height) {
151
+ if (width < 1 || height < 1 || width > 65535 || height > 65535)
152
+ throw new RangeError('TGA: dimensions must be 1-65535');
153
+ let opaque = true;
154
+ for (let i = 3; i < rgba.length; i += 4)
155
+ if (rgba[i] !== 255) {
156
+ opaque = false;
157
+ break;
158
+ }
159
+ const bpp = opaque ? 3 : 4;
160
+ const count = width * height;
161
+ if (rgba.byteOffset % 4)
162
+ rgba = rgba.slice(0, count * 4);
163
+ const pixels = new Uint32Array(rgba.buffer, rgba.byteOffset, count);
164
+ const out = new Uint8Array(18 + count * bpp + Math.ceil(count / 128) + 26);
165
+ out[2] = 10;
166
+ out[12] = width & 255;
167
+ out[13] = width >> 8;
168
+ out[14] = height & 255;
169
+ out[15] = height >> 8;
170
+ out[16] = bpp * 8;
171
+ out[17] = 32 | (opaque ? 0 : 8);
172
+ let o = 18;
173
+ const put = (i) => {
174
+ const p = i * 4;
175
+ out[o++] = rgba[p + 2];
176
+ out[o++] = rgba[p + 1];
177
+ out[o++] = rgba[p];
178
+ if (bpp === 4)
179
+ out[o++] = rgba[p + 3];
180
+ };
181
+ for (let i = 0; i < count;) {
182
+ let run = 1;
183
+ while (run < 128 && i + run < count && pixels[i + run] === pixels[i])
184
+ run++;
185
+ if (run >= 2) {
186
+ out[o++] = 128 | run - 1;
187
+ put(i);
188
+ i += run;
189
+ continue;
190
+ }
191
+ let raw = 1;
192
+ while (raw < 128 && i + raw < count && !(i + raw + 1 < count && pixels[i + raw] === pixels[i + raw + 1]))
193
+ raw++;
194
+ out[o++] = raw - 1;
195
+ for (let k = 0; k < raw; k++)
196
+ put(i + k);
197
+ i += raw;
198
+ }
199
+ o += 8;
200
+ for (let i = 0; i < SIGNATURE.length; i++)
201
+ out[o++] = SIGNATURE.charCodeAt(i);
202
+ return out.slice(0, o);
203
+ }
@@ -0,0 +1,10 @@
1
+ /*!
2
+ * VP8 constant tables, extracted from RFC 6386 ("VP8 Data Format and Decoding Guide", sections 9.6, 11.5, 13.4,
3
+ * 13.5 and 14.1). Copyright (c) 2010, 2011 Google Inc., BSD license (see THIRD_PARTY_NOTICES.md)
4
+ */
5
+ export declare const KF_BMODE_PROBS: Uint8Array<ArrayBuffer>;
6
+ export declare const COEFF_UPDATE_PROBS: Uint8Array<ArrayBuffer>;
7
+ export declare const DEFAULT_COEFF_PROBS: Uint8Array<ArrayBuffer>;
8
+ export declare const DC_QLOOKUP: Uint8Array<ArrayBuffer>;
9
+ export declare const AC_QLOOKUP: Int16Array<ArrayBuffer>;
10
+ export declare const PCAT: Uint8Array<ArrayBuffer>[];