@tsparticles/shape-image 3.0.0-alpha.1 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +16 -12
  2. package/browser/GifUtils/ByteStream.js +44 -0
  3. package/browser/GifUtils/Constants.js +2 -0
  4. package/browser/GifUtils/Enums/DisposalMethod.js +1 -0
  5. package/browser/GifUtils/Types/ApplicationExtension.js +1 -0
  6. package/browser/GifUtils/Types/Frame.js +1 -0
  7. package/browser/GifUtils/Types/GIF.js +1 -0
  8. package/browser/GifUtils/Types/GIFDataHeaders.js +1 -0
  9. package/browser/GifUtils/Types/GIFProgressCallbackFunction.js +1 -0
  10. package/browser/GifUtils/Types/PlainTextData.js +1 -0
  11. package/browser/GifUtils/Utils.js +324 -0
  12. package/browser/ImageDrawer.js +122 -58
  13. package/browser/ImagePreloader.js +33 -0
  14. package/browser/Options/Classes/Preload.js +29 -0
  15. package/browser/Options/Interfaces/IPreload.js +1 -0
  16. package/browser/Utils.js +35 -9
  17. package/browser/index.js +43 -3
  18. package/browser/package.json +1 -0
  19. package/browser/types.js +1 -0
  20. package/cjs/GifUtils/ByteStream.js +48 -0
  21. package/cjs/GifUtils/Constants.js +5 -0
  22. package/cjs/GifUtils/Enums/DisposalMethod.js +2 -0
  23. package/cjs/GifUtils/Types/ApplicationExtension.js +2 -0
  24. package/cjs/GifUtils/Types/Frame.js +2 -0
  25. package/cjs/GifUtils/Types/GIF.js +2 -0
  26. package/cjs/GifUtils/Types/GIFDataHeaders.js +2 -0
  27. package/cjs/GifUtils/Types/GIFProgressCallbackFunction.js +2 -0
  28. package/cjs/GifUtils/Types/PlainTextData.js +2 -0
  29. package/cjs/GifUtils/Utils.js +329 -0
  30. package/cjs/ImageDrawer.js +125 -72
  31. package/cjs/ImagePreloader.js +37 -0
  32. package/cjs/Options/Classes/Preload.js +33 -0
  33. package/cjs/Options/Interfaces/IPreload.js +2 -0
  34. package/cjs/Utils.js +66 -52
  35. package/cjs/index.js +43 -14
  36. package/cjs/package.json +1 -0
  37. package/cjs/types.js +2 -0
  38. package/esm/GifUtils/ByteStream.js +44 -0
  39. package/esm/GifUtils/Constants.js +2 -0
  40. package/esm/GifUtils/Enums/DisposalMethod.js +1 -0
  41. package/esm/GifUtils/Types/ApplicationExtension.js +1 -0
  42. package/esm/GifUtils/Types/Frame.js +1 -0
  43. package/esm/GifUtils/Types/GIF.js +1 -0
  44. package/esm/GifUtils/Types/GIFDataHeaders.js +1 -0
  45. package/esm/GifUtils/Types/GIFProgressCallbackFunction.js +1 -0
  46. package/esm/GifUtils/Types/PlainTextData.js +1 -0
  47. package/esm/GifUtils/Utils.js +324 -0
  48. package/esm/ImageDrawer.js +122 -58
  49. package/esm/ImagePreloader.js +33 -0
  50. package/esm/Options/Classes/Preload.js +29 -0
  51. package/esm/Options/Interfaces/IPreload.js +1 -0
  52. package/esm/Utils.js +35 -9
  53. package/esm/index.js +43 -3
  54. package/esm/package.json +1 -0
  55. package/esm/types.js +1 -0
  56. package/package.json +19 -6
  57. package/report.html +4 -4
  58. package/tsparticles.shape.image.js +676 -73
  59. package/tsparticles.shape.image.min.js +1 -1
  60. package/tsparticles.shape.image.min.js.LICENSE.txt +1 -8
  61. package/types/GifUtils/ByteStream.d.ts +11 -0
  62. package/types/GifUtils/Constants.d.ts +2 -0
  63. package/types/GifUtils/Enums/DisposalMethod.d.ts +10 -0
  64. package/types/GifUtils/Types/ApplicationExtension.d.ts +5 -0
  65. package/types/GifUtils/Types/Frame.d.ts +19 -0
  66. package/types/GifUtils/Types/GIF.d.ts +16 -0
  67. package/types/GifUtils/Types/GIFDataHeaders.d.ts +9 -0
  68. package/types/GifUtils/Types/GIFProgressCallbackFunction.d.ts +2 -0
  69. package/types/GifUtils/Types/PlainTextData.d.ts +11 -0
  70. package/types/GifUtils/Utils.d.ts +4 -0
  71. package/types/IImageShape.d.ts +2 -1
  72. package/types/ImageDrawer.d.ts +9 -9
  73. package/types/ImagePreloader.d.ts +10 -0
  74. package/types/Options/Classes/Preload.d.ts +12 -0
  75. package/types/Options/Interfaces/IPreload.d.ts +8 -0
  76. package/types/Utils.d.ts +16 -6
  77. package/types/index.d.ts +2 -2
  78. package/types/types.d.ts +17 -0
  79. package/umd/GifUtils/ByteStream.js +58 -0
  80. package/umd/GifUtils/Constants.js +15 -0
  81. package/umd/GifUtils/Enums/DisposalMethod.js +12 -0
  82. package/umd/GifUtils/Types/ApplicationExtension.js +12 -0
  83. package/umd/GifUtils/Types/Frame.js +12 -0
  84. package/umd/GifUtils/Types/GIF.js +12 -0
  85. package/umd/GifUtils/Types/GIFDataHeaders.js +12 -0
  86. package/umd/GifUtils/Types/GIFProgressCallbackFunction.js +12 -0
  87. package/umd/GifUtils/Types/PlainTextData.js +12 -0
  88. package/umd/GifUtils/Utils.js +339 -0
  89. package/umd/ImageDrawer.js +124 -60
  90. package/umd/ImagePreloader.js +47 -0
  91. package/umd/Options/Classes/Preload.js +43 -0
  92. package/umd/Options/Interfaces/IPreload.js +12 -0
  93. package/umd/Utils.js +37 -10
  94. package/umd/index.js +44 -4
  95. package/umd/types.js +12 -0
package/cjs/Utils.js CHANGED
@@ -1,16 +1,8 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.replaceImageColor = exports.downloadSvgImage = exports.loadImage = void 0;
3
+ exports.replaceImageColor = exports.downloadSvgImage = exports.loadGifImage = exports.loadImage = void 0;
13
4
  const engine_1 = require("@tsparticles/engine");
5
+ const Utils_js_1 = require("./GifUtils/Utils.js");
14
6
  const currentColorRegex = /(#(?:[0-9a-f]{2}){2,4}|(#[0-9a-f]{3})|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d.]+%?\))|currentcolor/gi;
15
7
  function replaceColorSvg(imageShape, color, opacity) {
16
8
  const { svgData } = imageShape;
@@ -24,55 +16,73 @@ function replaceColorSvg(imageShape, color, opacity) {
24
16
  const preFillIndex = svgData.indexOf(">");
25
17
  return `${svgData.substring(0, preFillIndex)} fill="${colorStyle}"${svgData.substring(preFillIndex)}`;
26
18
  }
27
- function loadImage(image) {
28
- return __awaiter(this, void 0, void 0, function* () {
29
- return new Promise((resolve) => {
30
- image.loading = true;
31
- const img = new Image();
32
- image.element = img;
33
- img.addEventListener("load", () => {
34
- image.loading = false;
35
- resolve();
36
- });
37
- img.addEventListener("error", () => {
38
- image.element = undefined;
39
- image.error = true;
40
- image.loading = false;
41
- console.error(`Error tsParticles - loading image: ${image.source}`);
42
- resolve();
43
- });
44
- img.src = image.source;
19
+ async function loadImage(image) {
20
+ return new Promise((resolve) => {
21
+ image.loading = true;
22
+ const img = new Image();
23
+ image.element = img;
24
+ img.addEventListener("load", () => {
25
+ image.loading = false;
26
+ resolve();
27
+ });
28
+ img.addEventListener("error", () => {
29
+ image.element = undefined;
30
+ image.error = true;
31
+ image.loading = false;
32
+ (0, engine_1.getLogger)().error(`${engine_1.errorPrefix} loading image: ${image.source}`);
33
+ resolve();
45
34
  });
35
+ img.src = image.source;
46
36
  });
47
37
  }
48
38
  exports.loadImage = loadImage;
49
- function downloadSvgImage(image) {
50
- return __awaiter(this, void 0, void 0, function* () {
51
- if (image.type !== "svg") {
52
- yield loadImage(image);
53
- return;
54
- }
55
- image.loading = true;
56
- const response = yield fetch(image.source);
57
- if (!response.ok) {
58
- console.error("Error tsParticles - Image not found");
59
- image.error = true;
60
- }
61
- if (!image.error) {
62
- image.svgData = yield response.text();
39
+ async function loadGifImage(image) {
40
+ if (image.type !== "gif") {
41
+ await loadImage(image);
42
+ return;
43
+ }
44
+ image.loading = true;
45
+ try {
46
+ image.gifData = await (0, Utils_js_1.decodeGIF)(image.source);
47
+ image.gifLoopCount = (0, Utils_js_1.getGIFLoopAmount)(image.gifData) ?? 0;
48
+ if (image.gifLoopCount === 0) {
49
+ image.gifLoopCount = Infinity;
63
50
  }
64
- image.loading = false;
65
- });
51
+ }
52
+ catch {
53
+ image.error = true;
54
+ }
55
+ image.loading = false;
56
+ }
57
+ exports.loadGifImage = loadGifImage;
58
+ async function downloadSvgImage(image) {
59
+ if (image.type !== "svg") {
60
+ await loadImage(image);
61
+ return;
62
+ }
63
+ image.loading = true;
64
+ const response = await fetch(image.source);
65
+ if (!response.ok) {
66
+ (0, engine_1.getLogger)().error(`${engine_1.errorPrefix} Image not found`);
67
+ image.error = true;
68
+ }
69
+ else {
70
+ image.svgData = await response.text();
71
+ }
72
+ image.loading = false;
66
73
  }
67
74
  exports.downloadSvgImage = downloadSvgImage;
68
75
  function replaceImageColor(image, imageData, color, particle) {
69
- var _a, _b, _c;
70
- const svgColoredData = replaceColorSvg(image, color, (_b = (_a = particle.opacity) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : 1), imageRes = {
76
+ const svgColoredData = replaceColorSvg(image, color, particle.opacity?.value ?? 1), imageRes = {
71
77
  color,
72
- data: Object.assign(Object.assign({}, image), { svgData: svgColoredData }),
78
+ gif: imageData.gif,
79
+ data: {
80
+ ...image,
81
+ svgData: svgColoredData,
82
+ },
73
83
  loaded: false,
74
84
  ratio: imageData.width / imageData.height,
75
- replaceColor: (_c = imageData.replaceColor) !== null && _c !== void 0 ? _c : imageData.replace_color,
85
+ replaceColor: imageData.replaceColor,
76
86
  source: imageData.src,
77
87
  };
78
88
  return new Promise((resolve) => {
@@ -83,14 +93,18 @@ function replaceImageColor(image, imageData, color, particle) {
83
93
  resolve(imageRes);
84
94
  domUrl.revokeObjectURL(url);
85
95
  });
86
- img.addEventListener("error", () => __awaiter(this, void 0, void 0, function* () {
96
+ img.addEventListener("error", async () => {
87
97
  domUrl.revokeObjectURL(url);
88
- const img2 = Object.assign(Object.assign({}, image), { error: false, loading: true });
89
- yield loadImage(img2);
98
+ const img2 = {
99
+ ...image,
100
+ error: false,
101
+ loading: true,
102
+ };
103
+ await loadImage(img2);
90
104
  imageRes.loaded = true;
91
105
  imageRes.element = img2.element;
92
106
  resolve(imageRes);
93
- }));
107
+ });
94
108
  img.src = url;
95
109
  });
96
110
  }
package/cjs/index.js CHANGED
@@ -1,19 +1,48 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.loadImageShape = void 0;
13
- const ImageDrawer_1 = require("./ImageDrawer");
14
- function loadImageShape(engine) {
15
- return __awaiter(this, void 0, void 0, function* () {
16
- yield engine.addShape(["image", "images"], new ImageDrawer_1.ImageDrawer());
17
- });
4
+ const Utils_js_1 = require("./Utils.js");
5
+ const ImageDrawer_js_1 = require("./ImageDrawer.js");
6
+ const ImagePreloader_js_1 = require("./ImagePreloader.js");
7
+ const engine_1 = require("@tsparticles/engine");
8
+ function addLoadImageToEngine(engine) {
9
+ if (engine.loadImage) {
10
+ return;
11
+ }
12
+ engine.loadImage = async (data) => {
13
+ if (!data.name && !data.src) {
14
+ throw new Error(`${engine_1.errorPrefix} no image source provided`);
15
+ }
16
+ if (!engine.images) {
17
+ engine.images = [];
18
+ }
19
+ if (engine.images.find((t) => t.name === data.name || t.source === data.src)) {
20
+ return;
21
+ }
22
+ try {
23
+ const image = {
24
+ gif: data.gif ?? false,
25
+ name: data.name ?? data.src,
26
+ source: data.src,
27
+ type: data.src.substring(data.src.length - 3),
28
+ error: false,
29
+ loading: true,
30
+ replaceColor: data.replaceColor,
31
+ ratio: data.width && data.height ? data.width / data.height : undefined,
32
+ };
33
+ engine.images.push(image);
34
+ const imageFunc = data.gif ? Utils_js_1.loadGifImage : data.replaceColor ? Utils_js_1.downloadSvgImage : Utils_js_1.loadImage;
35
+ await imageFunc(image);
36
+ }
37
+ catch {
38
+ throw new Error(`${engine_1.errorPrefix} ${data.name ?? data.src} not found`);
39
+ }
40
+ };
41
+ }
42
+ async function loadImageShape(engine, refresh = true) {
43
+ addLoadImageToEngine(engine);
44
+ const preloader = new ImagePreloader_js_1.ImagePreloaderPlugin(engine);
45
+ await engine.addPlugin(preloader, refresh);
46
+ await engine.addShape(["image", "images"], new ImageDrawer_js_1.ImageDrawer(engine), refresh);
18
47
  }
19
48
  exports.loadImageShape = loadImageShape;
@@ -0,0 +1 @@
1
+ { "type": "commonjs" }
package/cjs/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,44 @@
1
+ export class ByteStream {
2
+ constructor(bytes) {
3
+ this.pos = 0;
4
+ this.data = new Uint8ClampedArray(bytes);
5
+ }
6
+ getString(count) {
7
+ const slice = this.data.slice(this.pos, this.pos + count);
8
+ this.pos += slice.length;
9
+ return slice.reduce((acc, curr) => acc + String.fromCharCode(curr), "");
10
+ }
11
+ nextByte() {
12
+ return this.data[this.pos++];
13
+ }
14
+ nextTwoBytes() {
15
+ this.pos += 2;
16
+ return this.data[this.pos - 2] + (this.data[this.pos - 1] << 8);
17
+ }
18
+ readSubBlocks() {
19
+ let blockString = "", size = 0;
20
+ do {
21
+ size = this.data[this.pos++];
22
+ for (let count = size; --count >= 0; blockString += String.fromCharCode(this.data[this.pos++])) {
23
+ }
24
+ } while (size !== 0);
25
+ return blockString;
26
+ }
27
+ readSubBlocksBin() {
28
+ let size = 0, len = 0;
29
+ for (let offset = 0; (size = this.data[this.pos + offset]) !== 0; offset += size + 1) {
30
+ len += size;
31
+ }
32
+ const blockData = new Uint8Array(len);
33
+ for (let i = 0; (size = this.data[this.pos++]) !== 0;) {
34
+ for (let count = size; --count >= 0; blockData[i++] = this.data[this.pos++]) {
35
+ }
36
+ }
37
+ return blockData;
38
+ }
39
+ skipSubBlocks() {
40
+ for (; this.data[this.pos] !== 0; this.pos += this.data[this.pos] + 1) {
41
+ }
42
+ this.pos++;
43
+ }
44
+ }
@@ -0,0 +1,2 @@
1
+ export const InterlaceOffsets = [0, 4, 2, 1];
2
+ export const InterlaceSteps = [8, 8, 4, 2];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,324 @@
1
+ import { InterlaceOffsets, InterlaceSteps } from "./Constants.js";
2
+ import { ByteStream } from "./ByteStream.js";
3
+ function parseColorTable(byteStream, count) {
4
+ const colors = [];
5
+ for (let i = 0; i < count; i++) {
6
+ colors.push({
7
+ r: byteStream.data[byteStream.pos],
8
+ g: byteStream.data[byteStream.pos + 1],
9
+ b: byteStream.data[byteStream.pos + 2],
10
+ });
11
+ byteStream.pos += 3;
12
+ }
13
+ return colors;
14
+ }
15
+ async function parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex) {
16
+ switch (byteStream.nextByte()) {
17
+ case 249: {
18
+ const frame = gif.frames[getFrameIndex(false)];
19
+ byteStream.pos++;
20
+ const packedByte = byteStream.nextByte();
21
+ frame.GCreserved = (packedByte & 0xe0) >>> 5;
22
+ frame.disposalMethod = (packedByte & 0x1c) >>> 2;
23
+ frame.userInputDelayFlag = (packedByte & 2) === 2;
24
+ const transparencyFlag = (packedByte & 1) === 1;
25
+ frame.delayTime = byteStream.nextTwoBytes() * 0xa;
26
+ const transparencyIndex = byteStream.nextByte();
27
+ if (transparencyFlag) {
28
+ getTransparencyIndex(transparencyIndex);
29
+ }
30
+ byteStream.pos++;
31
+ break;
32
+ }
33
+ case 255: {
34
+ byteStream.pos++;
35
+ const applicationExtension = {
36
+ identifier: byteStream.getString(8),
37
+ authenticationCode: byteStream.getString(3),
38
+ data: byteStream.readSubBlocksBin(),
39
+ };
40
+ gif.applicationExtensions.push(applicationExtension);
41
+ break;
42
+ }
43
+ case 254: {
44
+ gif.comments.push([getFrameIndex(false), byteStream.readSubBlocks()]);
45
+ break;
46
+ }
47
+ case 1: {
48
+ if (gif.globalColorTable.length === 0) {
49
+ throw new EvalError("plain text extension without global color table");
50
+ }
51
+ byteStream.pos++;
52
+ gif.frames[getFrameIndex(false)].plainTextData = {
53
+ left: byteStream.nextTwoBytes(),
54
+ top: byteStream.nextTwoBytes(),
55
+ width: byteStream.nextTwoBytes(),
56
+ height: byteStream.nextTwoBytes(),
57
+ charSize: {
58
+ width: byteStream.nextTwoBytes(),
59
+ height: byteStream.nextTwoBytes(),
60
+ },
61
+ foregroundColor: byteStream.nextByte(),
62
+ backgroundColor: byteStream.nextByte(),
63
+ text: byteStream.readSubBlocks(),
64
+ };
65
+ break;
66
+ }
67
+ default:
68
+ byteStream.skipSubBlocks();
69
+ break;
70
+ }
71
+ }
72
+ async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, progressCallback) {
73
+ const frame = gif.frames[getFrameIndex(true)];
74
+ frame.left = byteStream.nextTwoBytes();
75
+ frame.top = byteStream.nextTwoBytes();
76
+ frame.width = byteStream.nextTwoBytes();
77
+ frame.height = byteStream.nextTwoBytes();
78
+ const packedByte = byteStream.nextByte(), localColorTableFlag = (packedByte & 0x80) === 0x80, interlacedFlag = (packedByte & 0x40) === 0x40;
79
+ frame.sortFlag = (packedByte & 0x20) === 0x20;
80
+ frame.reserved = (packedByte & 0x18) >>> 3;
81
+ const localColorCount = 1 << ((packedByte & 7) + 1);
82
+ if (localColorTableFlag) {
83
+ frame.localColorTable = parseColorTable(byteStream, localColorCount);
84
+ }
85
+ const getColor = (index) => {
86
+ const { r, g, b } = (localColorTableFlag ? frame.localColorTable : gif.globalColorTable)[index];
87
+ return { r, g, b, a: index === getTransparencyIndex(null) ? (avgAlpha ? ~~((r + g + b) / 3) : 0) : 255 };
88
+ };
89
+ const image = (() => {
90
+ try {
91
+ return new ImageData(frame.width, frame.height, { colorSpace: "srgb" });
92
+ }
93
+ catch (error) {
94
+ if (error instanceof DOMException && error.name === "IndexSizeError") {
95
+ return null;
96
+ }
97
+ throw error;
98
+ }
99
+ })();
100
+ if (image == null) {
101
+ throw new EvalError("GIF frame size is to large");
102
+ }
103
+ const minCodeSize = byteStream.nextByte(), imageData = byteStream.readSubBlocksBin(), clearCode = 1 << minCodeSize;
104
+ const readBits = (pos, len) => {
105
+ const bytePos = pos >>> 3, bitPos = pos & 7;
106
+ return (((imageData[bytePos] + (imageData[bytePos + 1] << 8) + (imageData[bytePos + 2] << 16)) &
107
+ (((1 << len) - 1) << bitPos)) >>>
108
+ bitPos);
109
+ };
110
+ if (interlacedFlag) {
111
+ for (let code = 0, size = minCodeSize + 1, pos = 0, dic = [[0]], pass = 0; pass < 4; pass++) {
112
+ if (InterlaceOffsets[pass] < frame.height) {
113
+ for (let pixelPos = 0, lineIndex = 0;;) {
114
+ const last = code;
115
+ code = readBits(pos, size);
116
+ pos += size + 1;
117
+ if (code === clearCode) {
118
+ size = minCodeSize + 1;
119
+ dic.length = clearCode + 2;
120
+ for (let i = 0; i < dic.length; i++) {
121
+ dic[i] = i < clearCode ? [i] : [];
122
+ }
123
+ }
124
+ else {
125
+ if (code >= dic.length) {
126
+ dic.push(dic[last].concat(dic[last][0]));
127
+ }
128
+ else if (last !== clearCode) {
129
+ dic.push(dic[last].concat(dic[code][0]));
130
+ }
131
+ for (let i = 0; i < dic[code].length; i++) {
132
+ const { r, g, b, a } = getColor(dic[code][i]);
133
+ image.data.set([r, g, b, a], InterlaceOffsets[pass] * frame.width +
134
+ InterlaceSteps[pass] * lineIndex +
135
+ (pixelPos % (frame.width * 4)));
136
+ pixelPos += 4;
137
+ }
138
+ if (dic.length === 1 << size && size < 0xc) {
139
+ size++;
140
+ }
141
+ }
142
+ if (pixelPos === frame.width * 4 * (lineIndex + 1)) {
143
+ lineIndex++;
144
+ if (InterlaceOffsets[pass] + InterlaceSteps[pass] * lineIndex >= frame.height) {
145
+ break;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ progressCallback?.(byteStream.pos / (byteStream.data.length - 1), getFrameIndex(false) + 1, image, { x: frame.left, y: frame.top }, { width: gif.width, height: gif.height });
151
+ }
152
+ frame.image = image;
153
+ frame.bitmap = await createImageBitmap(image);
154
+ }
155
+ else {
156
+ for (let code = 0, size = minCodeSize + 1, pos = 0, dic = [[0]], pixelPos = -4;;) {
157
+ const last = code;
158
+ code = readBits(pos, size);
159
+ pos += size;
160
+ if (code === clearCode) {
161
+ size = minCodeSize + 1;
162
+ dic.length = clearCode + 2;
163
+ for (let i = 0; i < dic.length; i++) {
164
+ dic[i] = i < clearCode ? [i] : [];
165
+ }
166
+ }
167
+ else {
168
+ if (code === clearCode + 1) {
169
+ break;
170
+ }
171
+ if (code >= dic.length) {
172
+ dic.push(dic[last].concat(dic[last][0]));
173
+ }
174
+ else if (last !== clearCode) {
175
+ dic.push(dic[last].concat(dic[code][0]));
176
+ }
177
+ for (let i = 0; i < dic[code].length; i++) {
178
+ const { r, g, b, a } = getColor(dic[code][i]);
179
+ image.data.set([r, g, b, a], (pixelPos += 4));
180
+ }
181
+ if (dic.length >= 1 << size && size < 0xc) {
182
+ size++;
183
+ }
184
+ }
185
+ }
186
+ frame.image = image;
187
+ frame.bitmap = await createImageBitmap(image);
188
+ progressCallback?.((byteStream.pos + 1) / byteStream.data.length, getFrameIndex(false) + 1, frame.image, { x: frame.left, y: frame.top }, { width: gif.width, height: gif.height });
189
+ }
190
+ }
191
+ async function parseBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, progressCallback) {
192
+ switch (byteStream.nextByte()) {
193
+ case 59:
194
+ return true;
195
+ case 44:
196
+ await parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, progressCallback);
197
+ break;
198
+ case 33:
199
+ await parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex);
200
+ break;
201
+ default:
202
+ throw new EvalError("undefined block found");
203
+ }
204
+ return false;
205
+ }
206
+ export function getGIFLoopAmount(gif) {
207
+ for (const extension of gif.applicationExtensions) {
208
+ if (extension.identifier + extension.authenticationCode !== "NETSCAPE2.0") {
209
+ continue;
210
+ }
211
+ return extension.data[1] + (extension.data[2] << 8);
212
+ }
213
+ return NaN;
214
+ }
215
+ export async function decodeGIF(gifURL, progressCallback, avgAlpha) {
216
+ if (!avgAlpha)
217
+ avgAlpha = false;
218
+ const res = await fetch(gifURL);
219
+ if (!res.ok && res.status === 404) {
220
+ throw new EvalError("file not found");
221
+ }
222
+ const buffer = await res.arrayBuffer();
223
+ const gif = {
224
+ width: 0,
225
+ height: 0,
226
+ totalTime: 0,
227
+ colorRes: 0,
228
+ pixelAspectRatio: 0,
229
+ frames: [],
230
+ sortFlag: false,
231
+ globalColorTable: [],
232
+ backgroundImage: new ImageData(1, 1, { colorSpace: "srgb" }),
233
+ comments: [],
234
+ applicationExtensions: [],
235
+ }, byteStream = new ByteStream(new Uint8ClampedArray(buffer));
236
+ if (byteStream.getString(6) !== "GIF89a") {
237
+ throw new Error("not a supported GIF file");
238
+ }
239
+ gif.width = byteStream.nextTwoBytes();
240
+ gif.height = byteStream.nextTwoBytes();
241
+ const packedByte = byteStream.nextByte(), globalColorTableFlag = (packedByte & 0x80) === 0x80;
242
+ gif.colorRes = (packedByte & 0x70) >>> 4;
243
+ gif.sortFlag = (packedByte & 8) === 8;
244
+ const globalColorCount = 1 << ((packedByte & 7) + 1), backgroundColorIndex = byteStream.nextByte();
245
+ gif.pixelAspectRatio = byteStream.nextByte();
246
+ if (gif.pixelAspectRatio !== 0) {
247
+ gif.pixelAspectRatio = (gif.pixelAspectRatio + 0xf) / 0x40;
248
+ }
249
+ if (globalColorTableFlag) {
250
+ gif.globalColorTable = parseColorTable(byteStream, globalColorCount);
251
+ }
252
+ const backgroundImage = (() => {
253
+ try {
254
+ return new ImageData(gif.width, gif.height, { colorSpace: "srgb" });
255
+ }
256
+ catch (error) {
257
+ if (error instanceof DOMException && error.name === "IndexSizeError") {
258
+ return null;
259
+ }
260
+ throw error;
261
+ }
262
+ })();
263
+ if (backgroundImage == null) {
264
+ throw new Error("GIF frame size is to large");
265
+ }
266
+ const { r, g, b } = gif.globalColorTable[backgroundColorIndex];
267
+ backgroundImage.data.set(globalColorTableFlag ? [r, g, b, 255] : [0, 0, 0, 0]);
268
+ for (let i = 4; i < backgroundImage.data.length; i *= 2) {
269
+ backgroundImage.data.copyWithin(i, 0, i);
270
+ }
271
+ gif.backgroundImage = backgroundImage;
272
+ let frameIndex = -1, incrementFrameIndex = true, transparencyIndex = -1;
273
+ const getframeIndex = (increment) => {
274
+ if (increment) {
275
+ incrementFrameIndex = true;
276
+ }
277
+ return frameIndex;
278
+ };
279
+ const getTransparencyIndex = (newValue) => {
280
+ if (newValue != null) {
281
+ transparencyIndex = newValue;
282
+ }
283
+ return transparencyIndex;
284
+ };
285
+ try {
286
+ do {
287
+ if (incrementFrameIndex) {
288
+ gif.frames.push({
289
+ left: 0,
290
+ top: 0,
291
+ width: 0,
292
+ height: 0,
293
+ disposalMethod: 0,
294
+ image: new ImageData(1, 1, { colorSpace: "srgb" }),
295
+ plainTextData: null,
296
+ userInputDelayFlag: false,
297
+ delayTime: 0,
298
+ sortFlag: false,
299
+ localColorTable: [],
300
+ reserved: 0,
301
+ GCreserved: 0,
302
+ });
303
+ frameIndex++;
304
+ transparencyIndex = -1;
305
+ incrementFrameIndex = false;
306
+ }
307
+ } while (!(await parseBlock(byteStream, gif, avgAlpha, getframeIndex, getTransparencyIndex, progressCallback)));
308
+ gif.frames.length--;
309
+ for (const frame of gif.frames) {
310
+ if (frame.userInputDelayFlag && frame.delayTime === 0) {
311
+ gif.totalTime = Infinity;
312
+ break;
313
+ }
314
+ gif.totalTime += frame.delayTime;
315
+ }
316
+ return gif;
317
+ }
318
+ catch (error) {
319
+ if (error instanceof EvalError) {
320
+ throw new Error(`error while parsing frame ${frameIndex} "${error.message}"`);
321
+ }
322
+ throw error;
323
+ }
324
+ }