@paper-design/shaders 0.0.0-canary.0d8316e

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 (127) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.md +7 -0
  4. package/dist/empty-pixel.d.ts +1 -0
  5. package/dist/empty-pixel.js +10 -0
  6. package/dist/empty-pixel.js.map +7 -0
  7. package/dist/get-shader-color-from-string.d.ts +3 -0
  8. package/dist/get-shader-color-from-string.js +101 -0
  9. package/dist/get-shader-color-from-string.js.map +7 -0
  10. package/dist/get-shader-noise-texture.d.ts +1 -0
  11. package/dist/get-shader-noise-texture.js +18 -0
  12. package/dist/get-shader-noise-texture.js.map +7 -0
  13. package/dist/html-in-canvas.d.ts +35 -0
  14. package/dist/html-in-canvas.js +79 -0
  15. package/dist/html-in-canvas.js.map +7 -0
  16. package/dist/index.d.ts +69 -0
  17. package/dist/index.js +196 -0
  18. package/dist/index.js.map +7 -0
  19. package/dist/shader-color-spaces.d.ts +6 -0
  20. package/dist/shader-color-spaces.js +96 -0
  21. package/dist/shader-color-spaces.js.map +7 -0
  22. package/dist/shader-mount.d.ts +151 -0
  23. package/dist/shader-mount.js +605 -0
  24. package/dist/shader-mount.js.map +7 -0
  25. package/dist/shader-sizing.d.ts +30 -0
  26. package/dist/shader-sizing.js +38 -0
  27. package/dist/shader-sizing.js.map +7 -0
  28. package/dist/shader-utils.d.ts +10 -0
  29. package/dist/shader-utils.js +131 -0
  30. package/dist/shader-utils.js.map +7 -0
  31. package/dist/shaders/color-panels.d.ts +70 -0
  32. package/dist/shaders/color-panels.js +224 -0
  33. package/dist/shaders/color-panels.js.map +7 -0
  34. package/dist/shaders/dithering.d.ts +62 -0
  35. package/dist/shaders/dithering.js +271 -0
  36. package/dist/shaders/dithering.js.map +7 -0
  37. package/dist/shaders/dot-grid.d.ts +65 -0
  38. package/dist/shaders/dot-grid.js +110 -0
  39. package/dist/shaders/dot-grid.js.map +7 -0
  40. package/dist/shaders/dot-orbit.d.ts +57 -0
  41. package/dist/shaders/dot-orbit.js +123 -0
  42. package/dist/shaders/dot-orbit.js.map +7 -0
  43. package/dist/shaders/fluted-glass.d.ts +115 -0
  44. package/dist/shaders/fluted-glass.js +361 -0
  45. package/dist/shaders/fluted-glass.js.map +7 -0
  46. package/dist/shaders/gem-smoke.d.ts +96 -0
  47. package/dist/shaders/gem-smoke.js +573 -0
  48. package/dist/shaders/gem-smoke.js.map +7 -0
  49. package/dist/shaders/god-rays.d.ts +67 -0
  50. package/dist/shaders/god-rays.js +133 -0
  51. package/dist/shaders/god-rays.js.map +7 -0
  52. package/dist/shaders/grain-gradient.d.ts +80 -0
  53. package/dist/shaders/grain-gradient.js +301 -0
  54. package/dist/shaders/grain-gradient.js.map +7 -0
  55. package/dist/shaders/halftone-cmyk.d.ts +107 -0
  56. package/dist/shaders/halftone-cmyk.js +319 -0
  57. package/dist/shaders/halftone-cmyk.js.map +7 -0
  58. package/dist/shaders/halftone-dots.d.ts +83 -0
  59. package/dist/shaders/halftone-dots.js +345 -0
  60. package/dist/shaders/halftone-dots.js.map +7 -0
  61. package/dist/shaders/heatmap.d.ts +66 -0
  62. package/dist/shaders/heatmap.js +384 -0
  63. package/dist/shaders/heatmap.js.map +7 -0
  64. package/dist/shaders/image-dithering.d.ts +66 -0
  65. package/dist/shaders/image-dithering.js +209 -0
  66. package/dist/shaders/image-dithering.js.map +7 -0
  67. package/dist/shaders/lens-distortion.d.ts +100 -0
  68. package/dist/shaders/lens-distortion.js +290 -0
  69. package/dist/shaders/lens-distortion.js.map +7 -0
  70. package/dist/shaders/liquid-metal.d.ts +89 -0
  71. package/dist/shaders/liquid-metal.js +633 -0
  72. package/dist/shaders/liquid-metal.js.map +7 -0
  73. package/dist/shaders/mesh-gradient.d.ts +52 -0
  74. package/dist/shaders/mesh-gradient.js +131 -0
  75. package/dist/shaders/mesh-gradient.js.map +7 -0
  76. package/dist/shaders/metaballs.d.ts +51 -0
  77. package/dist/shaders/metaballs.js +111 -0
  78. package/dist/shaders/metaballs.js.map +7 -0
  79. package/dist/shaders/neuro-noise.d.ts +47 -0
  80. package/dist/shaders/neuro-noise.js +77 -0
  81. package/dist/shaders/neuro-noise.js.map +7 -0
  82. package/dist/shaders/paper-texture.d.ts +113 -0
  83. package/dist/shaders/paper-texture.js +541 -0
  84. package/dist/shaders/paper-texture.js.map +7 -0
  85. package/dist/shaders/perlin-noise.d.ts +52 -0
  86. package/dist/shaders/perlin-noise.js +167 -0
  87. package/dist/shaders/perlin-noise.js.map +7 -0
  88. package/dist/shaders/pulsing-border.d.ts +98 -0
  89. package/dist/shaders/pulsing-border.js +239 -0
  90. package/dist/shaders/pulsing-border.js.map +7 -0
  91. package/dist/shaders/simplex-noise.d.ts +45 -0
  92. package/dist/shaders/simplex-noise.js +92 -0
  93. package/dist/shaders/simplex-noise.js.map +7 -0
  94. package/dist/shaders/smoke-ring.d.ts +61 -0
  95. package/dist/shaders/smoke-ring.js +133 -0
  96. package/dist/shaders/smoke-ring.js.map +7 -0
  97. package/dist/shaders/spiral.d.ts +61 -0
  98. package/dist/shaders/spiral.js +83 -0
  99. package/dist/shaders/spiral.js.map +7 -0
  100. package/dist/shaders/static-mesh-gradient.d.ts +63 -0
  101. package/dist/shaders/static-mesh-gradient.js +131 -0
  102. package/dist/shaders/static-mesh-gradient.js.map +7 -0
  103. package/dist/shaders/static-radial-gradient.d.ts +72 -0
  104. package/dist/shaders/static-radial-gradient.js +188 -0
  105. package/dist/shaders/static-radial-gradient.js.map +7 -0
  106. package/dist/shaders/swirl.d.ts +63 -0
  107. package/dist/shaders/swirl.js +104 -0
  108. package/dist/shaders/swirl.js.map +7 -0
  109. package/dist/shaders/voronoi.d.ts +65 -0
  110. package/dist/shaders/voronoi.js +139 -0
  111. package/dist/shaders/voronoi.js.map +7 -0
  112. package/dist/shaders/warp.d.ts +70 -0
  113. package/dist/shaders/warp.js +130 -0
  114. package/dist/shaders/warp.js.map +7 -0
  115. package/dist/shaders/water.d.ts +63 -0
  116. package/dist/shaders/water.js +135 -0
  117. package/dist/shaders/water.js.map +7 -0
  118. package/dist/shaders/waves.d.ts +53 -0
  119. package/dist/shaders/waves.js +66 -0
  120. package/dist/shaders/waves.js.map +7 -0
  121. package/dist/types.d.ts +3 -0
  122. package/dist/types.js +6 -0
  123. package/dist/types.js.map +7 -0
  124. package/dist/vertex-shader.d.ts +2 -0
  125. package/dist/vertex-shader.js +163 -0
  126. package/dist/vertex-shader.js.map +7 -0
  127. package/package.json +29 -0
@@ -0,0 +1,96 @@
1
+ import type { vec4 } from '../types.js';
2
+ import type { ShaderMotionParams } from '../shader-mount.js';
3
+ import type { ShaderSizingParams, ShaderSizingUniforms } from '../shader-sizing.js';
4
+ export declare const gemSmokeMeta: {
5
+ readonly maxColorCount: 6;
6
+ };
7
+ /**
8
+ * Animated color fields placed over uploaded logo shape; gives the illusion of smoky noise
9
+ * behind the glassy shape.
10
+ *
11
+ * Fragment shader uniforms:
12
+ * - u_time (float): Animation time
13
+ * - u_image (sampler2D): Pre-processed source image texture (R = edge gradient, G = alpha)
14
+ * - u_colors (vec4[]): Up to 6 smoke colors in RGBA
15
+ * - u_colorsCount (float): Number of active colors
16
+ * - u_colorBack (vec4): Background color in RGBA
17
+ * - u_innerDistortion (float): Power of smoke distortion inside the input shape, needs innerGlow > 0 (0 to 1)
18
+ * - u_outerDistortion (float): Power of smoke distortion outside the input shape, needs outerGlow > 0 (0 to 1)
19
+ * - u_outerGlow (float): Visibility of smoke shape outside the input shape (0 to 1)
20
+ * - u_innerGlow (float): Visibility of smoke shape inside the input shape (0 to 1)
21
+ * - u_colorInner (vec4): Additional color inside the input shape, mixing with smoke (RGBA)
22
+ * - u_offset (float): Vertical offset of smoke inside the shape, needs innerGlow > 0 (-1 to 1)
23
+ * - u_angle (float): Smoke direction in degrees (0 to 360)
24
+ * - u_size (float): Size of smoke shape relative to the image box (0 to 1)
25
+ * - u_shape (float): Predefined shape mask, needs no image (0 = none, 1 = circle, 2 = daisy, 3 = diamond, 4 = metaballs)
26
+ * - u_isImage (bool): Whether an image mask is provided; when true, u_shape is ignored
27
+ *
28
+ * Vertex shader outputs (used in fragment shader):
29
+ * - v_imageUV (vec2): UV coordinates for sampling the source image, with fit, scale, rotation, and offset applied
30
+ * - v_objectUV (vec2): Normalized UV coordinates with scale, rotation, and offset applied
31
+ * - v_responsiveUV (vec2): Responsive UV coordinates that adapt to canvas aspect ratio (used by the 'none' shape)
32
+ * - v_responsiveBoxGivenSize (vec2): Given size of the responsive bounding box (used by the 'none' shape)
33
+ *
34
+ * Vertex shader uniforms:
35
+ * - u_resolution (vec2): Canvas resolution in pixels
36
+ * - u_pixelRatio (float): Device pixel ratio
37
+ * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
38
+ * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
39
+ * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
40
+ * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
41
+ * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
42
+ * - u_scale (float): Overall zoom level of the graphics (0.1 to 4)
43
+ * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
44
+ * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
45
+ * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
46
+ * - u_imageAspectRatio (float): Aspect ratio of the source image
47
+ *
48
+ */
49
+ export declare const gemSmokeFragmentShader: string;
50
+ export declare const POISSON_CONFIG_OPTIMIZED: {
51
+ measurePerformance: boolean;
52
+ workingSize: number;
53
+ iterations: number;
54
+ };
55
+ export declare function toProcessedGemSmoke(file: File | string): Promise<{
56
+ imageData: ImageData;
57
+ pngBlob: Blob;
58
+ }>;
59
+ export interface GemSmokeUniforms extends ShaderSizingUniforms {
60
+ u_colorBack: [number, number, number, number];
61
+ u_colors: vec4[];
62
+ u_colorsCount: number;
63
+ u_image: HTMLImageElement | string | undefined;
64
+ u_innerDistortion: number;
65
+ u_outerDistortion: number;
66
+ u_outerGlow: number;
67
+ u_innerGlow: number;
68
+ u_colorInner: [number, number, number, number];
69
+ u_offset: number;
70
+ u_angle: number;
71
+ u_size: number;
72
+ u_shape: (typeof GemSmokeShapes)[GemSmokeShape];
73
+ u_isImage: boolean;
74
+ }
75
+ export interface GemSmokeParams extends ShaderSizingParams, ShaderMotionParams {
76
+ colors?: string[];
77
+ colorBack?: string;
78
+ image?: HTMLImageElement | string | undefined;
79
+ innerDistortion?: number;
80
+ outerDistortion?: number;
81
+ outerGlow?: number;
82
+ innerGlow?: number;
83
+ colorInner?: string;
84
+ offset?: number;
85
+ angle?: number;
86
+ size?: number;
87
+ shape?: GemSmokeShape;
88
+ }
89
+ export declare const GemSmokeShapes: {
90
+ readonly none: 0;
91
+ readonly circle: 1;
92
+ readonly daisy: 2;
93
+ readonly diamond: 3;
94
+ readonly metaballs: 4;
95
+ };
96
+ export type GemSmokeShape = keyof typeof GemSmokeShapes;
@@ -0,0 +1,573 @@
1
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * *
2
+ * Paper Shaders *
3
+ * https://github.com/paper-design/shaders *
4
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * */
5
+
6
+ import { rotation2, declarePI } from "../shader-utils.js";
7
+ const gemSmokeMeta = {
8
+ maxColorCount: 6
9
+ };
10
+ const gemSmokeFragmentShader = `#version 300 es
11
+ precision mediump float;
12
+
13
+ in mediump vec2 v_imageUV;
14
+ in mediump vec2 v_objectUV;
15
+ in mediump vec2 v_responsiveUV;
16
+ in mediump vec2 v_responsiveBoxGivenSize;
17
+
18
+ out vec4 fragColor;
19
+
20
+ uniform sampler2D u_image;
21
+ uniform float u_shape;
22
+ uniform bool u_isImage;
23
+
24
+ uniform float u_time;
25
+ uniform vec4 u_colors[${gemSmokeMeta.maxColorCount}];
26
+ uniform float u_colorsCount;
27
+ uniform vec4 u_colorBack;
28
+ uniform vec4 u_colorInner;
29
+ uniform float u_innerDistortion;
30
+ uniform float u_outerDistortion;
31
+ uniform float u_outerGlow;
32
+ uniform float u_innerGlow;
33
+ uniform float u_offset;
34
+ uniform float u_angle;
35
+ uniform float u_size;
36
+
37
+ ${declarePI}
38
+ ${rotation2}
39
+
40
+ // 9x9 Gaussian blur on R and G channels
41
+ vec2 gaussBlur9x9RG(sampler2D tex, vec2 uv, float radius) {
42
+ vec2 texel = 1.0 / vec2(textureSize(tex, 0));
43
+ vec2 r = max(radius, 0.0) * texel;
44
+ // Pascal's row 8: sum = 256, 2D norm = 65536
45
+ const float k[9] = float[9](1.0, 8.0, 28.0, 56.0, 70.0, 56.0, 28.0, 8.0, 1.0);
46
+ vec2 sum = vec2(0.0);
47
+
48
+ for (int j = -4; j <= 4; ++j) {
49
+ float wy = k[j + 4];
50
+ for (int i = -4; i <= 4; ++i) {
51
+ float w = k[i + 4] * wy;
52
+ vec2 off = vec2(float(i) * r.x, float(j) * r.y);
53
+ sum += w * texture(tex, uv + off).rg;
54
+ }
55
+ }
56
+
57
+ return sum / 65536.0;
58
+ }
59
+
60
+ float sst(float a, float b, float x) {
61
+ return smoothstep(a, b, x);
62
+ }
63
+
64
+ void main() {
65
+ float time = u_time;
66
+
67
+ float roundness = 0.;
68
+ float imgAlpha = 0.;
69
+
70
+ if (u_isImage == true) {
71
+ // Image sampling (UV scaled inward to account for padding)
72
+ vec2 imageUV = v_imageUV;
73
+ imageUV -= .5;
74
+ imageUV *= .95;
75
+ imageUV += .5;
76
+
77
+ // Blurred image: x = roundness, y = alpha
78
+ vec2 blurred = gaussBlur9x9RG(u_image, imageUV, 10.);
79
+ roundness = 1. - blurred.x;
80
+ vec2 texelA = 1.0 / vec2(textureSize(u_image, 0));
81
+ const float k3[3] = float[3](1.0, 2.0, 1.0);
82
+ for (int j = -1; j <= 1; ++j) {
83
+ for (int i = -1; i <= 1; ++i) {
84
+ imgAlpha += k3[i + 1] * k3[j + 1] * texture(u_image, imageUV + vec2(float(i) * texelA.x, float(j) * texelA.y)).g;
85
+ }
86
+ }
87
+ imgAlpha /= 16.0;
88
+ } else {
89
+ vec2 uv = v_objectUV + .5;
90
+ uv.y = 1. - uv.y;
91
+ float edge = 0.;
92
+
93
+ if (u_shape < 1.) {
94
+ // full-fill on canvas
95
+ vec2 borderUV = v_responsiveUV + .5;
96
+ vec2 mask = min(borderUV, 1. - borderUV);
97
+ vec2 pixel_thickness = min(250. / v_responsiveBoxGivenSize, vec2(.5));
98
+ float maskX = smoothstep(0.0, pixel_thickness.x, mask.x);
99
+ float maskY = smoothstep(0.0, pixel_thickness.y, mask.y);
100
+ maskX = pow(maskX, .25);
101
+ maskY = pow(maskY, .25);
102
+ edge = clamp(1. - maskX * maskY, 0., 1.);
103
+ } else if (u_shape < 2.) {
104
+ // circle
105
+ vec2 shapeUV = uv - .5;
106
+ shapeUV *= .67;
107
+ edge = pow(clamp(3. * length(shapeUV), 0., 1.), 18.);
108
+ } else if (u_shape < 3.) {
109
+ // daisy
110
+ vec2 shapeUV = uv - .5;
111
+ shapeUV *= 1.68;
112
+
113
+ float r = length(shapeUV) * 2.;
114
+ float a = atan(shapeUV.y, shapeUV.x) + .2;
115
+ r *= (1. + .05 * sin(3. * a + 2. * time));
116
+ float f = abs(cos(a * 3.));
117
+ edge = smoothstep(f, f + .7, r);
118
+ edge *= edge;
119
+ } else if (u_shape < 4.) {
120
+ // diamond
121
+ vec2 shapeUV = uv - .5;
122
+ shapeUV = rotate(shapeUV, .25 * PI);
123
+ shapeUV *= 1.42;
124
+ shapeUV += .5;
125
+ vec2 mask = min(shapeUV, 1. - shapeUV);
126
+ vec2 pixel_thickness = vec2(.15);
127
+ float maskX = smoothstep(0.0, pixel_thickness.x, mask.x);
128
+ float maskY = smoothstep(0.0, pixel_thickness.y, mask.y);
129
+ maskX = pow(maskX, .25);
130
+ maskY = pow(maskY, .25);
131
+ edge = clamp(1. - maskX * maskY, 0., 1.);
132
+ } else if (u_shape < 5.) {
133
+ // metaballs
134
+ vec2 shapeUV = uv - .5;
135
+ shapeUV *= 1.3;
136
+ edge = 0.;
137
+ for (int i = 0; i < 5; i++) {
138
+ float fi = float(i);
139
+ float speed = 1.5 + 2./3. * sin(fi * 12.345);
140
+ float angle = -fi * 1.5;
141
+ vec2 dir1 = vec2(cos(angle), sin(angle));
142
+ vec2 dir2 = vec2(cos(angle + 1.57), sin(angle + 1.));
143
+ vec2 traj = .4 * (dir1 * sin(time * speed + fi * 1.23) + dir2 * cos(time * (speed * 0.7) + fi * 2.17));
144
+ float d = length(shapeUV + traj);
145
+ edge += pow(1.0 - clamp(d, 0.0, 1.0), 4.0);
146
+ }
147
+ edge = 1. - smoothstep(.65, .9, edge);
148
+ edge = pow(edge, 4.);
149
+ }
150
+
151
+ imgAlpha = 1. - smoothstep(.9 - 2. * fwidth(edge), .9, edge);
152
+ roundness = 1. - edge;
153
+ }
154
+
155
+ // Smoke UV setup
156
+ vec2 smokeUV = v_objectUV;
157
+ smokeUV = rotate(smokeUV, u_angle * PI / 180.);
158
+ smokeUV *= mix(4., 1., u_size);
159
+
160
+ // Two swirl paths: inner (shape-masked) and outer (free), each with independent distortion
161
+ vec2 innerUV = smokeUV;
162
+ vec2 outerUV = smokeUV;
163
+
164
+ // Vertical displacement \u2014 applied independently to inner and outer
165
+ innerUV.y += u_innerDistortion * (1. - sst(0., 1., length(.4 * innerUV)));
166
+ innerUV.y -= .4 * u_innerDistortion;
167
+ innerUV.y += .7 * u_offset * roundness;
168
+
169
+ outerUV.y += u_outerDistortion * (1. - sst(0., 1., length(.4 * outerUV)));
170
+ outerUV.y -= .4 * u_outerDistortion;
171
+
172
+ float innerSwirl = u_innerDistortion * roundness;
173
+ float outerSwirl = u_outerDistortion;
174
+
175
+ for (int i = 1; i < 5; i++) {
176
+ float fi = float(i);
177
+
178
+ float stretchIn = max(length(dFdx(innerUV)), length(dFdy(innerUV)));
179
+ float dampenIn = 1. / (1. + stretchIn * 8.);
180
+ float sIn = innerSwirl * dampenIn;
181
+ innerUV.x += sIn / fi * cos(time + fi * 2.9 * innerUV.y);
182
+ innerUV.y += sIn / fi * cos(time + fi * 1.5 * innerUV.x);
183
+
184
+ float stretchOut = max(length(dFdx(outerUV)), length(dFdy(outerUV)));
185
+ float dampenOut = 1. / (1. + stretchOut * 8.);
186
+ float sOut = outerSwirl * dampenOut;
187
+ outerUV.x += sOut / fi * cos(time + fi * 2.9 * outerUV.y);
188
+ outerUV.y += sOut / fi * cos(time + fi * 1.5 * outerUV.x);
189
+ }
190
+
191
+ // Smoke shapes from swirl fields
192
+ float innerShape = exp(-1.5 * dot(innerUV, innerUV));
193
+ float outerShape = exp(-1.5 * dot(outerUV, outerUV));
194
+
195
+ // Visibility masks
196
+ float outerMask = pow(u_outerGlow, 2.) * (1. - imgAlpha);
197
+ float innerMask = (.01 + .99 * u_innerGlow) * imgAlpha;
198
+
199
+ innerShape *= innerMask;
200
+ outerShape *= outerMask;
201
+
202
+ // Color gradient
203
+ float mixer = (innerShape + outerShape) * u_colorsCount;
204
+ vec4 gradient = u_colors[0];
205
+ gradient.rgb *= gradient.a;
206
+
207
+ float smokeMask = 0.;
208
+ for (int i = 1; i < ${gemSmokeMeta.maxColorCount + 1}; i++) {
209
+ if (i > int(u_colorsCount)) break;
210
+
211
+ float m = sst(0., 1., clamp(mixer - float(i - 1), 0., 1.));
212
+ if (i == 1) smokeMask = m;
213
+
214
+ vec4 c = u_colors[i - 1];
215
+ c.rgb *= c.a;
216
+ gradient = mix(gradient, c, m);
217
+ }
218
+
219
+ // Compositing (premultiplied alpha, front-to-back)
220
+ vec3 color = gradient.rgb * smokeMask;
221
+ float opacity = gradient.a * smokeMask;
222
+
223
+ float innerOpacity = u_colorInner.a * imgAlpha;
224
+ vec3 innerColor = u_colorInner.rgb * innerOpacity;
225
+ color += innerColor * (1.0 - opacity);
226
+ opacity += innerOpacity * (1.0 - opacity);
227
+
228
+ vec3 backColor = u_colorBack.rgb * u_colorBack.a;
229
+ color += backColor * (1.0 - opacity);
230
+ opacity += u_colorBack.a * (1.0 - opacity);
231
+
232
+ fragColor = vec4(color, opacity);
233
+ }
234
+ `;
235
+ const POISSON_CONFIG_OPTIMIZED = {
236
+ measurePerformance: false,
237
+ // Set to true to see performance metrics
238
+ workingSize: 512,
239
+ // Size to solve Poisson at (will upscale to original size)
240
+ iterations: 32
241
+ // SOR converges ~2-20x faster than standard Gauss-Seidel
242
+ };
243
+ function toProcessedGemSmoke(file) {
244
+ const canvas = document.createElement("canvas");
245
+ const ctx = canvas.getContext("2d");
246
+ const isBlob = typeof file === "string" && file.startsWith("blob:");
247
+ return new Promise((resolve, reject) => {
248
+ if (!file || !ctx) {
249
+ reject(new Error("Invalid file or canvas context"));
250
+ return;
251
+ }
252
+ const blobContentTypePromise = isBlob && fetch(file).then((res) => res.headers.get("Content-Type"));
253
+ const img = new Image();
254
+ img.crossOrigin = "anonymous";
255
+ const totalStartTime = performance.now();
256
+ img.onload = async () => {
257
+ let isSVG;
258
+ const blobContentType = await blobContentTypePromise;
259
+ if (blobContentType) {
260
+ isSVG = blobContentType === "image/svg+xml";
261
+ } else if (typeof file === "string") {
262
+ isSVG = file.endsWith(".svg") || file.startsWith("data:image/svg+xml");
263
+ } else {
264
+ isSVG = file.type === "image/svg+xml";
265
+ }
266
+ let originalWidth = img.width || img.naturalWidth;
267
+ let originalHeight = img.height || img.naturalHeight;
268
+ if (isSVG) {
269
+ const svgMaxSize = 4096;
270
+ const aspectRatio = originalWidth / originalHeight;
271
+ if (originalWidth > originalHeight) {
272
+ originalWidth = svgMaxSize;
273
+ originalHeight = svgMaxSize / aspectRatio;
274
+ } else {
275
+ originalHeight = svgMaxSize;
276
+ originalWidth = svgMaxSize * aspectRatio;
277
+ }
278
+ img.width = originalWidth;
279
+ img.height = originalHeight;
280
+ }
281
+ const minDimension = Math.min(originalWidth, originalHeight);
282
+ const targetSize = POISSON_CONFIG_OPTIMIZED.workingSize;
283
+ const scaleFactor = targetSize / minDimension;
284
+ const width = Math.round(originalWidth * scaleFactor);
285
+ const height = Math.round(originalHeight * scaleFactor);
286
+ if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
287
+ console.log(`[Processing Mode]`);
288
+ console.log(` Original: ${originalWidth}\xD7${originalHeight}`);
289
+ console.log(` Working: ${width}\xD7${height} (${(scaleFactor * 100).toFixed(1)}% scale)`);
290
+ if (scaleFactor < 1) {
291
+ console.log(` Speedup: ~${Math.round(1 / (scaleFactor * scaleFactor))}\xD7`);
292
+ }
293
+ }
294
+ canvas.width = originalWidth;
295
+ canvas.height = originalHeight;
296
+ const paddingSize = 0.025;
297
+ const padX = Math.ceil(width * paddingSize);
298
+ const padY = Math.ceil(height * paddingSize);
299
+ const imgW = width - 2 * padX;
300
+ const imgH = height - 2 * padY;
301
+ const shapeCanvas = document.createElement("canvas");
302
+ shapeCanvas.width = width;
303
+ shapeCanvas.height = height;
304
+ const shapeCtx = shapeCanvas.getContext("2d");
305
+ shapeCtx.drawImage(img, padX, padY, imgW, imgH);
306
+ const startMask = performance.now();
307
+ const shapeImageData = shapeCtx.getImageData(0, 0, width, height);
308
+ const data = shapeImageData.data;
309
+ const shapeMask = new Uint8Array(width * height);
310
+ const boundaryMask = new Uint8Array(width * height);
311
+ let shapePixelCount = 0;
312
+ for (let i = 0, idx = 0; i < data.length; i += 4, idx++) {
313
+ const a = data[i + 3];
314
+ const isShape = a === 0 ? 0 : 1;
315
+ shapeMask[idx] = isShape;
316
+ shapePixelCount += isShape;
317
+ }
318
+ const boundaryIndices = [];
319
+ const interiorIndices = [];
320
+ for (let y = 0; y < height; y++) {
321
+ for (let x = 0; x < width; x++) {
322
+ const idx = y * width + x;
323
+ if (!shapeMask[idx]) continue;
324
+ let isBoundary = false;
325
+ if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {
326
+ isBoundary = true;
327
+ } else {
328
+ isBoundary = !shapeMask[idx - 1] || // left
329
+ !shapeMask[idx + 1] || // right
330
+ !shapeMask[idx - width] || // top
331
+ !shapeMask[idx + width] || // bottom
332
+ !shapeMask[idx - width - 1] || // top-left
333
+ !shapeMask[idx - width + 1] || // top-right
334
+ !shapeMask[idx + width - 1] || // bottom-left
335
+ !shapeMask[idx + width + 1];
336
+ }
337
+ if (isBoundary) {
338
+ boundaryMask[idx] = 1;
339
+ boundaryIndices.push(idx);
340
+ } else {
341
+ interiorIndices.push(idx);
342
+ }
343
+ }
344
+ }
345
+ if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
346
+ console.log(`[Mask Building] Time: ${(performance.now() - startMask).toFixed(2)}ms`);
347
+ console.log(
348
+ ` Shape pixels: ${shapePixelCount} / ${width * height} (${(shapePixelCount / (width * height) * 100).toFixed(1)}%)`
349
+ );
350
+ console.log(` Interior pixels: ${interiorIndices.length}`);
351
+ console.log(` Boundary pixels: ${boundaryIndices.length}`);
352
+ }
353
+ const sparseData = buildSparseData(
354
+ shapeMask,
355
+ boundaryMask,
356
+ new Uint32Array(interiorIndices),
357
+ new Uint32Array(boundaryIndices),
358
+ width,
359
+ height
360
+ );
361
+ const startSolve = performance.now();
362
+ const u = solvePoissonSparse(sparseData, shapeMask, boundaryMask, width, height);
363
+ if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
364
+ console.log(`[Poisson Solve] Time: ${(performance.now() - startSolve).toFixed(2)}ms`);
365
+ }
366
+ let maxVal = 0;
367
+ let finalImageData;
368
+ for (let i = 0; i < interiorIndices.length; i++) {
369
+ const idx = interiorIndices[i];
370
+ if (u[idx] > maxVal) maxVal = u[idx];
371
+ }
372
+ const tempCanvas = document.createElement("canvas");
373
+ tempCanvas.width = width;
374
+ tempCanvas.height = height;
375
+ const tempCtx = tempCanvas.getContext("2d");
376
+ const tempImg = tempCtx.createImageData(width, height);
377
+ for (let y = 0; y < height; y++) {
378
+ for (let x = 0; x < width; x++) {
379
+ const idx = y * width + x;
380
+ const px = idx * 4;
381
+ if (!shapeMask[idx]) {
382
+ tempImg.data[px] = 255;
383
+ tempImg.data[px + 1] = 255;
384
+ tempImg.data[px + 2] = 255;
385
+ tempImg.data[px + 3] = 0;
386
+ } else {
387
+ const poissonRatio = u[idx] / maxVal;
388
+ let gray = 255 * (1 - poissonRatio);
389
+ tempImg.data[px] = gray;
390
+ tempImg.data[px + 1] = gray;
391
+ tempImg.data[px + 2] = gray;
392
+ tempImg.data[px + 3] = 255;
393
+ }
394
+ }
395
+ }
396
+ tempCtx.putImageData(tempImg, 0, 0);
397
+ ctx.imageSmoothingEnabled = true;
398
+ ctx.imageSmoothingQuality = "high";
399
+ ctx.drawImage(tempCanvas, 0, 0, width, height, 0, 0, originalWidth, originalHeight);
400
+ const outImg = ctx.getImageData(0, 0, originalWidth, originalHeight);
401
+ const origPadX = Math.ceil(originalWidth * paddingSize);
402
+ const origPadY = Math.ceil(originalHeight * paddingSize);
403
+ const originalCanvas = document.createElement("canvas");
404
+ originalCanvas.width = originalWidth;
405
+ originalCanvas.height = originalHeight;
406
+ const originalCtx = originalCanvas.getContext("2d");
407
+ originalCtx.drawImage(img, origPadX, origPadY, originalWidth - 2 * origPadX, originalHeight - 2 * origPadY);
408
+ const originalData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
409
+ for (let i = 0; i < outImg.data.length; i += 4) {
410
+ const a = originalData.data[i + 3];
411
+ const upscaledAlpha = outImg.data[i + 3];
412
+ if (a === 0) {
413
+ outImg.data[i] = 255;
414
+ outImg.data[i + 1] = 0;
415
+ } else {
416
+ outImg.data[i] = upscaledAlpha === 0 ? 0 : outImg.data[i];
417
+ outImg.data[i + 1] = a;
418
+ }
419
+ outImg.data[i + 2] = 255;
420
+ outImg.data[i + 3] = 255;
421
+ }
422
+ ctx.putImageData(outImg, 0, 0);
423
+ finalImageData = outImg;
424
+ canvas.toBlob((blob) => {
425
+ if (!blob) {
426
+ reject(new Error("Failed to create PNG blob"));
427
+ return;
428
+ }
429
+ if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
430
+ const totalTime = performance.now() - totalStartTime;
431
+ console.log(`[Total Processing Time] ${totalTime.toFixed(2)}ms`);
432
+ if (scaleFactor < 1) {
433
+ const estimatedFullResTime = totalTime * Math.pow(originalWidth * originalHeight / (width * height), 1.5);
434
+ console.log(`[Estimated time at full resolution] ~${estimatedFullResTime.toFixed(0)}ms`);
435
+ console.log(
436
+ `[Time saved] ~${(estimatedFullResTime - totalTime).toFixed(0)}ms (${Math.round(estimatedFullResTime / totalTime)}\xD7 faster)`
437
+ );
438
+ }
439
+ }
440
+ resolve({
441
+ imageData: finalImageData,
442
+ pngBlob: blob
443
+ });
444
+ }, "image/png");
445
+ };
446
+ img.onerror = () => reject(new Error("Failed to load image"));
447
+ img.src = typeof file === "string" ? file : URL.createObjectURL(file);
448
+ });
449
+ }
450
+ function buildSparseData(shapeMask, boundaryMask, interiorPixels, boundaryPixels, width, height) {
451
+ const pixelCount = interiorPixels.length;
452
+ const neighborIndices = new Int32Array(pixelCount * 4);
453
+ for (let i = 0; i < pixelCount; i++) {
454
+ const idx = interiorPixels[i];
455
+ const x = idx % width;
456
+ const y = Math.floor(idx / width);
457
+ neighborIndices[i * 4 + 0] = x < width - 1 && shapeMask[idx + 1] ? idx + 1 : -1;
458
+ neighborIndices[i * 4 + 1] = x > 0 && shapeMask[idx - 1] ? idx - 1 : -1;
459
+ neighborIndices[i * 4 + 2] = y > 0 && shapeMask[idx - width] ? idx - width : -1;
460
+ neighborIndices[i * 4 + 3] = y < height - 1 && shapeMask[idx + width] ? idx + width : -1;
461
+ }
462
+ return {
463
+ interiorPixels,
464
+ boundaryPixels,
465
+ pixelCount,
466
+ neighborIndices
467
+ };
468
+ }
469
+ function solvePoissonSparse(sparseData, shapeMask, boundaryMask, width, height) {
470
+ const ITERATIONS = POISSON_CONFIG_OPTIMIZED.iterations;
471
+ const C = 0.01;
472
+ const u = new Float32Array(width * height);
473
+ const { interiorPixels, neighborIndices, pixelCount } = sparseData;
474
+ const startTime = performance.now();
475
+ const omega = 1.9;
476
+ const redPixels = [];
477
+ const blackPixels = [];
478
+ for (let i = 0; i < pixelCount; i++) {
479
+ const idx = interiorPixels[i];
480
+ const x = idx % width;
481
+ const y = Math.floor(idx / width);
482
+ if ((x + y) % 2 === 0) {
483
+ redPixels.push(i);
484
+ } else {
485
+ blackPixels.push(i);
486
+ }
487
+ }
488
+ for (let iter = 0; iter < ITERATIONS; iter++) {
489
+ for (const i of redPixels) {
490
+ const idx = interiorPixels[i];
491
+ const eastIdx = neighborIndices[i * 4 + 0];
492
+ const westIdx = neighborIndices[i * 4 + 1];
493
+ const northIdx = neighborIndices[i * 4 + 2];
494
+ const southIdx = neighborIndices[i * 4 + 3];
495
+ let sumN = 0;
496
+ if (eastIdx >= 0) sumN += u[eastIdx];
497
+ if (westIdx >= 0) sumN += u[westIdx];
498
+ if (northIdx >= 0) sumN += u[northIdx];
499
+ if (southIdx >= 0) sumN += u[southIdx];
500
+ const newValue = (C + sumN) / 4;
501
+ u[idx] = omega * newValue + (1 - omega) * u[idx];
502
+ }
503
+ for (const i of blackPixels) {
504
+ const idx = interiorPixels[i];
505
+ const eastIdx = neighborIndices[i * 4 + 0];
506
+ const westIdx = neighborIndices[i * 4 + 1];
507
+ const northIdx = neighborIndices[i * 4 + 2];
508
+ const southIdx = neighborIndices[i * 4 + 3];
509
+ let sumN = 0;
510
+ if (eastIdx >= 0) sumN += u[eastIdx];
511
+ if (westIdx >= 0) sumN += u[westIdx];
512
+ if (northIdx >= 0) sumN += u[northIdx];
513
+ if (southIdx >= 0) sumN += u[southIdx];
514
+ const newValue = (C + sumN) / 4;
515
+ u[idx] = omega * newValue + (1 - omega) * u[idx];
516
+ }
517
+ }
518
+ const tmp = new Float32Array(width * height);
519
+ for (let smooth = 0; smooth < 3; smooth++) {
520
+ tmp.set(u);
521
+ for (let i = 0; i < pixelCount; i++) {
522
+ const idx = interiorPixels[i];
523
+ const eastIdx = neighborIndices[i * 4 + 0];
524
+ const westIdx = neighborIndices[i * 4 + 1];
525
+ const northIdx = neighborIndices[i * 4 + 2];
526
+ const southIdx = neighborIndices[i * 4 + 3];
527
+ let sum = 0;
528
+ let count = 0;
529
+ if (eastIdx >= 0) {
530
+ sum += tmp[eastIdx];
531
+ count++;
532
+ }
533
+ if (westIdx >= 0) {
534
+ sum += tmp[westIdx];
535
+ count++;
536
+ }
537
+ if (northIdx >= 0) {
538
+ sum += tmp[northIdx];
539
+ count++;
540
+ }
541
+ if (southIdx >= 0) {
542
+ sum += tmp[southIdx];
543
+ count++;
544
+ }
545
+ u[idx] = count > 0 ? (tmp[idx] + sum / count) * 0.5 : tmp[idx];
546
+ }
547
+ }
548
+ if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {
549
+ const elapsed = performance.now() - startTime;
550
+ console.log(`[Optimized Poisson Solver (SOR \u03C9=${omega})]`);
551
+ console.log(` Working size: ${width}\xD7${height}`);
552
+ console.log(` Iterations: ${ITERATIONS}`);
553
+ console.log(` Time: ${elapsed.toFixed(2)}ms`);
554
+ console.log(` Interior pixels processed: ${pixelCount}`);
555
+ console.log(` Speed: ${(ITERATIONS * pixelCount / (elapsed * 1e3)).toFixed(2)} Mpixels/sec`);
556
+ }
557
+ return u;
558
+ }
559
+ const GemSmokeShapes = {
560
+ none: 0,
561
+ circle: 1,
562
+ daisy: 2,
563
+ diamond: 3,
564
+ metaballs: 4
565
+ };
566
+ export {
567
+ GemSmokeShapes,
568
+ POISSON_CONFIG_OPTIMIZED,
569
+ gemSmokeFragmentShader,
570
+ gemSmokeMeta,
571
+ toProcessedGemSmoke
572
+ };
573
+ //# sourceMappingURL=gem-smoke.js.map