Skip to content

Commit 87f5e70

Browse files
committed
fix: fixed image drawer
1 parent d9a9bba commit 87f5e70

5 files changed

Lines changed: 56 additions & 59 deletions

File tree

shapes/image/src/GifUtils/ByteStream.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class ByteStream {
99
/**
1010
* current position in `data`
1111
*/
12-
pos: number;
12+
pos;
1313

1414
constructor(bytes: Uint8ClampedArray) {
1515
this.pos = 0;
@@ -65,7 +65,7 @@ export class ByteStream {
6565
do {
6666
size = this.data[this.pos++]!;
6767

68-
for (let count = size; --count >= minCount; blockString += String.fromCharCode(this.data[this.pos++]!)) {
68+
for (let count = size; --count >= minCount; blockString += String.fromCodePoint(this.data[this.pos++]!)) {
6969
// do nothing
7070
}
7171
} while (size !== emptySize);

shapes/image/src/GifUtils/Utils.ts

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-magic-numbers */
22
/* eslint-disable @typescript-eslint/no-non-null-assertion */
3-
import { type ICoordinates, type IRgb, type IRgba, type IShapeDrawData, half } from "@tsparticles/engine";
43
import { type IImage, type ImageParticle, loadImage } from "../Utils.js";
4+
import { type IRgb, type IRgba, type IShapeDrawData, half, originPoint } from "@tsparticles/engine";
55
import { InterlaceOffsets, InterlaceSteps } from "./Constants.js";
66
import type { ApplicationExtension } from "./Types/ApplicationExtension.js";
77
import { ByteStream } from "./ByteStream.js";
@@ -10,11 +10,7 @@ import type { GIF } from "./Types/GIF.js";
1010
import { GIFDataHeaders } from "./Types/GIFDataHeaders.js";
1111
import type { GIFProgressCallbackFunction } from "./Types/GIFProgressCallbackFunction.js";
1212

13-
const origin: ICoordinates = {
14-
x: 0,
15-
y: 0,
16-
},
17-
defaultFrame = 0,
13+
const defaultFrame = 0,
1814
initialTime = 0,
1915
firstIndex = 0,
2016
defaultLoopCount = 0;
@@ -156,6 +152,23 @@ function parseExtensionBlock(
156152
}
157153
}
158154

155+
/**
156+
* __read `len` bits from `imageData` at `pos`__
157+
* @param imageData - `Uint8ClampedArray` to read from
158+
* @param pos - bit position in `imageData`
159+
* @param len - bit length to read [1-12 bits]
160+
* @returns `len` bits at `pos`
161+
*/
162+
function readBits(imageData: Uint8Array, pos: number, len: number): number {
163+
const bytePos = pos >>> 3,
164+
bitPos = pos & 7;
165+
return (
166+
((imageData[bytePos]! + (imageData[bytePos + 1]! << 8) + (imageData[bytePos + 2]! << 16)) &
167+
(((1 << len) - 1) << bitPos)) >>>
168+
bitPos
169+
);
170+
}
171+
159172
/**
160173
* __parsing one image block in GIF data stream__
161174
* @param byteStream - GIF data stream
@@ -239,22 +252,7 @@ async function parseImageBlock(
239252

240253
const minCodeSize = byteStream.nextByte(),
241254
imageData = byteStream.readSubBlocksBin(),
242-
clearCode = 1 << minCodeSize,
243-
/**
244-
* __read `len` bits from `imageData` at `pos`__
245-
* @param pos - bit position in `imageData`
246-
* @param len - bit length to read [1-12 bits]
247-
* @returns `len` bits at `pos`
248-
*/
249-
readBits = (pos: number, len: number): number => {
250-
const bytePos = pos >>> 3,
251-
bitPos = pos & 7;
252-
return (
253-
((imageData[bytePos]! + (imageData[bytePos + 1]! << 8) + (imageData[bytePos + 2]! << 16)) &
254-
(((1 << len) - 1) << bitPos)) >>>
255-
bitPos
256-
);
257-
};
255+
clearCode = 1 << minCodeSize;
258256

259257
if (interlacedFlag) {
260258
for (let code = 0, size = minCodeSize + 1, pos = 0, dic = [[0]], pass = 0; pass < 4; pass++) {
@@ -266,7 +264,7 @@ async function parseImageBlock(
266264
while (!exit) {
267265
const last = code;
268266

269-
code = readBits(pos, size);
267+
code = readBits(imageData, pos, size);
270268
pos += size + 1;
271269

272270
if (code === clearCode) {
@@ -333,7 +331,7 @@ async function parseImageBlock(
333331
for (;;) {
334332
const last = code;
335333

336-
code = readBits(pos, size);
334+
code = readBits(imageData, pos, size);
337335
pos += size;
338336

339337
if (code === clearCode) {
@@ -431,7 +429,7 @@ export function getGIFLoopAmount(gif: GIF): number {
431429
return extension.data[1]! + (extension.data[2]! << 8);
432430
}
433431

434-
return NaN;
432+
return Number.NaN;
435433
}
436434

437435
/**
@@ -634,7 +632,7 @@ export function drawGif(data: IShapeDrawData<ImageParticle>, canvasSettings?: Ca
634632
offscreenContext.imageSmoothingQuality = "low";
635633
offscreenContext.imageSmoothingEnabled = false;
636634

637-
offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
635+
offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
638636

639637
particle.gifLoopCount ??= image.gifLoopCount ?? defaultLoopCount;
640638

@@ -661,7 +659,7 @@ export function drawGif(data: IShapeDrawData<ImageParticle>, canvasSettings?: Ca
661659

662660
context.drawImage(offscreenCanvas, pos.x, pos.y);
663661

664-
offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
662+
offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
665663

666664
break;
667665
case DisposalMethod.Combine:
@@ -675,7 +673,7 @@ export function drawGif(data: IShapeDrawData<ImageParticle>, canvasSettings?: Ca
675673

676674
context.drawImage(offscreenCanvas, pos.x, pos.y);
677675

678-
offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
676+
offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
679677

680678
if (!image.gifData.globalColorTable.length) {
681679
offscreenContext.putImageData(image.gifData.frames[firstIndex]!.image, pos.x + frame.left, pos.y + frame.top);
@@ -687,8 +685,8 @@ export function drawGif(data: IShapeDrawData<ImageParticle>, canvasSettings?: Ca
687685
case DisposalMethod.RestorePrevious:
688686
{
689687
const previousImageData = offscreenContext.getImageData(
690-
origin.x,
691-
origin.y,
688+
originPoint.x,
689+
originPoint.y,
692690
offscreenCanvas.width,
693691
offscreenCanvas.height,
694692
);
@@ -697,8 +695,8 @@ export function drawGif(data: IShapeDrawData<ImageParticle>, canvasSettings?: Ca
697695

698696
context.drawImage(offscreenCanvas, pos.x, pos.y);
699697

700-
offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
701-
offscreenContext.putImageData(previousImageData, origin.x, origin.y);
698+
offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
699+
offscreenContext.putImageData(previousImageData, originPoint.x, originPoint.y);
702700
}
703701
break;
704702
}
@@ -716,7 +714,7 @@ export function drawGif(data: IShapeDrawData<ImageParticle>, canvasSettings?: Ca
716714
frameIndex = firstIndex;
717715

718716
// ? so apparently some GIFs seam to set the disposal method of the last frame wrong?...so this is a "fix" for that (clear after the last frame)
719-
offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
717+
offscreenContext.clearRect(originPoint.x, originPoint.y, offscreenCanvas.width, offscreenCanvas.height);
720718
}
721719

722720
particle.gifFrame = frameIndex;

shapes/image/src/ImageDrawer.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
defaultRatio,
77
double,
88
} from "@tsparticles/engine";
9-
import { type IImage, type IParticleImage, type ImageParticle, replaceImageColor } from "./Utils.js";
9+
import { type IImage, type IParticleImage, type ImageParticle, replaceImageColor, shapeTypes } from "./Utils.js";
1010
import type { ImageContainer, ImageEngine } from "./types.js";
1111
import type { IImageShape } from "./IImageShape.js";
1212
import { drawGif } from "./GifUtils/Utils.js";
@@ -32,9 +32,7 @@ export class ImageDrawer implements IShapeDrawer<ImageParticle> {
3232
* @param image - the image to add to the container collection
3333
*/
3434
addImage(image: IImage): void {
35-
this._engine.images ??= [];
36-
37-
this._engine.images.push(image);
35+
this._engine.images?.push(image);
3836
}
3937

4038
/**
@@ -90,25 +88,25 @@ export class ImageDrawer implements IShapeDrawer<ImageParticle> {
9088
}
9189

9290
loadShape(particle: ImageParticle): void {
93-
if (particle.shape !== "image" && particle.shape !== "images") {
91+
if (!particle.shape || !shapeTypes.includes(particle.shape)) {
9492
return;
9593
}
9694

97-
this._engine.images ??= [];
98-
9995
const imageData = particle.shapeData as IImageShape | undefined;
10096

10197
if (!imageData) {
10298
return;
10399
}
104100

105-
const image = this._engine.images.find((t: IImage) => t.name === imageData.name || t.source === imageData.src);
101+
const image = this._engine.images?.find((t: IImage) => t.name === imageData.name || t.source === imageData.src);
106102

107-
if (!image) {
108-
void this.loadImageShape(imageData).then(() => {
109-
this.loadShape(particle);
110-
});
103+
if (image) {
104+
return;
111105
}
106+
107+
void this.loadImageShape(imageData).then(() => {
108+
this.loadShape(particle);
109+
});
112110
}
113111

114112
/**
@@ -121,8 +119,6 @@ export class ImageDrawer implements IShapeDrawer<ImageParticle> {
121119
return;
122120
}
123121

124-
this._engine.images ??= [];
125-
126122
const images = this._engine.images,
127123
imageData = particle.shapeData as IImageShape | undefined;
128124

@@ -131,7 +127,7 @@ export class ImageDrawer implements IShapeDrawer<ImageParticle> {
131127
}
132128

133129
const color = particle.getFillColor(),
134-
image = images.find((t: IImage) => t.name === imageData.name || t.source === imageData.src);
130+
image = images?.find((t: IImage) => t.name === imageData.name || t.source === imageData.src);
135131

136132
if (!image) {
137133
return;

shapes/image/src/Utils.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { type IHsl, type Particle, getLogger, getStyleFromHsl } from "@tsparticl
22
import type { GIF } from "./GifUtils/Types/GIF.js";
33
import type { IImageShape } from "./IImageShape.js";
44

5+
export const shapeTypes = ["image", "images"];
6+
57
const stringStart = 0,
68
defaultOpacity = 1;
79

@@ -75,7 +77,7 @@ function replaceColorSvg(imageShape: IImage, color: IHsl, opacity: number, hdr =
7577

7678
/* set color to svg element */
7779
if (svgData.includes("fill")) {
78-
return svgData.replace(currentColorRegex, () => colorStyle);
80+
return svgData.replaceAll(currentColorRegex, () => colorStyle);
7981
}
8082

8183
const preFillIndex = svgData.indexOf(">");
@@ -88,7 +90,7 @@ function replaceColorSvg(imageShape: IImage, color: IHsl, opacity: number, hdr =
8890
* @param image - the image to load
8991
*/
9092
export async function loadImage(image: IImage): Promise<void> {
91-
return new Promise<void>((resolve: () => void) => {
93+
return new Promise<void>(resolve => {
9294
image.loading = true;
9395

9496
const img = new Image();
@@ -130,12 +132,12 @@ export async function downloadSvgImage(image: IImage): Promise<void> {
130132

131133
const response = await fetch(image.source);
132134

133-
if (!response.ok) {
135+
if (response.ok) {
136+
image.svgData = await response.text();
137+
} else {
134138
getLogger().error("Image not found");
135139

136140
image.error = true;
137-
} else {
138-
image.svgData = await response.text();
139141
}
140142

141143
image.loading = false;

shapes/image/src/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import type { IImage } from "./Utils.js";
1+
import { type IImage, shapeTypes } from "./Utils.js";
22
import type { IPreload } from "./Options/Interfaces/IPreload.js";
33
import type { ImageEngine } from "./types.js";
44

5-
declare const __VERSION__: string,
6-
extLength = 3;
5+
const extLength = 3;
76

87
/**
98
*
@@ -62,6 +61,8 @@ function addLoadImageToEngine(engine: ImageEngine): void {
6261
};
6362
}
6463

64+
declare const __VERSION__: string;
65+
6566
/**
6667
* Loads the image shape in the given engine
6768
* @param engine - the engine where the image shape is going to be added
@@ -77,7 +78,7 @@ export async function loadImageShape(engine: ImageEngine): Promise<void> {
7778
const preloader = new ImagePreloaderPlugin();
7879

7980
e.addPlugin(preloader);
80-
e.addShape(["image", "images"], async () => {
81+
e.addShape(shapeTypes, async () => {
8182
const { ImageDrawer } = await import("./ImageDrawer.js");
8283

8384
return new ImageDrawer(e);

0 commit comments

Comments
 (0)