@tsparticles/shape-image 3.0.3 → 3.2.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.
Files changed (42) hide show
  1. package/21.min.js +2 -0
  2. package/21.min.js.LICENSE.txt +1 -0
  3. package/618.min.js +2 -0
  4. package/618.min.js.LICENSE.txt +1 -0
  5. package/623.min.js +2 -0
  6. package/623.min.js.LICENSE.txt +1 -0
  7. package/browser/GifUtils/ByteStream.js +13 -9
  8. package/browser/GifUtils/Utils.js +113 -10
  9. package/browser/ImageDrawer.js +44 -109
  10. package/browser/ImagePreloader.js +3 -2
  11. package/browser/Utils.js +6 -23
  12. package/browser/index.js +12 -5
  13. package/cjs/GifUtils/ByteStream.js +13 -9
  14. package/cjs/GifUtils/Utils.js +139 -11
  15. package/cjs/ImageDrawer.js +67 -109
  16. package/cjs/ImagePreloader.js +3 -2
  17. package/cjs/Utils.js +7 -25
  18. package/cjs/index.js +36 -6
  19. package/dist_browser_GifUtils_Utils_js.js +50 -0
  20. package/dist_browser_ImageDrawer_js.js +30 -0
  21. package/dist_browser_ImagePreloader_js.js +40 -0
  22. package/esm/GifUtils/ByteStream.js +13 -9
  23. package/esm/GifUtils/Utils.js +113 -10
  24. package/esm/ImageDrawer.js +44 -109
  25. package/esm/ImagePreloader.js +3 -2
  26. package/esm/Utils.js +6 -23
  27. package/esm/index.js +12 -5
  28. package/package.json +2 -2
  29. package/report.html +3 -3
  30. package/tsparticles.shape.image.js +251 -862
  31. package/tsparticles.shape.image.min.js +1 -1
  32. package/tsparticles.shape.image.min.js.LICENSE.txt +1 -1
  33. package/types/GifUtils/Utils.d.ts +4 -0
  34. package/types/ImageDrawer.d.ts +3 -3
  35. package/types/ImagePreloader.d.ts +1 -1
  36. package/types/Utils.d.ts +0 -1
  37. package/umd/GifUtils/ByteStream.js +13 -9
  38. package/umd/GifUtils/Utils.js +140 -11
  39. package/umd/ImageDrawer.js +69 -110
  40. package/umd/ImagePreloader.js +3 -2
  41. package/umd/Utils.js +8 -26
  42. package/umd/index.js +38 -7
package/21.min.js ADDED
@@ -0,0 +1,2 @@
1
+ /*! For license information please see 21.min.js.LICENSE.txt */
2
+ (this.webpackChunk_tsparticles_shape_image=this.webpackChunk_tsparticles_shape_image||[]).push([[21],{21:(e,a,i)=>{i.d(a,{ImageDrawer:()=>s});var t=i(533);class s{constructor(e){this.loadImageShape=async e=>{if(!this._engine.loadImage)throw new Error(`${t.errorPrefix} image shape not initialized`);await this._engine.loadImage({gif:e.gif,name:e.name,replaceColor:e.replaceColor??!1,src:e.src})},this._engine=e}addImage(e){this._engine.images||(this._engine.images=[]),this._engine.images.push(e)}async draw(e){const{context:a,radius:t,particle:s,opacity:n}=e,o=s.image,r=o?.element;if(o){if(a.globalAlpha=n,o.gif&&o.gifData){const{drawGif:a}=await i.e(618).then(i.bind(i,618));a(e)}else if(r){const e=o.ratio,i={x:-t,y:-t},s=2*t;a.drawImage(r,i.x,i.y,s,s/e)}a.globalAlpha=1}}getSidesCount(){return 12}async init(e){const a=e.actualOptions;if(a.preload&&this._engine.loadImage)for(const e of a.preload)await this._engine.loadImage(e)}async loadShape(e){if("image"!==e.shape&&"images"!==e.shape)return;this._engine.images||(this._engine.images=[]);const a=e.shapeData;if(!a)return;this._engine.images.find((e=>e.name===a.name||e.source===a.src))||(await this.loadImageShape(a),await this.loadShape(e))}async particleInit(e,a){if("image"!==a.shape&&"images"!==a.shape)return;this._engine.images||(this._engine.images=[]);const t=this._engine.images,s=a.shapeData;if(!s)return;const n=a.getFillColor(),o=t.find((e=>e.name===s.name||e.source===s.src));if(!o)return;const r=s.replaceColor??o.replaceColor;if(o.loading)return void setTimeout((()=>{this.particleInit(e,a)}));let l;if(o.svgData&&n){const{replaceImageColor:e}=await Promise.resolve().then(i.bind(i,103));l=await e(o,s,n,a)}else l={color:n,data:o,element:o.element,gif:o.gif,gifData:o.gifData,gifLoopCount:o.gifLoopCount,loaded:!0,ratio:s.width&&s.height?s.width/s.height:o.ratio??1,replaceColor:r,source:s.src};l.ratio||(l.ratio=1);const g={image:l,fill:s.fill??a.shapeFill,close:s.close??a.shapeClose};a.image=g.image,a.shapeFill=g.fill,a.shapeClose=g.close}}}}]);
@@ -0,0 +1 @@
1
+ /*! tsParticles Image Shape v3.2.0 by Matteo Bruni */
package/618.min.js ADDED
@@ -0,0 +1,2 @@
1
+ /*! For license information please see 618.min.js.LICENSE.txt */
2
+ (this.webpackChunk_tsparticles_shape_image=this.webpackChunk_tsparticles_shape_image||[]).push([[618],{618:(t,e,a)=>{a.d(e,{drawGif:()=>p,loadGifImage:()=>w});const o=[0,4,2,1],i=[8,8,4,2];class r{constructor(t){this.pos=0,this.data=new Uint8ClampedArray(t)}getString(t){const e=this.data.slice(this.pos,this.pos+t);return this.pos+=e.length,e.reduce(((t,e)=>t+String.fromCharCode(e)),"")}nextByte(){return this.data[this.pos++]}nextTwoBytes(){return this.pos+=2,this.data[this.pos-2]+(this.data[this.pos-1]<<8)}readSubBlocks(){let t="",e=0;do{e=this.data[this.pos++];for(let a=e;--a>=0;t+=String.fromCharCode(this.data[this.pos++]));}while(0!==e);return t}readSubBlocksBin(){let t=this.data[this.pos],e=0;for(let a=0;0!==t;a+=t+1,t=this.data[this.pos+a])e+=t;const a=new Uint8Array(e);t=this.data[this.pos++];for(let e=0;0!==t;t=this.data[this.pos++])for(let o=t;--o>=0;a[e++]=this.data[this.pos++]);return a}skipSubBlocks(){for(const t=1,e=0;this.data[this.pos]!==e;this.pos+=this.data[this.pos]+t);this.pos++}}const s={x:0,y:0},n=0,l=.5,h=0,c=0,g=0;function f(t,e){const a=[];for(let o=0;o<e;o++)a.push({r:t.data[t.pos],g:t.data[t.pos+1],b:t.data[t.pos+2]}),t.pos+=3;return a}async function d(t,e,a,r,s,n){switch(t.nextByte()){case 59:return!0;case 44:await async function(t,e,a,r,s,n){const l=e.frames[r(!0)];l.left=t.nextTwoBytes(),l.top=t.nextTwoBytes(),l.width=t.nextTwoBytes(),l.height=t.nextTwoBytes();const h=t.nextByte(),c=128==(128&h),g=64==(64&h);l.sortFlag=32==(32&h),l.reserved=(24&h)>>>3;const d=1<<1+(7&h);c&&(l.localColorTable=f(t,d));const p=t=>{const{r:o,g:i,b:r}=(c?l.localColorTable:e.globalColorTable)[t];return t!==s(null)?{r:o,g:i,b:r,a:255}:{r:o,g:i,b:r,a:a?~~((o+i+r)/3):0}},w=(()=>{try{return new ImageData(l.width,l.height,{colorSpace:"srgb"})}catch(t){if(t instanceof DOMException&&"IndexSizeError"===t.name)return null;throw t}})();if(null==w)throw new EvalError("GIF frame size is to large");const u=t.nextByte(),m=t.readSubBlocksBin(),y=1<<u,b=(t,e)=>{const a=t>>>3,o=7&t;return(m[a]+(m[a+1]<<8)+(m[a+2]<<16)&(1<<e)-1<<o)>>>o};if(g){for(let a=0,s=u+1,h=0,c=[[0]],g=0;g<4;g++){if(o[g]<l.height){let t=0,e=0,r=!1;for(;!r;){const n=a;if(a=b(h,s),h+=s+1,a===y){s=u+1,c.length=y+2;for(let t=0;t<c.length;t++)c[t]=t<y?[t]:[]}else{a>=c.length?c.push(c[n].concat(c[n][0])):n!==y&&c.push(c[n].concat(c[a][0]));for(const r of c[a]){const{r:a,g:s,b:n,a:h}=p(r);w.data.set([a,s,n,h],o[g]*l.width+i[g]*e+t%(4*l.width)),t+=4}c.length===1<<s&&s<12&&s++}t===4*l.width*(e+1)&&(e++,o[g]+i[g]*e>=l.height&&(r=!0))}}n?.(t.pos/(t.data.length-1),r(!1)+1,w,{x:l.left,y:l.top},{width:e.width,height:e.height})}l.image=w,l.bitmap=await createImageBitmap(w)}else{let a=0,o=u+1,i=0,s=-4,h=!1;const c=[[0]];for(;!h;){const t=a;if(a=b(i,o),i+=o,a===y){o=u+1,c.length=y+2;for(let t=0;t<c.length;t++)c[t]=t<y?[t]:[]}else{if(a===y+1){h=!0;break}a>=c.length?c.push(c[t].concat(c[t][0])):t!==y&&c.push(c[t].concat(c[a][0]));for(const t of c[a]){const{r:e,g:a,b:o,a:i}=p(t);w.data.set([e,a,o,i],s+=4)}c.length>=1<<o&&o<12&&o++}}l.image=w,l.bitmap=await createImageBitmap(w),n?.((t.pos+1)/t.data.length,r(!1)+1,l.image,{x:l.left,y:l.top},{width:e.width,height:e.height})}}(t,e,a,r,s,n);break;case 33:!function(t,e,a,o){switch(t.nextByte()){case 249:{const i=e.frames[a(!1)];t.pos++;const r=t.nextByte();i.GCreserved=(224&r)>>>5,i.disposalMethod=(28&r)>>>2,i.userInputDelayFlag=2==(2&r);const s=1==(1&r);i.delayTime=10*t.nextTwoBytes();const n=t.nextByte();s&&o(n),t.pos++;break}case 255:{t.pos++;const a={identifier:t.getString(8),authenticationCode:t.getString(3),data:t.readSubBlocksBin()};e.applicationExtensions.push(a);break}case 254:e.comments.push([a(!1),t.readSubBlocks()]);break;case 1:if(0===e.globalColorTable.length)throw new EvalError("plain text extension without global color table");t.pos++,e.frames[a(!1)].plainTextData={left:t.nextTwoBytes(),top:t.nextTwoBytes(),width:t.nextTwoBytes(),height:t.nextTwoBytes(),charSize:{width:t.nextTwoBytes(),height:t.nextTwoBytes()},foregroundColor:t.nextByte(),backgroundColor:t.nextByte(),text:t.readSubBlocks()};break;default:t.skipSubBlocks()}}(t,e,r,s);break;default:throw new EvalError("undefined block found")}return!1}function p(t){const{context:e,radius:a,particle:o,delta:i}=t,r=o.image;if(!r?.gifData||!r.gif)return;const f=new OffscreenCanvas(r.gifData.width,r.gifData.height),d=f.getContext("2d");if(!d)throw new Error("could not create offscreen canvas context");d.imageSmoothingQuality="low",d.imageSmoothingEnabled=!1,d.clearRect(s.x,s.y,f.width,f.height),void 0===o.gifLoopCount&&(o.gifLoopCount=r.gifLoopCount??g);let p=o.gifFrame??n;const w={x:-r.gifData.width*l,y:-r.gifData.height*l},u=r.gifData.frames[p];if(void 0===o.gifTime&&(o.gifTime=h),u.bitmap){switch(e.scale(a/r.gifData.width,a/r.gifData.height),u.disposalMethod){case 4:case 5:case 6:case 7:case 0:d.drawImage(u.bitmap,u.left,u.top),e.drawImage(f,w.x,w.y),d.clearRect(s.x,s.y,f.width,f.height);break;case 1:d.drawImage(u.bitmap,u.left,u.top),e.drawImage(f,w.x,w.y);break;case 2:d.drawImage(u.bitmap,u.left,u.top),e.drawImage(f,w.x,w.y),d.clearRect(s.x,s.y,f.width,f.height),r.gifData.globalColorTable.length?d.putImageData(r.gifData.backgroundImage,w.x,w.y):d.putImageData(r.gifData.frames[c].image,w.x+u.left,w.y+u.top);break;case 3:{const t=d.getImageData(s.x,s.y,f.width,f.height);d.drawImage(u.bitmap,u.left,u.top),e.drawImage(f,w.x,w.y),d.clearRect(s.x,s.y,f.width,f.height),d.putImageData(t,s.x,s.y)}}if(o.gifTime+=i.value,o.gifTime>u.delayTime){if(o.gifTime-=u.delayTime,++p>=r.gifData.frames.length){if(--o.gifLoopCount<=g)return;p=c,d.clearRect(s.x,s.y,f.width,f.height)}o.gifFrame=p}e.scale(r.gifData.width/a,r.gifData.height/a)}}async function w(t){if("gif"===t.type){t.loading=!0;try{t.gifData=await async function(t,e,a){a||(a=!1);const o=await fetch(t);if(!o.ok&&404===o.status)throw new EvalError("file not found");const i=await o.arrayBuffer(),s={width:0,height:0,totalTime:0,colorRes:0,pixelAspectRatio:0,frames:[],sortFlag:!1,globalColorTable:[],backgroundImage:new ImageData(1,1,{colorSpace:"srgb"}),comments:[],applicationExtensions:[]},n=new r(new Uint8ClampedArray(i));if("GIF89a"!==n.getString(6))throw new Error("not a supported GIF file");s.width=n.nextTwoBytes(),s.height=n.nextTwoBytes();const l=n.nextByte(),h=128==(128&l);s.colorRes=(112&l)>>>4,s.sortFlag=8==(8&l);const c=1<<1+(7&l),g=n.nextByte();s.pixelAspectRatio=n.nextByte(),0!==s.pixelAspectRatio&&(s.pixelAspectRatio=(s.pixelAspectRatio+15)/64),h&&(s.globalColorTable=f(n,c));const p=(()=>{try{return new ImageData(s.width,s.height,{colorSpace:"srgb"})}catch(t){if(t instanceof DOMException&&"IndexSizeError"===t.name)return null;throw t}})();if(null==p)throw new Error("GIF frame size is to large");const{r:w,g:u,b:m}=s.globalColorTable[g];p.data.set(h?[w,u,m,255]:[0,0,0,0]);for(let t=4;t<p.data.length;t*=2)p.data.copyWithin(t,0,t);s.backgroundImage=p;let y=-1,b=!0,x=-1;const B=t=>(t&&(b=!0),y),T=t=>(null!=t&&(x=t),x);try{do{b&&(s.frames.push({left:0,top:0,width:0,height:0,disposalMethod:0,image:new ImageData(1,1,{colorSpace:"srgb"}),plainTextData:null,userInputDelayFlag:!1,delayTime:0,sortFlag:!1,localColorTable:[],reserved:0,GCreserved:0}),y++,x=-1,b=!1)}while(!await d(n,s,a,B,T,e));s.frames.length--;for(const t of s.frames){if(t.userInputDelayFlag&&0===t.delayTime){s.totalTime=1/0;break}s.totalTime+=t.delayTime}return s}catch(t){if(t instanceof EvalError)throw new Error(`error while parsing frame ${y} "${t.message}"`);throw t}}(t.source),t.gifLoopCount=function(t){for(const e of t.applicationExtensions)if(e.identifier+e.authenticationCode==="NETSCAPE2.0")return e.data[1]+(e.data[2]<<8);return NaN}(t.gifData)??g,t.gifLoopCount||(t.gifLoopCount=1/0)}catch{t.error=!0}t.loading=!1}else{const{loadImage:e}=await Promise.resolve().then(a.bind(a,103));await e(t)}}}}]);
@@ -0,0 +1 @@
1
+ /*! tsParticles Image Shape v3.2.0 by Matteo Bruni */
package/623.min.js ADDED
@@ -0,0 +1,2 @@
1
+ /*! For license information please see 623.min.js.LICENSE.txt */
2
+ (this.webpackChunk_tsparticles_shape_image=this.webpackChunk_tsparticles_shape_image||[]).push([[623],{623:(e,i,s)=>{s.d(i,{ImagePreloaderPlugin:()=>r});class o{constructor(){this.src="",this.gif=!1}load(e){e&&(void 0!==e.gif&&(this.gif=e.gif),void 0!==e.height&&(this.height=e.height),void 0!==e.name&&(this.name=e.name),void 0!==e.replaceColor&&(this.replaceColor=e.replaceColor),void 0!==e.src&&(this.src=e.src),void 0!==e.width&&(this.width=e.width))}}class r{constructor(e){this.id="imagePreloader",this._engine=e}async getPlugin(){return await Promise.resolve(),{}}loadOptions(e,i){if(!i?.preload)return;e.preload||(e.preload=[]);const s=e.preload;for(const e of i.preload){const i=s.find((i=>i.name===e.name||i.src===e.src));if(i)i.load(e);else{const i=new o;i.load(e),s.push(i)}}}needsPlugin(){return!0}}}}]);
@@ -0,0 +1 @@
1
+ /*! tsParticles Image Shape v3.2.0 by Matteo Bruni */
@@ -12,32 +12,36 @@ export class ByteStream {
12
12
  return this.data[this.pos++];
13
13
  }
14
14
  nextTwoBytes() {
15
- this.pos += 2;
16
- return this.data[this.pos - 2] + (this.data[this.pos - 1] << 8);
15
+ const increment = 2, previous = 1, shift = 8;
16
+ this.pos += increment;
17
+ return this.data[this.pos - increment] + (this.data[this.pos - previous] << shift);
17
18
  }
18
19
  readSubBlocks() {
19
20
  let blockString = "", size = 0;
21
+ const minCount = 0, emptySize = 0;
20
22
  do {
21
23
  size = this.data[this.pos++];
22
- for (let count = size; --count >= 0; blockString += String.fromCharCode(this.data[this.pos++])) {
24
+ for (let count = size; --count >= minCount; blockString += String.fromCharCode(this.data[this.pos++])) {
23
25
  }
24
- } while (size !== 0);
26
+ } while (size !== emptySize);
25
27
  return blockString;
26
28
  }
27
29
  readSubBlocksBin() {
28
- let size = 0, len = 0;
29
- for (let offset = 0; (size = this.data[this.pos + offset]) !== 0; offset += size + 1) {
30
+ let size = this.data[this.pos], len = 0;
31
+ const emptySize = 0, increment = 1;
32
+ for (let offset = 0; size !== emptySize; offset += size + increment, size = this.data[this.pos + offset]) {
30
33
  len += size;
31
34
  }
32
35
  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++]) {
36
+ size = this.data[this.pos++];
37
+ for (let i = 0; size !== emptySize; size = this.data[this.pos++]) {
38
+ for (let count = size; --count >= emptySize; blockData[i++] = this.data[this.pos++]) {
35
39
  }
36
40
  }
37
41
  return blockData;
38
42
  }
39
43
  skipSubBlocks() {
40
- for (; this.data[this.pos] !== 0; this.pos += this.data[this.pos] + 1) {
44
+ for (const increment = 1, noData = 0; this.data[this.pos] !== noData; this.pos += this.data[this.pos] + increment) {
41
45
  }
42
46
  this.pos++;
43
47
  }
@@ -1,5 +1,9 @@
1
1
  import { InterlaceOffsets, InterlaceSteps } from "./Constants.js";
2
2
  import { ByteStream } from "./ByteStream.js";
3
+ const origin = {
4
+ x: 0,
5
+ y: 0,
6
+ }, defaultFrame = 0, half = 0.5, initialTime = 0, firstIndex = 0, defaultLoopCount = 0;
3
7
  function parseColorTable(byteStream, count) {
4
8
  const colors = [];
5
9
  for (let i = 0; i < count; i++) {
@@ -12,7 +16,7 @@ function parseColorTable(byteStream, count) {
12
16
  }
13
17
  return colors;
14
18
  }
15
- async function parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex) {
19
+ function parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex) {
16
20
  switch (byteStream.nextByte()) {
17
21
  case 249: {
18
22
  const frame = gif.frames[getFrameIndex(false)];
@@ -84,7 +88,10 @@ async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTran
84
88
  }
85
89
  const getColor = (index) => {
86
90
  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 };
91
+ if (index !== getTransparencyIndex(null)) {
92
+ return { r, g, b, a: 255 };
93
+ }
94
+ return { r, g, b, a: avgAlpha ? ~~((r + g + b) / 3) : 0 };
88
95
  };
89
96
  const image = (() => {
90
97
  try {
@@ -110,7 +117,8 @@ async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTran
110
117
  if (interlacedFlag) {
111
118
  for (let code = 0, size = minCodeSize + 1, pos = 0, dic = [[0]], pass = 0; pass < 4; pass++) {
112
119
  if (InterlaceOffsets[pass] < frame.height) {
113
- for (let pixelPos = 0, lineIndex = 0;;) {
120
+ let pixelPos = 0, lineIndex = 0, exit = false;
121
+ while (!exit) {
114
122
  const last = code;
115
123
  code = readBits(pos, size);
116
124
  pos += size + 1;
@@ -128,8 +136,8 @@ async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTran
128
136
  else if (last !== clearCode) {
129
137
  dic.push(dic[last].concat(dic[code][0]));
130
138
  }
131
- for (let i = 0; i < dic[code].length; i++) {
132
- const { r, g, b, a } = getColor(dic[code][i]);
139
+ for (const item of dic[code]) {
140
+ const { r, g, b, a } = getColor(item);
133
141
  image.data.set([r, g, b, a], InterlaceOffsets[pass] * frame.width +
134
142
  InterlaceSteps[pass] * lineIndex +
135
143
  (pixelPos % (frame.width * 4)));
@@ -142,7 +150,7 @@ async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTran
142
150
  if (pixelPos === frame.width * 4 * (lineIndex + 1)) {
143
151
  lineIndex++;
144
152
  if (InterlaceOffsets[pass] + InterlaceSteps[pass] * lineIndex >= frame.height) {
145
- break;
153
+ exit = true;
146
154
  }
147
155
  }
148
156
  }
@@ -153,7 +161,9 @@ async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTran
153
161
  frame.bitmap = await createImageBitmap(image);
154
162
  }
155
163
  else {
156
- for (let code = 0, size = minCodeSize + 1, pos = 0, dic = [[0]], pixelPos = -4;;) {
164
+ let code = 0, size = minCodeSize + 1, pos = 0, pixelPos = -4, exit = false;
165
+ const dic = [[0]];
166
+ while (!exit) {
157
167
  const last = code;
158
168
  code = readBits(pos, size);
159
169
  pos += size;
@@ -166,6 +176,7 @@ async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTran
166
176
  }
167
177
  else {
168
178
  if (code === clearCode + 1) {
179
+ exit = true;
169
180
  break;
170
181
  }
171
182
  if (code >= dic.length) {
@@ -174,8 +185,8 @@ async function parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTran
174
185
  else if (last !== clearCode) {
175
186
  dic.push(dic[last].concat(dic[code][0]));
176
187
  }
177
- for (let i = 0; i < dic[code].length; i++) {
178
- const { r, g, b, a } = getColor(dic[code][i]);
188
+ for (const item of dic[code]) {
189
+ const { r, g, b, a } = getColor(item);
179
190
  image.data.set([r, g, b, a], (pixelPos += 4));
180
191
  }
181
192
  if (dic.length >= 1 << size && size < 0xc) {
@@ -196,7 +207,7 @@ async function parseBlock(byteStream, gif, avgAlpha, getFrameIndex, getTranspare
196
207
  await parseImageBlock(byteStream, gif, avgAlpha, getFrameIndex, getTransparencyIndex, progressCallback);
197
208
  break;
198
209
  case 33:
199
- await parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex);
210
+ parseExtensionBlock(byteStream, gif, getFrameIndex, getTransparencyIndex);
200
211
  break;
201
212
  default:
202
213
  throw new EvalError("undefined block found");
@@ -322,3 +333,95 @@ export async function decodeGIF(gifURL, progressCallback, avgAlpha) {
322
333
  throw error;
323
334
  }
324
335
  }
336
+ export function drawGif(data) {
337
+ const { context, radius, particle, delta } = data, image = particle.image;
338
+ if (!image?.gifData || !image.gif) {
339
+ return;
340
+ }
341
+ const offscreenCanvas = new OffscreenCanvas(image.gifData.width, image.gifData.height), offscreenContext = offscreenCanvas.getContext("2d");
342
+ if (!offscreenContext) {
343
+ throw new Error("could not create offscreen canvas context");
344
+ }
345
+ offscreenContext.imageSmoothingQuality = "low";
346
+ offscreenContext.imageSmoothingEnabled = false;
347
+ offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
348
+ if (particle.gifLoopCount === undefined) {
349
+ particle.gifLoopCount = image.gifLoopCount ?? defaultLoopCount;
350
+ }
351
+ let frameIndex = particle.gifFrame ?? defaultFrame;
352
+ const pos = { x: -image.gifData.width * half, y: -image.gifData.height * half }, frame = image.gifData.frames[frameIndex];
353
+ if (particle.gifTime === undefined) {
354
+ particle.gifTime = initialTime;
355
+ }
356
+ if (!frame.bitmap) {
357
+ return;
358
+ }
359
+ context.scale(radius / image.gifData.width, radius / image.gifData.height);
360
+ switch (frame.disposalMethod) {
361
+ case 4:
362
+ case 5:
363
+ case 6:
364
+ case 7:
365
+ case 0:
366
+ offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
367
+ context.drawImage(offscreenCanvas, pos.x, pos.y);
368
+ offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
369
+ break;
370
+ case 1:
371
+ offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
372
+ context.drawImage(offscreenCanvas, pos.x, pos.y);
373
+ break;
374
+ case 2:
375
+ offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
376
+ context.drawImage(offscreenCanvas, pos.x, pos.y);
377
+ offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
378
+ if (!image.gifData.globalColorTable.length) {
379
+ offscreenContext.putImageData(image.gifData.frames[firstIndex].image, pos.x + frame.left, pos.y + frame.top);
380
+ }
381
+ else {
382
+ offscreenContext.putImageData(image.gifData.backgroundImage, pos.x, pos.y);
383
+ }
384
+ break;
385
+ case 3:
386
+ {
387
+ const previousImageData = offscreenContext.getImageData(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
388
+ offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
389
+ context.drawImage(offscreenCanvas, pos.x, pos.y);
390
+ offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
391
+ offscreenContext.putImageData(previousImageData, origin.x, origin.y);
392
+ }
393
+ break;
394
+ }
395
+ particle.gifTime += delta.value;
396
+ if (particle.gifTime > frame.delayTime) {
397
+ particle.gifTime -= frame.delayTime;
398
+ if (++frameIndex >= image.gifData.frames.length) {
399
+ if (--particle.gifLoopCount <= defaultLoopCount) {
400
+ return;
401
+ }
402
+ frameIndex = firstIndex;
403
+ offscreenContext.clearRect(origin.x, origin.y, offscreenCanvas.width, offscreenCanvas.height);
404
+ }
405
+ particle.gifFrame = frameIndex;
406
+ }
407
+ context.scale(image.gifData.width / radius, image.gifData.height / radius);
408
+ }
409
+ export async function loadGifImage(image) {
410
+ if (image.type !== "gif") {
411
+ const { loadImage } = await import("../Utils.js");
412
+ await loadImage(image);
413
+ return;
414
+ }
415
+ image.loading = true;
416
+ try {
417
+ image.gifData = await decodeGIF(image.source);
418
+ image.gifLoopCount = getGIFLoopAmount(image.gifData) ?? defaultLoopCount;
419
+ if (!image.gifLoopCount) {
420
+ image.gifLoopCount = Infinity;
421
+ }
422
+ }
423
+ catch {
424
+ image.error = true;
425
+ }
426
+ image.loading = false;
427
+ }
@@ -1,5 +1,5 @@
1
1
  import { errorPrefix } from "@tsparticles/engine";
2
- import { replaceImageColor } from "./Utils.js";
2
+ const double = 2, defaultAlpha = 1, sides = 12, defaultRatio = 1;
3
3
  export class ImageDrawer {
4
4
  constructor(engine) {
5
5
  this.loadImageShape = async (imageShape) => {
@@ -21,92 +21,27 @@ export class ImageDrawer {
21
21
  }
22
22
  this._engine.images.push(image);
23
23
  }
24
- draw(data) {
25
- const { context, radius, particle, opacity, delta } = data, image = particle.image, element = image?.element;
24
+ async draw(data) {
25
+ const { context, radius, particle, opacity } = data, image = particle.image, element = image?.element;
26
26
  if (!image) {
27
27
  return;
28
28
  }
29
29
  context.globalAlpha = opacity;
30
30
  if (image.gif && image.gifData) {
31
- const offscreenCanvas = new OffscreenCanvas(image.gifData.width, image.gifData.height), offscreenContext = offscreenCanvas.getContext("2d");
32
- if (!offscreenContext) {
33
- throw new Error("could not create offscreen canvas context");
34
- }
35
- offscreenContext.imageSmoothingQuality = "low";
36
- offscreenContext.imageSmoothingEnabled = false;
37
- offscreenContext.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
38
- if (particle.gifLoopCount === undefined) {
39
- particle.gifLoopCount = image.gifLoopCount ?? 0;
40
- }
41
- let frameIndex = particle.gifFrame ?? 0;
42
- const pos = { x: -image.gifData.width * 0.5, y: -image.gifData.height * 0.5 }, frame = image.gifData.frames[frameIndex];
43
- if (particle.gifTime === undefined) {
44
- particle.gifTime = 0;
45
- }
46
- if (!frame.bitmap) {
47
- return;
48
- }
49
- context.scale(radius / image.gifData.width, radius / image.gifData.height);
50
- switch (frame.disposalMethod) {
51
- case 4:
52
- case 5:
53
- case 6:
54
- case 7:
55
- case 0:
56
- offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
57
- context.drawImage(offscreenCanvas, pos.x, pos.y);
58
- offscreenContext.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
59
- break;
60
- case 1:
61
- offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
62
- context.drawImage(offscreenCanvas, pos.x, pos.y);
63
- break;
64
- case 2:
65
- offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
66
- context.drawImage(offscreenCanvas, pos.x, pos.y);
67
- offscreenContext.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
68
- if (image.gifData.globalColorTable.length === 0) {
69
- offscreenContext.putImageData(image.gifData.frames[0].image, pos.x + frame.left, pos.y + frame.top);
70
- }
71
- else {
72
- offscreenContext.putImageData(image.gifData.backgroundImage, pos.x, pos.y);
73
- }
74
- break;
75
- case 3:
76
- {
77
- const previousImageData = offscreenContext.getImageData(0, 0, offscreenCanvas.width, offscreenCanvas.height);
78
- offscreenContext.drawImage(frame.bitmap, frame.left, frame.top);
79
- context.drawImage(offscreenCanvas, pos.x, pos.y);
80
- offscreenContext.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
81
- offscreenContext.putImageData(previousImageData, 0, 0);
82
- }
83
- break;
84
- }
85
- particle.gifTime += delta.value;
86
- if (particle.gifTime > frame.delayTime) {
87
- particle.gifTime -= frame.delayTime;
88
- if (++frameIndex >= image.gifData.frames.length) {
89
- if (--particle.gifLoopCount <= 0) {
90
- return;
91
- }
92
- frameIndex = 0;
93
- offscreenContext.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
94
- }
95
- particle.gifFrame = frameIndex;
96
- }
97
- context.scale(image.gifData.width / radius, image.gifData.height / radius);
31
+ const { drawGif } = await import("./GifUtils/Utils.js");
32
+ drawGif(data);
98
33
  }
99
34
  else if (element) {
100
35
  const ratio = image.ratio, pos = {
101
36
  x: -radius,
102
37
  y: -radius,
103
- }, diameter = radius * 2;
38
+ }, diameter = radius * double;
104
39
  context.drawImage(element, pos.x, pos.y, diameter, diameter / ratio);
105
40
  }
106
- context.globalAlpha = 1;
41
+ context.globalAlpha = defaultAlpha;
107
42
  }
108
43
  getSidesCount() {
109
- return 12;
44
+ return sides;
110
45
  }
111
46
  async init(container) {
112
47
  const options = container.actualOptions;
@@ -117,7 +52,7 @@ export class ImageDrawer {
117
52
  await this._engine.loadImage(imageData);
118
53
  }
119
54
  }
120
- loadShape(particle) {
55
+ async loadShape(particle) {
121
56
  if (particle.shape !== "image" && particle.shape !== "images") {
122
57
  return;
123
58
  }
@@ -130,12 +65,11 @@ export class ImageDrawer {
130
65
  }
131
66
  const image = this._engine.images.find((t) => t.name === imageData.name || t.source === imageData.src);
132
67
  if (!image) {
133
- this.loadImageShape(imageData).then(() => {
134
- this.loadShape(particle);
135
- });
68
+ await this.loadImageShape(imageData);
69
+ await this.loadShape(particle);
136
70
  }
137
71
  }
138
- particleInit(container, particle) {
72
+ async particleInit(container, particle) {
139
73
  if (particle.shape !== "image" && particle.shape !== "images") {
140
74
  return;
141
75
  }
@@ -153,40 +87,41 @@ export class ImageDrawer {
153
87
  const replaceColor = imageData.replaceColor ?? image.replaceColor;
154
88
  if (image.loading) {
155
89
  setTimeout(() => {
156
- this.particleInit(container, particle);
90
+ void this.particleInit(container, particle);
157
91
  });
158
92
  return;
159
93
  }
160
- (async () => {
161
- let imageRes;
162
- if (image.svgData && color) {
163
- imageRes = await replaceImageColor(image, imageData, color, particle);
164
- }
165
- else {
166
- imageRes = {
167
- color,
168
- data: image,
169
- element: image.element,
170
- gif: image.gif,
171
- gifData: image.gifData,
172
- gifLoopCount: image.gifLoopCount,
173
- loaded: true,
174
- ratio: imageData.width && imageData.height ? imageData.width / imageData.height : image.ratio ?? 1,
175
- replaceColor: replaceColor,
176
- source: imageData.src,
177
- };
178
- }
179
- if (!imageRes.ratio) {
180
- imageRes.ratio = 1;
181
- }
182
- const fill = imageData.fill ?? particle.shapeFill, close = imageData.close ?? particle.shapeClose, imageShape = {
183
- image: imageRes,
184
- fill,
185
- close,
94
+ let imageRes;
95
+ if (image.svgData && color) {
96
+ const { replaceImageColor } = await import("./Utils.js");
97
+ imageRes = await replaceImageColor(image, imageData, color, particle);
98
+ }
99
+ else {
100
+ imageRes = {
101
+ color,
102
+ data: image,
103
+ element: image.element,
104
+ gif: image.gif,
105
+ gifData: image.gifData,
106
+ gifLoopCount: image.gifLoopCount,
107
+ loaded: true,
108
+ ratio: imageData.width && imageData.height
109
+ ? imageData.width / imageData.height
110
+ : image.ratio ?? defaultRatio,
111
+ replaceColor: replaceColor,
112
+ source: imageData.src,
186
113
  };
187
- particle.image = imageShape.image;
188
- particle.shapeFill = imageShape.fill;
189
- particle.shapeClose = imageShape.close;
190
- })();
114
+ }
115
+ if (!imageRes.ratio) {
116
+ imageRes.ratio = 1;
117
+ }
118
+ const fill = imageData.fill ?? particle.shapeFill, close = imageData.close ?? particle.shapeClose, imageShape = {
119
+ image: imageRes,
120
+ fill,
121
+ close,
122
+ };
123
+ particle.image = imageShape.image;
124
+ particle.shapeFill = imageShape.fill;
125
+ particle.shapeClose = imageShape.close;
191
126
  }
192
127
  }
@@ -4,11 +4,12 @@ export class ImagePreloaderPlugin {
4
4
  this.id = "imagePreloader";
5
5
  this._engine = engine;
6
6
  }
7
- getPlugin() {
7
+ async getPlugin() {
8
+ await Promise.resolve();
8
9
  return {};
9
10
  }
10
11
  loadOptions(options, source) {
11
- if (!source || !source.preload) {
12
+ if (!source?.preload) {
12
13
  return;
13
14
  }
14
15
  if (!options.preload) {
package/browser/Utils.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { errorPrefix, getLogger, getStyleFromHsl } from "@tsparticles/engine";
2
- import { decodeGIF, getGIFLoopAmount } from "./GifUtils/Utils.js";
2
+ const stringStart = 0, defaultOpacity = 1;
3
3
  const currentColorRegex = /(#(?:[0-9a-f]{2}){2,4}|(#[0-9a-f]{3})|(rgb|hsl)a?\((-?\d+%?[,\s]+){2,3}\s*[\d.]+%?\))|currentcolor/gi;
4
4
  function replaceColorSvg(imageShape, color, opacity) {
5
5
  const { svgData } = imageShape;
@@ -11,7 +11,7 @@ function replaceColorSvg(imageShape, color, opacity) {
11
11
  return svgData.replace(currentColorRegex, () => colorStyle);
12
12
  }
13
13
  const preFillIndex = svgData.indexOf(">");
14
- return `${svgData.substring(0, preFillIndex)} fill="${colorStyle}"${svgData.substring(preFillIndex)}`;
14
+ return `${svgData.substring(stringStart, preFillIndex)} fill="${colorStyle}"${svgData.substring(preFillIndex)}`;
15
15
  }
16
16
  export async function loadImage(image) {
17
17
  return new Promise((resolve) => {
@@ -32,24 +32,6 @@ export async function loadImage(image) {
32
32
  img.src = image.source;
33
33
  });
34
34
  }
35
- export async function loadGifImage(image) {
36
- if (image.type !== "gif") {
37
- await loadImage(image);
38
- return;
39
- }
40
- image.loading = true;
41
- try {
42
- image.gifData = await decodeGIF(image.source);
43
- image.gifLoopCount = getGIFLoopAmount(image.gifData) ?? 0;
44
- if (image.gifLoopCount === 0) {
45
- image.gifLoopCount = Infinity;
46
- }
47
- }
48
- catch {
49
- image.error = true;
50
- }
51
- image.loading = false;
52
- }
53
35
  export async function downloadSvgImage(image) {
54
36
  if (image.type !== "svg") {
55
37
  await loadImage(image);
@@ -67,7 +49,7 @@ export async function downloadSvgImage(image) {
67
49
  image.loading = false;
68
50
  }
69
51
  export function replaceImageColor(image, imageData, color, particle) {
70
- const svgColoredData = replaceColorSvg(image, color, particle.opacity?.value ?? 1), imageRes = {
52
+ const svgColoredData = replaceColorSvg(image, color, particle.opacity?.value ?? defaultOpacity), imageRes = {
71
53
  color,
72
54
  gif: imageData.gif,
73
55
  data: {
@@ -87,7 +69,7 @@ export function replaceImageColor(image, imageData, color, particle) {
87
69
  resolve(imageRes);
88
70
  domUrl.revokeObjectURL(url);
89
71
  });
90
- img.addEventListener("error", async () => {
72
+ const errorHandler = async () => {
91
73
  domUrl.revokeObjectURL(url);
92
74
  const img2 = {
93
75
  ...image,
@@ -98,7 +80,8 @@ export function replaceImageColor(image, imageData, color, particle) {
98
80
  imageRes.loaded = true;
99
81
  imageRes.element = img2.element;
100
82
  resolve(imageRes);
101
- });
83
+ };
84
+ img.addEventListener("error", () => void errorHandler());
102
85
  img.src = url;
103
86
  });
104
87
  }
package/browser/index.js CHANGED
@@ -1,7 +1,6 @@
1
- import { downloadSvgImage, loadGifImage, loadImage } from "./Utils.js";
2
- import { ImageDrawer } from "./ImageDrawer.js";
3
- import { ImagePreloaderPlugin } from "./ImagePreloader.js";
1
+ import { downloadSvgImage, loadImage } from "./Utils.js";
4
2
  import { errorPrefix } from "@tsparticles/engine";
3
+ const extLength = 3;
5
4
  function addLoadImageToEngine(engine) {
6
5
  if (engine.loadImage) {
7
6
  return;
@@ -21,14 +20,21 @@ function addLoadImageToEngine(engine) {
21
20
  gif: data.gif ?? false,
22
21
  name: data.name ?? data.src,
23
22
  source: data.src,
24
- type: data.src.substring(data.src.length - 3),
23
+ type: data.src.substring(data.src.length - extLength),
25
24
  error: false,
26
25
  loading: true,
27
26
  replaceColor: data.replaceColor,
28
27
  ratio: data.width && data.height ? data.width / data.height : undefined,
29
28
  };
30
29
  engine.images.push(image);
31
- const imageFunc = data.gif ? loadGifImage : data.replaceColor ? downloadSvgImage : loadImage;
30
+ let imageFunc;
31
+ if (data.gif) {
32
+ const { loadGifImage } = await import("./GifUtils/Utils.js");
33
+ imageFunc = loadGifImage;
34
+ }
35
+ else {
36
+ imageFunc = data.replaceColor ? downloadSvgImage : loadImage;
37
+ }
32
38
  await imageFunc(image);
33
39
  }
34
40
  catch {
@@ -38,6 +44,7 @@ function addLoadImageToEngine(engine) {
38
44
  }
39
45
  export async function loadImageShape(engine, refresh = true) {
40
46
  addLoadImageToEngine(engine);
47
+ const { ImagePreloaderPlugin } = await import("./ImagePreloader.js"), { ImageDrawer } = await import("./ImageDrawer.js");
41
48
  const preloader = new ImagePreloaderPlugin(engine);
42
49
  await engine.addPlugin(preloader, refresh);
43
50
  await engine.addShape(["image", "images"], new ImageDrawer(engine), refresh);
@@ -15,32 +15,36 @@ class ByteStream {
15
15
  return this.data[this.pos++];
16
16
  }
17
17
  nextTwoBytes() {
18
- this.pos += 2;
19
- return this.data[this.pos - 2] + (this.data[this.pos - 1] << 8);
18
+ const increment = 2, previous = 1, shift = 8;
19
+ this.pos += increment;
20
+ return this.data[this.pos - increment] + (this.data[this.pos - previous] << shift);
20
21
  }
21
22
  readSubBlocks() {
22
23
  let blockString = "", size = 0;
24
+ const minCount = 0, emptySize = 0;
23
25
  do {
24
26
  size = this.data[this.pos++];
25
- for (let count = size; --count >= 0; blockString += String.fromCharCode(this.data[this.pos++])) {
27
+ for (let count = size; --count >= minCount; blockString += String.fromCharCode(this.data[this.pos++])) {
26
28
  }
27
- } while (size !== 0);
29
+ } while (size !== emptySize);
28
30
  return blockString;
29
31
  }
30
32
  readSubBlocksBin() {
31
- let size = 0, len = 0;
32
- for (let offset = 0; (size = this.data[this.pos + offset]) !== 0; offset += size + 1) {
33
+ let size = this.data[this.pos], len = 0;
34
+ const emptySize = 0, increment = 1;
35
+ for (let offset = 0; size !== emptySize; offset += size + increment, size = this.data[this.pos + offset]) {
33
36
  len += size;
34
37
  }
35
38
  const blockData = new Uint8Array(len);
36
- for (let i = 0; (size = this.data[this.pos++]) !== 0;) {
37
- for (let count = size; --count >= 0; blockData[i++] = this.data[this.pos++]) {
39
+ size = this.data[this.pos++];
40
+ for (let i = 0; size !== emptySize; size = this.data[this.pos++]) {
41
+ for (let count = size; --count >= emptySize; blockData[i++] = this.data[this.pos++]) {
38
42
  }
39
43
  }
40
44
  return blockData;
41
45
  }
42
46
  skipSubBlocks() {
43
- for (; this.data[this.pos] !== 0; this.pos += this.data[this.pos] + 1) {
47
+ for (const increment = 1, noData = 0; this.data[this.pos] !== noData; this.pos += this.data[this.pos] + increment) {
44
48
  }
45
49
  this.pos++;
46
50
  }