@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.
- package/README.md +16 -12
- package/browser/GifUtils/ByteStream.js +44 -0
- package/browser/GifUtils/Constants.js +2 -0
- package/browser/GifUtils/Enums/DisposalMethod.js +1 -0
- package/browser/GifUtils/Types/ApplicationExtension.js +1 -0
- package/browser/GifUtils/Types/Frame.js +1 -0
- package/browser/GifUtils/Types/GIF.js +1 -0
- package/browser/GifUtils/Types/GIFDataHeaders.js +1 -0
- package/browser/GifUtils/Types/GIFProgressCallbackFunction.js +1 -0
- package/browser/GifUtils/Types/PlainTextData.js +1 -0
- package/browser/GifUtils/Utils.js +324 -0
- package/browser/ImageDrawer.js +122 -58
- package/browser/ImagePreloader.js +33 -0
- package/browser/Options/Classes/Preload.js +29 -0
- package/browser/Options/Interfaces/IPreload.js +1 -0
- package/browser/Utils.js +35 -9
- package/browser/index.js +43 -3
- package/browser/package.json +1 -0
- package/browser/types.js +1 -0
- package/cjs/GifUtils/ByteStream.js +48 -0
- package/cjs/GifUtils/Constants.js +5 -0
- package/cjs/GifUtils/Enums/DisposalMethod.js +2 -0
- package/cjs/GifUtils/Types/ApplicationExtension.js +2 -0
- package/cjs/GifUtils/Types/Frame.js +2 -0
- package/cjs/GifUtils/Types/GIF.js +2 -0
- package/cjs/GifUtils/Types/GIFDataHeaders.js +2 -0
- package/cjs/GifUtils/Types/GIFProgressCallbackFunction.js +2 -0
- package/cjs/GifUtils/Types/PlainTextData.js +2 -0
- package/cjs/GifUtils/Utils.js +329 -0
- package/cjs/ImageDrawer.js +125 -72
- package/cjs/ImagePreloader.js +37 -0
- package/cjs/Options/Classes/Preload.js +33 -0
- package/cjs/Options/Interfaces/IPreload.js +2 -0
- package/cjs/Utils.js +66 -52
- package/cjs/index.js +43 -14
- package/cjs/package.json +1 -0
- package/cjs/types.js +2 -0
- package/esm/GifUtils/ByteStream.js +44 -0
- package/esm/GifUtils/Constants.js +2 -0
- package/esm/GifUtils/Enums/DisposalMethod.js +1 -0
- package/esm/GifUtils/Types/ApplicationExtension.js +1 -0
- package/esm/GifUtils/Types/Frame.js +1 -0
- package/esm/GifUtils/Types/GIF.js +1 -0
- package/esm/GifUtils/Types/GIFDataHeaders.js +1 -0
- package/esm/GifUtils/Types/GIFProgressCallbackFunction.js +1 -0
- package/esm/GifUtils/Types/PlainTextData.js +1 -0
- package/esm/GifUtils/Utils.js +324 -0
- package/esm/ImageDrawer.js +122 -58
- package/esm/ImagePreloader.js +33 -0
- package/esm/Options/Classes/Preload.js +29 -0
- package/esm/Options/Interfaces/IPreload.js +1 -0
- package/esm/Utils.js +35 -9
- package/esm/index.js +43 -3
- package/esm/package.json +1 -0
- package/esm/types.js +1 -0
- package/package.json +19 -6
- package/report.html +4 -4
- package/tsparticles.shape.image.js +676 -73
- package/tsparticles.shape.image.min.js +1 -1
- package/tsparticles.shape.image.min.js.LICENSE.txt +1 -8
- package/types/GifUtils/ByteStream.d.ts +11 -0
- package/types/GifUtils/Constants.d.ts +2 -0
- package/types/GifUtils/Enums/DisposalMethod.d.ts +10 -0
- package/types/GifUtils/Types/ApplicationExtension.d.ts +5 -0
- package/types/GifUtils/Types/Frame.d.ts +19 -0
- package/types/GifUtils/Types/GIF.d.ts +16 -0
- package/types/GifUtils/Types/GIFDataHeaders.d.ts +9 -0
- package/types/GifUtils/Types/GIFProgressCallbackFunction.d.ts +2 -0
- package/types/GifUtils/Types/PlainTextData.d.ts +11 -0
- package/types/GifUtils/Utils.d.ts +4 -0
- package/types/IImageShape.d.ts +2 -1
- package/types/ImageDrawer.d.ts +9 -9
- package/types/ImagePreloader.d.ts +10 -0
- package/types/Options/Classes/Preload.d.ts +12 -0
- package/types/Options/Interfaces/IPreload.d.ts +8 -0
- package/types/Utils.d.ts +16 -6
- package/types/index.d.ts +2 -2
- package/types/types.d.ts +17 -0
- package/umd/GifUtils/ByteStream.js +58 -0
- package/umd/GifUtils/Constants.js +15 -0
- package/umd/GifUtils/Enums/DisposalMethod.js +12 -0
- package/umd/GifUtils/Types/ApplicationExtension.js +12 -0
- package/umd/GifUtils/Types/Frame.js +12 -0
- package/umd/GifUtils/Types/GIF.js +12 -0
- package/umd/GifUtils/Types/GIFDataHeaders.js +12 -0
- package/umd/GifUtils/Types/GIFProgressCallbackFunction.js +12 -0
- package/umd/GifUtils/Types/PlainTextData.js +12 -0
- package/umd/GifUtils/Utils.js +339 -0
- package/umd/ImageDrawer.js +124 -60
- package/umd/ImagePreloader.js +47 -0
- package/umd/Options/Classes/Preload.js +43 -0
- package/umd/Options/Interfaces/IPreload.js +12 -0
- package/umd/Utils.js +37 -10
- package/umd/index.js +44 -4
- 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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
image.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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", () =>
|
|
96
|
+
img.addEventListener("error", async () => {
|
|
87
97
|
domUrl.revokeObjectURL(url);
|
|
88
|
-
const img2 =
|
|
89
|
-
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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;
|
package/cjs/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "type": "commonjs" }
|
package/cjs/types.js
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|