@paper-design/shaders 0.0.67 → 0.0.69

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 (94) hide show
  1. package/dist/index.d.ts +29 -18
  2. package/dist/index.js +6 -0
  3. package/dist/index.js.map +2 -2
  4. package/dist/shader-sizing.d.ts +0 -5
  5. package/dist/shader-sizing.js +1 -193
  6. package/dist/shader-sizing.js.map +2 -2
  7. package/dist/shaders/color-panels.d.ts +32 -12
  8. package/dist/shaders/color-panels.js +5 -5
  9. package/dist/shaders/color-panels.js.map +2 -2
  10. package/dist/shaders/dithering.d.ts +25 -19
  11. package/dist/shaders/dithering.js +114 -66
  12. package/dist/shaders/dithering.js.map +2 -2
  13. package/dist/shaders/dot-grid.d.ts +28 -14
  14. package/dist/shaders/dot-grid.js +2 -2
  15. package/dist/shaders/dot-grid.js.map +2 -2
  16. package/dist/shaders/dot-orbit.d.ts +27 -9
  17. package/dist/shaders/dot-orbit.js +8 -9
  18. package/dist/shaders/dot-orbit.js.map +2 -2
  19. package/dist/shaders/fluted-glass.d.ts +43 -18
  20. package/dist/shaders/fluted-glass.js +20 -51
  21. package/dist/shaders/fluted-glass.js.map +2 -2
  22. package/dist/shaders/god-rays.d.ts +32 -10
  23. package/dist/shaders/god-rays.js +2 -2
  24. package/dist/shaders/god-rays.js.map +2 -2
  25. package/dist/shaders/grain-gradient.d.ts +41 -17
  26. package/dist/shaders/grain-gradient.js +47 -33
  27. package/dist/shaders/grain-gradient.js.map +2 -2
  28. package/dist/shaders/halftone-cmyk.d.ts +105 -0
  29. package/dist/shaders/halftone-cmyk.js +290 -0
  30. package/dist/shaders/halftone-cmyk.js.map +7 -0
  31. package/dist/shaders/halftone-dots.d.ts +35 -6
  32. package/dist/shaders/halftone-dots.js +18 -55
  33. package/dist/shaders/halftone-dots.js.map +2 -2
  34. package/dist/shaders/heatmap.d.ts +36 -0
  35. package/dist/shaders/heatmap.js +98 -16
  36. package/dist/shaders/heatmap.js.map +2 -2
  37. package/dist/shaders/image-dithering.d.ts +29 -15
  38. package/dist/shaders/image-dithering.js +68 -37
  39. package/dist/shaders/image-dithering.js.map +2 -2
  40. package/dist/shaders/liquid-metal.d.ts +38 -14
  41. package/dist/shaders/liquid-metal.js +5 -2
  42. package/dist/shaders/liquid-metal.js.map +2 -2
  43. package/dist/shaders/mesh-gradient.d.ts +27 -8
  44. package/dist/shaders/mesh-gradient.js +1 -7
  45. package/dist/shaders/mesh-gradient.js.map +2 -2
  46. package/dist/shaders/metaballs.d.ts +24 -8
  47. package/dist/shaders/metaballs.js +2 -2
  48. package/dist/shaders/metaballs.js.map +2 -2
  49. package/dist/shaders/neuro-noise.d.ts +27 -5
  50. package/dist/shaders/neuro-noise.js +2 -3
  51. package/dist/shaders/neuro-noise.js.map +2 -2
  52. package/dist/shaders/paper-texture.d.ts +37 -12
  53. package/dist/shaders/paper-texture.js +20 -20
  54. package/dist/shaders/paper-texture.js.map +2 -2
  55. package/dist/shaders/perlin-noise.d.ts +27 -8
  56. package/dist/shaders/perlin-noise.js +13 -35
  57. package/dist/shaders/perlin-noise.js.map +2 -2
  58. package/dist/shaders/pulsing-border.d.ts +39 -13
  59. package/dist/shaders/pulsing-border.js +9 -7
  60. package/dist/shaders/pulsing-border.js.map +2 -2
  61. package/dist/shaders/simplex-noise.d.ts +25 -6
  62. package/dist/shaders/simplex-noise.js +21 -21
  63. package/dist/shaders/simplex-noise.js.map +2 -2
  64. package/dist/shaders/smoke-ring.d.ts +29 -7
  65. package/dist/shaders/smoke-ring.js +20 -17
  66. package/dist/shaders/smoke-ring.js.map +2 -2
  67. package/dist/shaders/spiral.d.ts +30 -10
  68. package/dist/shaders/spiral.js +4 -4
  69. package/dist/shaders/spiral.js.map +2 -2
  70. package/dist/shaders/static-mesh-gradient.d.ts +30 -9
  71. package/dist/shaders/static-mesh-gradient.js +9 -17
  72. package/dist/shaders/static-mesh-gradient.js.map +2 -2
  73. package/dist/shaders/static-radial-gradient.d.ts +33 -11
  74. package/dist/shaders/static-radial-gradient.js +18 -25
  75. package/dist/shaders/static-radial-gradient.js.map +2 -2
  76. package/dist/shaders/swirl.d.ts +29 -8
  77. package/dist/shaders/swirl.js +4 -4
  78. package/dist/shaders/swirl.js.map +2 -2
  79. package/dist/shaders/voronoi.d.ts +31 -10
  80. package/dist/shaders/voronoi.js +8 -8
  81. package/dist/shaders/voronoi.js.map +2 -2
  82. package/dist/shaders/warp.d.ts +31 -13
  83. package/dist/shaders/warp.js +3 -3
  84. package/dist/shaders/warp.js.map +2 -2
  85. package/dist/shaders/water.d.ts +30 -10
  86. package/dist/shaders/water.js +2 -2
  87. package/dist/shaders/water.js.map +2 -2
  88. package/dist/shaders/waves.d.ts +27 -12
  89. package/dist/shaders/waves.js +3 -3
  90. package/dist/shaders/waves.js.map +2 -2
  91. package/dist/vertex-shader.d.ts +1 -1
  92. package/dist/vertex-shader.js +2 -53
  93. package/dist/vertex-shader.js.map +2 -2
  94. package/package.json +1 -1
@@ -8,16 +8,7 @@ import { declarePI, rotation2, proceduralHash21 } from "../shader-utils.js";
8
8
  const halftoneDotsFragmentShader = `#version 300 es
9
9
  precision mediump float;
10
10
 
11
- uniform mediump vec2 u_resolution;
12
- uniform mediump float u_pixelRatio;
13
- uniform mediump float u_originX;
14
- uniform mediump float u_originY;
15
- uniform mediump float u_fit;
16
-
17
- uniform mediump float u_scale;
18
- uniform mediump float u_rotation;
19
- uniform mediump float u_offsetX;
20
- uniform mediump float u_offsetY;
11
+ uniform float u_rotation;
21
12
 
22
13
  uniform float u_time;
23
14
 
@@ -27,7 +18,7 @@ uniform float u_radius;
27
18
  uniform float u_contrast;
28
19
 
29
20
  uniform sampler2D u_image;
30
- uniform mediump float u_imageAspectRatio;
21
+ uniform float u_imageAspectRatio;
31
22
 
32
23
  uniform float u_size;
33
24
  uniform float u_grainMixer;
@@ -38,6 +29,8 @@ uniform bool u_originalColors;
38
29
  uniform bool u_inverted;
39
30
  uniform float u_type;
40
31
 
32
+ in vec2 v_imageUV;
33
+
41
34
  out vec4 fragColor;
42
35
 
43
36
  ${declarePI}
@@ -65,37 +58,6 @@ float sst(float edge0, float edge1, float x) {
65
58
  return smoothstep(edge0, edge1, x);
66
59
  }
67
60
 
68
- vec2 getImageUV(vec2 uv, vec2 extraScale) {
69
- vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5);
70
- float r = u_rotation * PI / 180.;
71
- mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r));
72
- vec2 graphicOffset = vec2(-u_offsetX, u_offsetY);
73
-
74
- vec2 imageBoxSize;
75
- if (u_fit == 1.) {
76
- imageBoxSize.x = min(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;
77
- } else {
78
- imageBoxSize.x = max(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;
79
- }
80
- imageBoxSize.y = imageBoxSize.x / u_imageAspectRatio;
81
- vec2 imageBoxScale = u_resolution.xy / imageBoxSize;
82
-
83
- vec2 imageUV = uv;
84
- imageUV *= imageBoxScale;
85
- imageUV += boxOrigin * (imageBoxScale - 1.);
86
- imageUV += graphicOffset;
87
- imageUV /= u_scale;
88
- imageUV *= extraScale;
89
- imageUV.x *= u_imageAspectRatio;
90
- imageUV = graphicRotation * imageUV;
91
- imageUV.x /= u_imageAspectRatio;
92
-
93
- imageUV += .5;
94
- imageUV.y = 1. - imageUV.y;
95
-
96
- return imageUV;
97
- }
98
-
99
61
  float getCircle(vec2 uv, float r, float baseR) {
100
62
  r = mix(.25 * baseR, 0., r);
101
63
  float d = length(uv - .5);
@@ -114,7 +76,7 @@ float getCircleWithHole(vec2 uv, float r, float baseR) {
114
76
 
115
77
  r = mix(.75 * baseR, 0., r);
116
78
  float rMod = mod(r, .5);
117
-
79
+
118
80
  float d = length(uv - .5);
119
81
  float aa = fwidth(d);
120
82
  float circle = 1. - smoothstep(rMod - aa, rMod + aa, d);
@@ -133,7 +95,7 @@ float getGooeyBall(vec2 uv, float r, float baseR) {
133
95
  }
134
96
  sizeRadius = mix(sizeRadius * baseR, 0., r);
135
97
  d = 1. - sst(0., sizeRadius, d);
136
-
98
+
137
99
  d = pow(d, 2. + baseR);
138
100
  return d;
139
101
  }
@@ -182,7 +144,7 @@ float getLumBall(vec2 p, vec2 pad, vec2 inCellOffset, float contrast, float base
182
144
  vec2 uv_f = fract(p);
183
145
  vec2 samplingUV = (uv_i + .5 - inCellOffset) * pad + vec2(.5);
184
146
  float outOfFrame = getUvFrame(samplingUV, pad * stepSize);
185
-
147
+
186
148
  float lum = getLumAtPx(samplingUV, contrast);
187
149
  ballColor = texture(u_image, samplingUV);
188
150
  ballColor.rgb *= ballColor.a;
@@ -208,7 +170,7 @@ float getLumBall(vec2 p, vec2 pad, vec2 inCellOffset, float contrast, float base
208
170
 
209
171
 
210
172
  void main() {
211
-
173
+
212
174
  float stepMultiplier = 1.;
213
175
  if (u_type == 0.) {
214
176
  // classic
@@ -217,7 +179,7 @@ void main() {
217
179
  // gooey & soft
218
180
  stepMultiplier = 6.;
219
181
  }
220
-
182
+
221
183
  float cellsPerSide = mix(300., 7., pow(u_size, .7));
222
184
  cellsPerSide /= stepMultiplier;
223
185
  float cellSizeY = 1. / cellsPerSide;
@@ -227,8 +189,7 @@ void main() {
227
189
  pad *= .7;
228
190
  }
229
191
 
230
- vec2 uvNormalised = (gl_FragCoord.xy - .5 * u_resolution) / u_resolution.xy;
231
- vec2 uv = getImageUV(uvNormalised, vec2(1.));
192
+ vec2 uv = v_imageUV;
232
193
  uv -= vec2(.5);
233
194
  uv /= pad;
234
195
 
@@ -276,9 +237,9 @@ void main() {
276
237
  totalOpacity += shape;
277
238
  }
278
239
  }
279
-
240
+
280
241
  const float eps = 1e-4;
281
-
242
+
282
243
  totalColor /= max(totalShape, eps);
283
244
  totalOpacity /= max(totalShape, eps);
284
245
 
@@ -296,7 +257,9 @@ void main() {
296
257
  }
297
258
 
298
259
  vec2 grainSize = mix(2000., 200., u_grainSize) * vec2(1., 1. / u_imageAspectRatio);
299
- vec2 grainUV = getImageUV(uvNormalised, grainSize);
260
+ vec2 grainUV = v_imageUV - .5;
261
+ grainUV *= grainSize;
262
+ grainUV += .5;
300
263
  float grain = valueNoise(grainUV);
301
264
  grain = smoothstep(.55, .7 + .2 * u_grainMixer, grain);
302
265
  grain *= u_grainMixer;
@@ -304,7 +267,7 @@ void main() {
304
267
 
305
268
  vec3 color = vec3(0.);
306
269
  float opacity = 0.;
307
-
270
+
308
271
  if (u_originalColors == true) {
309
272
  color = totalColor * finalShape;
310
273
  opacity = totalOpacity * finalShape;
@@ -327,13 +290,13 @@ void main() {
327
290
  float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.));
328
291
  grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);
329
292
  grainOverlay = pow(grainOverlay, 1.3);
330
-
293
+
331
294
  float grainOverlayV = grainOverlay * 2. - 1.;
332
295
  vec3 grainOverlayColor = vec3(step(0., grainOverlayV));
333
296
  float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV);
334
297
  grainOverlayStrength = pow(grainOverlayStrength, .8);
335
298
  color = mix(color, grainOverlayColor, .5 * grainOverlayStrength);
336
-
299
+
337
300
  opacity += .5 * grainOverlayStrength;
338
301
  opacity = clamp(opacity, 0., 1.);
339
302
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/shaders/halftone-dots.ts"],
4
- "sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js';\n\n/**\n\n\n Uniforms:\n - u_colorBack, u_colorFront, u_colorHighlight (RGBA)\n (u_colorHighlight to be the lighstraight parts of u_colorFront pixels)\n - size: px size set relative to canvas resolution\n */\n\n// language=GLSL\nexport const halftoneDotsFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform mediump vec2 u_resolution;\nuniform mediump float u_pixelRatio;\nuniform mediump float u_originX;\nuniform mediump float u_originY;\nuniform mediump float u_fit;\n\nuniform mediump float u_scale;\nuniform mediump float u_rotation;\nuniform mediump float u_offsetX;\nuniform mediump float u_offsetY;\n\nuniform float u_time;\n\nuniform vec4 u_colorFront;\nuniform vec4 u_colorBack;\nuniform float u_radius;\nuniform float u_contrast;\n\nuniform sampler2D u_image;\nuniform mediump float u_imageAspectRatio;\n\nuniform float u_size;\nuniform float u_grainMixer;\nuniform float u_grainOverlay;\nuniform float u_grainSize;\nuniform float u_grid;\nuniform bool u_originalColors;\nuniform bool u_inverted;\nuniform float u_type;\n\nout vec4 fragColor;\n\n${declarePI}\n${rotation2}\n${proceduralHash21}\n\nfloat valueNoise(vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = hash21(i);\n float b = hash21(i + vec2(1.0, 0.0));\n float c = hash21(i + vec2(0.0, 1.0));\n float d = hash21(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n float x1 = mix(a, b, u.x);\n float x2 = mix(c, d, u.x);\n return mix(x1, x2, u.y);\n}\n\nfloat lst(float edge0, float edge1, float x) {\n return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n}\n\nfloat sst(float edge0, float edge1, float x) {\n return smoothstep(edge0, edge1, x);\n}\n\nvec2 getImageUV(vec2 uv, vec2 extraScale) {\n vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5);\n float r = u_rotation * PI / 180.;\n mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r));\n vec2 graphicOffset = vec2(-u_offsetX, u_offsetY);\n\n vec2 imageBoxSize;\n if (u_fit == 1.) {\n imageBoxSize.x = min(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;\n } else {\n imageBoxSize.x = max(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;\n }\n imageBoxSize.y = imageBoxSize.x / u_imageAspectRatio;\n vec2 imageBoxScale = u_resolution.xy / imageBoxSize;\n\n vec2 imageUV = uv;\n imageUV *= imageBoxScale;\n imageUV += boxOrigin * (imageBoxScale - 1.);\n imageUV += graphicOffset;\n imageUV /= u_scale;\n imageUV *= extraScale;\n imageUV.x *= u_imageAspectRatio;\n imageUV = graphicRotation * imageUV;\n imageUV.x /= u_imageAspectRatio;\n\n imageUV += .5;\n imageUV.y = 1. - imageUV.y;\n \n return imageUV;\n}\n\nfloat getCircle(vec2 uv, float r, float baseR) {\n r = mix(.25 * baseR, 0., r);\n float d = length(uv - .5);\n float aa = fwidth(d);\n return 1. - smoothstep(r - aa, r + aa, d);\n}\n\nfloat getCell(vec2 uv) {\n float insideX = step(0.0, uv.x) * (1.0 - step(1.0, uv.x));\n float insideY = step(0.0, uv.y) * (1.0 - step(1.0, uv.y));\n return insideX * insideY;\n}\n\nfloat getCircleWithHole(vec2 uv, float r, float baseR) {\n float cell = getCell(uv);\n\n r = mix(.75 * baseR, 0., r);\n float rMod = mod(r, .5);\n \n float d = length(uv - .5);\n float aa = fwidth(d);\n float circle = 1. - smoothstep(rMod - aa, rMod + aa, d);\n if (r < .5) {\n return circle;\n } else {\n return cell - circle;\n }\n}\n\nfloat getGooeyBall(vec2 uv, float r, float baseR) {\n float d = length(uv - .5);\n float sizeRadius = .3;\n if (u_grid == 1.) {\n sizeRadius = .42;\n }\n sizeRadius = mix(sizeRadius * baseR, 0., r);\n d = 1. - sst(0., sizeRadius, d);\n \n d = pow(d, 2. + baseR);\n return d;\n}\n\nfloat getSoftBall(vec2 uv, float r, float baseR) {\n float d = length(uv - .5);\n float sizeRadius = clamp(baseR, 0., 1.);\n sizeRadius = mix(.5 * sizeRadius, 0., r);\n d = 1. - lst(0., sizeRadius, d);\n float powRadius = 1. - lst(0., 2., baseR);\n d = pow(d, 4. + 3. * powRadius);\n return d;\n}\n\nfloat getUvFrame(vec2 uv, vec2 pad) {\n float aa = 0.0001;\n\n float left = smoothstep(-pad.x, -pad.x + aa, uv.x);\n float right = smoothstep(1.0 + pad.x, 1.0 + pad.x - aa, uv.x);\n float bottom = smoothstep(-pad.y, -pad.y + aa, uv.y);\n float top = smoothstep(1.0 + pad.y, 1.0 + pad.y - aa, uv.y);\n\n return left * right * bottom * top;\n}\n\nfloat sigmoid(float x, float k) {\n return 1.0 / (1.0 + exp(-k * (x - 0.5)));\n}\n\nfloat getLumAtPx(vec2 uv, float contrast) {\n vec4 tex = texture(u_image, uv);\n vec3 color = vec3(\n sigmoid(tex.r, contrast),\n sigmoid(tex.g, contrast),\n sigmoid(tex.b, contrast)\n );\n float lum = dot(vec3(0.2126, 0.7152, 0.0722), color);\n lum = mix(1., lum, tex.a);\n lum = u_inverted ? (1. - lum) : lum;\n return lum;\n}\n\nfloat getLumBall(vec2 p, vec2 pad, vec2 inCellOffset, float contrast, float baseR, float stepSize, out vec4 ballColor) {\n p += inCellOffset;\n vec2 uv_i = floor(p);\n vec2 uv_f = fract(p);\n vec2 samplingUV = (uv_i + .5 - inCellOffset) * pad + vec2(.5);\n float outOfFrame = getUvFrame(samplingUV, pad * stepSize);\n \n float lum = getLumAtPx(samplingUV, contrast);\n ballColor = texture(u_image, samplingUV);\n ballColor.rgb *= ballColor.a;\n ballColor *= outOfFrame;\n\n float ball = 0.;\n if (u_type == 0.) {\n // classic\n ball = getCircle(uv_f, lum, baseR);\n } else if (u_type == 1.) {\n // gooey\n ball = getGooeyBall(uv_f, lum, baseR);\n } else if (u_type == 2.) {\n // holes\n ball = getCircleWithHole(uv_f, lum, baseR);\n } else if (u_type == 3.) {\n // soft\n ball = getSoftBall(uv_f, lum, baseR);\n }\n\n return ball * outOfFrame;\n}\n\n\nvoid main() {\n \n float stepMultiplier = 1.;\n if (u_type == 0.) {\n // classic\n stepMultiplier = 2.;\n } else if (u_type == 1. || u_type == 3.) {\n // gooey & soft\n stepMultiplier = 6.;\n }\n \n float cellsPerSide = mix(300., 7., pow(u_size, .7));\n cellsPerSide /= stepMultiplier;\n float cellSizeY = 1. / cellsPerSide;\n vec2 pad = cellSizeY * vec2(1. / u_imageAspectRatio, 1.);\n if (u_type == 1. && u_grid == 1.) {\n // gooey diagonal grid works differently\n pad *= .7;\n }\n\n vec2 uvNormalised = (gl_FragCoord.xy - .5 * u_resolution) / u_resolution.xy;\n vec2 uv = getImageUV(uvNormalised, vec2(1.));\n uv -= vec2(.5);\n uv /= pad;\n\n float contrast = mix(0., 15., pow(u_contrast, 1.5));\n float baseRadius = u_radius;\n if (u_originalColors == true) {\n contrast = mix(.1, 4., pow(u_contrast, 2.));\n baseRadius = 2. * pow(.5 * u_radius, .3);\n }\n\n float totalShape = 0.;\n vec3 totalColor = vec3(0.);\n float totalOpacity = 0.;\n\n vec4 ballColor;\n float shape;\n float stepSize = 1. / stepMultiplier;\n for (float x = -0.5; x < 0.5; x += stepSize) {\n for (float y = -0.5; y < 0.5; y += stepSize) {\n vec2 offset = vec2(x, y);\n\n if (u_grid == 1.) {\n float rowIndex = floor((y + .5) / stepSize);\n float colIndex = floor((x + .5) / stepSize);\n if (stepSize == 1.) {\n rowIndex = floor(uv.y + y + 1.);\n if (u_type == 1.) {\n colIndex = floor(uv.x + x + 1.);\n }\n }\n if (u_type == 1.) {\n if (mod(rowIndex + colIndex, 2.) == 1.) {\n continue;\n }\n } else {\n if (mod(rowIndex, 2.) == 1.) {\n offset.x += .5 * stepSize;\n }\n }\n }\n\n shape = getLumBall(uv, pad, offset, contrast, baseRadius, stepSize, ballColor);\n totalColor += ballColor.rgb * shape;\n totalShape += shape;\n totalOpacity += shape;\n }\n }\n \n const float eps = 1e-4;\n \n totalColor /= max(totalShape, eps);\n totalOpacity /= max(totalShape, eps);\n\n float finalShape = 0.;\n if (u_type == 0.) {\n finalShape = min(1., totalShape);\n } else if (u_type == 1.) {\n float aa = fwidth(totalShape);\n float th = .5;\n finalShape = smoothstep(th - aa, th + aa, totalShape);\n } else if (u_type == 2.) {\n finalShape = min(1., totalShape);\n } else if (u_type == 3.) {\n finalShape = totalShape;\n }\n\n vec2 grainSize = mix(2000., 200., u_grainSize) * vec2(1., 1. / u_imageAspectRatio);\n vec2 grainUV = getImageUV(uvNormalised, grainSize);\n float grain = valueNoise(grainUV);\n grain = smoothstep(.55, .7 + .2 * u_grainMixer, grain);\n grain *= u_grainMixer;\n finalShape = mix(finalShape, 0., grain);\n\n vec3 color = vec3(0.);\n float opacity = 0.;\n \n if (u_originalColors == true) {\n color = totalColor * finalShape;\n opacity = totalOpacity * finalShape;\n\n vec3 bgColor = u_colorBack.rgb * u_colorBack.a;\n color = color + bgColor * (1. - opacity);\n opacity = opacity + u_colorBack.a * (1. - opacity);\n } else {\n vec3 fgColor = u_colorFront.rgb * u_colorFront.a;\n float fgOpacity = u_colorFront.a;\n vec3 bgColor = u_colorBack.rgb * u_colorBack.a;\n float bgOpacity = u_colorBack.a;\n\n color = fgColor * finalShape;\n opacity = fgOpacity * finalShape;\n color += bgColor * (1. - opacity);\n opacity += bgOpacity * (1. - opacity);\n }\n\n float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.));\n grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);\n grainOverlay = pow(grainOverlay, 1.3);\n \n float grainOverlayV = grainOverlay * 2. - 1.;\n vec3 grainOverlayColor = vec3(step(0., grainOverlayV));\n float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV);\n grainOverlayStrength = pow(grainOverlayStrength, .8);\n color = mix(color, grainOverlayColor, .5 * grainOverlayStrength);\n \n opacity += .5 * grainOverlayStrength;\n opacity = clamp(opacity, 0., 1.);\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport interface HalftoneDotsUniforms extends ShaderSizingUniforms {\n u_image: HTMLImageElement | string | undefined;\n u_colorFront: [number, number, number, number];\n u_colorBack: [number, number, number, number];\n u_size: number;\n u_grid: (typeof HalftoneDotsGrids)[HalftoneDotsGrid];\n u_radius: number;\n u_contrast: number;\n u_originalColors: boolean;\n u_inverted: boolean;\n u_grainMixer: number;\n u_grainOverlay: number;\n u_grainSize: number;\n u_type: (typeof HalftoneDotsTypes)[HalftoneDotsType];\n}\n\nexport interface HalftoneDotsParams extends ShaderSizingParams, ShaderMotionParams {\n image?: HTMLImageElement | string;\n colorFront?: string;\n colorBack?: string;\n size?: number;\n grid?: HalftoneDotsGrid;\n radius?: number;\n contrast?: number;\n originalColors?: boolean;\n inverted?: boolean;\n grainMixer?: number;\n grainOverlay?: number;\n grainSize?: number;\n type?: HalftoneDotsType;\n}\n\nexport const HalftoneDotsTypes = {\n classic: 0,\n gooey: 1,\n holes: 2,\n soft: 3,\n} as const;\n\nexport type HalftoneDotsType = keyof typeof HalftoneDotsTypes;\n\nexport const HalftoneDotsGrids = {\n square: 0,\n hex: 1,\n} as const;\n\nexport type HalftoneDotsGrid = keyof typeof HalftoneDotsGrids;\n"],
5
- "mappings": ";;;;;AACA,eAAmE;AACnE,SAAS,WAAW,WAAW,wBAAwB;AAYhD,MAAM,6BAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmChD,SAAS;AAAA,EACT,SAAS;AAAA,EACT,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2UX,MAAM,oBAAoB;AAAA,EAC/B,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AACR;AAIO,MAAM,oBAAoB;AAAA,EAC/B,QAAQ;AAAA,EACR,KAAK;AACP;",
4
+ "sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js';\n\n/**\n * A halftone-dot image filter featuring customizable grids, color palettes, and dot styles.\n *\n * Fragment shader uniforms:\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_time (float): Animation time\n * - u_image (sampler2D): Source image texture\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n * - u_colorFront (vec4): Foreground color in RGBA\n * - u_colorBack (vec4): Background color in RGBA\n * - u_originalColors (bool): Use sampled image's original colors instead of colorFront\n * - u_type (float): Dot style (0 = classic, 1 = gooey, 2 = holes, 3 = soft)\n * - u_inverted (bool): Inverts the image luminance, doesn't affect the color scheme; not effective at zero contrast\n * - u_grid (float): Grid type (0 = square, 1 = hex)\n * - u_size (float): Grid size relative to the image box (0 to 1)\n * - u_radius (float): Maximum dot size relative to grid cell (0 to 2)\n * - u_contrast (float): Contrast applied to the sampled image (0 to 1)\n * - u_grainMixer (float): Strength of grain distortion applied to shape edges (0 to 1)\n * - u_grainOverlay (float): Post-processing black/white grain overlay (0 to 1)\n * - u_grainSize (float): Scale applied to both grain distortion and grain overlay (0 to 1)\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_imageUV (vec2): Image UV coordinates with global sizing (rotation, scale, offset, etc) applied\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n *\n */\n\n// language=GLSL\nexport const halftoneDotsFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform float u_rotation;\n\nuniform float u_time;\n\nuniform vec4 u_colorFront;\nuniform vec4 u_colorBack;\nuniform float u_radius;\nuniform float u_contrast;\n\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\n\nuniform float u_size;\nuniform float u_grainMixer;\nuniform float u_grainOverlay;\nuniform float u_grainSize;\nuniform float u_grid;\nuniform bool u_originalColors;\nuniform bool u_inverted;\nuniform float u_type;\n\nin vec2 v_imageUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ rotation2 }\n${ proceduralHash21 }\n\nfloat valueNoise(vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = hash21(i);\n float b = hash21(i + vec2(1.0, 0.0));\n float c = hash21(i + vec2(0.0, 1.0));\n float d = hash21(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n float x1 = mix(a, b, u.x);\n float x2 = mix(c, d, u.x);\n return mix(x1, x2, u.y);\n}\n\nfloat lst(float edge0, float edge1, float x) {\n return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n}\n\nfloat sst(float edge0, float edge1, float x) {\n return smoothstep(edge0, edge1, x);\n}\n\nfloat getCircle(vec2 uv, float r, float baseR) {\n r = mix(.25 * baseR, 0., r);\n float d = length(uv - .5);\n float aa = fwidth(d);\n return 1. - smoothstep(r - aa, r + aa, d);\n}\n\nfloat getCell(vec2 uv) {\n float insideX = step(0.0, uv.x) * (1.0 - step(1.0, uv.x));\n float insideY = step(0.0, uv.y) * (1.0 - step(1.0, uv.y));\n return insideX * insideY;\n}\n\nfloat getCircleWithHole(vec2 uv, float r, float baseR) {\n float cell = getCell(uv);\n\n r = mix(.75 * baseR, 0., r);\n float rMod = mod(r, .5);\n\n float d = length(uv - .5);\n float aa = fwidth(d);\n float circle = 1. - smoothstep(rMod - aa, rMod + aa, d);\n if (r < .5) {\n return circle;\n } else {\n return cell - circle;\n }\n}\n\nfloat getGooeyBall(vec2 uv, float r, float baseR) {\n float d = length(uv - .5);\n float sizeRadius = .3;\n if (u_grid == 1.) {\n sizeRadius = .42;\n }\n sizeRadius = mix(sizeRadius * baseR, 0., r);\n d = 1. - sst(0., sizeRadius, d);\n\n d = pow(d, 2. + baseR);\n return d;\n}\n\nfloat getSoftBall(vec2 uv, float r, float baseR) {\n float d = length(uv - .5);\n float sizeRadius = clamp(baseR, 0., 1.);\n sizeRadius = mix(.5 * sizeRadius, 0., r);\n d = 1. - lst(0., sizeRadius, d);\n float powRadius = 1. - lst(0., 2., baseR);\n d = pow(d, 4. + 3. * powRadius);\n return d;\n}\n\nfloat getUvFrame(vec2 uv, vec2 pad) {\n float aa = 0.0001;\n\n float left = smoothstep(-pad.x, -pad.x + aa, uv.x);\n float right = smoothstep(1.0 + pad.x, 1.0 + pad.x - aa, uv.x);\n float bottom = smoothstep(-pad.y, -pad.y + aa, uv.y);\n float top = smoothstep(1.0 + pad.y, 1.0 + pad.y - aa, uv.y);\n\n return left * right * bottom * top;\n}\n\nfloat sigmoid(float x, float k) {\n return 1.0 / (1.0 + exp(-k * (x - 0.5)));\n}\n\nfloat getLumAtPx(vec2 uv, float contrast) {\n vec4 tex = texture(u_image, uv);\n vec3 color = vec3(\n sigmoid(tex.r, contrast),\n sigmoid(tex.g, contrast),\n sigmoid(tex.b, contrast)\n );\n float lum = dot(vec3(0.2126, 0.7152, 0.0722), color);\n lum = mix(1., lum, tex.a);\n lum = u_inverted ? (1. - lum) : lum;\n return lum;\n}\n\nfloat getLumBall(vec2 p, vec2 pad, vec2 inCellOffset, float contrast, float baseR, float stepSize, out vec4 ballColor) {\n p += inCellOffset;\n vec2 uv_i = floor(p);\n vec2 uv_f = fract(p);\n vec2 samplingUV = (uv_i + .5 - inCellOffset) * pad + vec2(.5);\n float outOfFrame = getUvFrame(samplingUV, pad * stepSize);\n\n float lum = getLumAtPx(samplingUV, contrast);\n ballColor = texture(u_image, samplingUV);\n ballColor.rgb *= ballColor.a;\n ballColor *= outOfFrame;\n\n float ball = 0.;\n if (u_type == 0.) {\n // classic\n ball = getCircle(uv_f, lum, baseR);\n } else if (u_type == 1.) {\n // gooey\n ball = getGooeyBall(uv_f, lum, baseR);\n } else if (u_type == 2.) {\n // holes\n ball = getCircleWithHole(uv_f, lum, baseR);\n } else if (u_type == 3.) {\n // soft\n ball = getSoftBall(uv_f, lum, baseR);\n }\n\n return ball * outOfFrame;\n}\n\n\nvoid main() {\n\n float stepMultiplier = 1.;\n if (u_type == 0.) {\n // classic\n stepMultiplier = 2.;\n } else if (u_type == 1. || u_type == 3.) {\n // gooey & soft\n stepMultiplier = 6.;\n }\n\n float cellsPerSide = mix(300., 7., pow(u_size, .7));\n cellsPerSide /= stepMultiplier;\n float cellSizeY = 1. / cellsPerSide;\n vec2 pad = cellSizeY * vec2(1. / u_imageAspectRatio, 1.);\n if (u_type == 1. && u_grid == 1.) {\n // gooey diagonal grid works differently\n pad *= .7;\n }\n\n vec2 uv = v_imageUV;\n uv -= vec2(.5);\n uv /= pad;\n\n float contrast = mix(0., 15., pow(u_contrast, 1.5));\n float baseRadius = u_radius;\n if (u_originalColors == true) {\n contrast = mix(.1, 4., pow(u_contrast, 2.));\n baseRadius = 2. * pow(.5 * u_radius, .3);\n }\n\n float totalShape = 0.;\n vec3 totalColor = vec3(0.);\n float totalOpacity = 0.;\n\n vec4 ballColor;\n float shape;\n float stepSize = 1. / stepMultiplier;\n for (float x = -0.5; x < 0.5; x += stepSize) {\n for (float y = -0.5; y < 0.5; y += stepSize) {\n vec2 offset = vec2(x, y);\n\n if (u_grid == 1.) {\n float rowIndex = floor((y + .5) / stepSize);\n float colIndex = floor((x + .5) / stepSize);\n if (stepSize == 1.) {\n rowIndex = floor(uv.y + y + 1.);\n if (u_type == 1.) {\n colIndex = floor(uv.x + x + 1.);\n }\n }\n if (u_type == 1.) {\n if (mod(rowIndex + colIndex, 2.) == 1.) {\n continue;\n }\n } else {\n if (mod(rowIndex, 2.) == 1.) {\n offset.x += .5 * stepSize;\n }\n }\n }\n\n shape = getLumBall(uv, pad, offset, contrast, baseRadius, stepSize, ballColor);\n totalColor += ballColor.rgb * shape;\n totalShape += shape;\n totalOpacity += shape;\n }\n }\n\n const float eps = 1e-4;\n\n totalColor /= max(totalShape, eps);\n totalOpacity /= max(totalShape, eps);\n\n float finalShape = 0.;\n if (u_type == 0.) {\n finalShape = min(1., totalShape);\n } else if (u_type == 1.) {\n float aa = fwidth(totalShape);\n float th = .5;\n finalShape = smoothstep(th - aa, th + aa, totalShape);\n } else if (u_type == 2.) {\n finalShape = min(1., totalShape);\n } else if (u_type == 3.) {\n finalShape = totalShape;\n }\n\n vec2 grainSize = mix(2000., 200., u_grainSize) * vec2(1., 1. / u_imageAspectRatio);\n vec2 grainUV = v_imageUV - .5;\n grainUV *= grainSize;\n grainUV += .5;\n float grain = valueNoise(grainUV);\n grain = smoothstep(.55, .7 + .2 * u_grainMixer, grain);\n grain *= u_grainMixer;\n finalShape = mix(finalShape, 0., grain);\n\n vec3 color = vec3(0.);\n float opacity = 0.;\n\n if (u_originalColors == true) {\n color = totalColor * finalShape;\n opacity = totalOpacity * finalShape;\n\n vec3 bgColor = u_colorBack.rgb * u_colorBack.a;\n color = color + bgColor * (1. - opacity);\n opacity = opacity + u_colorBack.a * (1. - opacity);\n } else {\n vec3 fgColor = u_colorFront.rgb * u_colorFront.a;\n float fgOpacity = u_colorFront.a;\n vec3 bgColor = u_colorBack.rgb * u_colorBack.a;\n float bgOpacity = u_colorBack.a;\n\n color = fgColor * finalShape;\n opacity = fgOpacity * finalShape;\n color += bgColor * (1. - opacity);\n opacity += bgOpacity * (1. - opacity);\n }\n\n float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.));\n grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);\n grainOverlay = pow(grainOverlay, 1.3);\n\n float grainOverlayV = grainOverlay * 2. - 1.;\n vec3 grainOverlayColor = vec3(step(0., grainOverlayV));\n float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV);\n grainOverlayStrength = pow(grainOverlayStrength, .8);\n color = mix(color, grainOverlayColor, .5 * grainOverlayStrength);\n\n opacity += .5 * grainOverlayStrength;\n opacity = clamp(opacity, 0., 1.);\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport interface HalftoneDotsUniforms extends ShaderSizingUniforms {\n u_image: HTMLImageElement | string | undefined;\n u_colorFront: [number, number, number, number];\n u_colorBack: [number, number, number, number];\n u_size: number;\n u_grid: (typeof HalftoneDotsGrids)[HalftoneDotsGrid];\n u_radius: number;\n u_contrast: number;\n u_originalColors: boolean;\n u_inverted: boolean;\n u_grainMixer: number;\n u_grainOverlay: number;\n u_grainSize: number;\n u_type: (typeof HalftoneDotsTypes)[HalftoneDotsType];\n}\n\nexport interface HalftoneDotsParams extends ShaderSizingParams, ShaderMotionParams {\n image?: HTMLImageElement | string;\n colorFront?: string;\n colorBack?: string;\n size?: number;\n grid?: HalftoneDotsGrid;\n radius?: number;\n contrast?: number;\n originalColors?: boolean;\n inverted?: boolean;\n grainMixer?: number;\n grainOverlay?: number;\n grainSize?: number;\n type?: HalftoneDotsType;\n}\n\nexport const HalftoneDotsTypes = {\n classic: 0,\n gooey: 1,\n holes: 2,\n soft: 3,\n} as const;\n\nexport type HalftoneDotsType = keyof typeof HalftoneDotsTypes;\n\nexport const HalftoneDotsGrids = {\n square: 0,\n hex: 1,\n} as const;\n\nexport type HalftoneDotsGrid = keyof typeof HalftoneDotsGrids;\n"],
5
+ "mappings": ";;;;;AACA,eAAmE;AACnE,SAAS,WAAW,WAAW,wBAAwB;AAyChD,MAAM,6BAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4B/C,SAAU;AAAA,EACV,SAAU;AAAA,EACV,gBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6Sb,MAAM,oBAAoB;AAAA,EAC/B,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AACR;AAIO,MAAM,oBAAoB;AAAA,EAC/B,QAAQ;AAAA,EACR,KAAK;AACP;",
6
6
  "names": []
7
7
  }
@@ -4,6 +4,42 @@ import type { ShaderSizingParams, ShaderSizingUniforms } from '../shader-sizing.
4
4
  export declare const heatmapMeta: {
5
5
  readonly maxColorCount: 10;
6
6
  };
7
+ /**
8
+ * A glowing gradient of colors flowing through an input shape.
9
+ * The effect creates a smoothly animated wave of intensity across the image.
10
+ *
11
+ * Fragment shader uniforms:
12
+ * - u_time (float): Animation time
13
+ * - u_image (sampler2D): Pre-processed source image texture (R = contour, G = outer blur, B = inner blur)
14
+ * - u_imageAspectRatio (float): Aspect ratio of the source image
15
+ * - u_colorBack (vec4): Background color in RGBA
16
+ * - u_colors (vec4[]): Up to 10 heatmap colors in RGBA
17
+ * - u_colorsCount (float): Number of active colors
18
+ * - u_contour (float): Heat intensity near the edges of the input shape (0 to 1)
19
+ * - u_angle (float): Direction of the heatwaves in degrees (0 to 360)
20
+ * - u_noise (float): Grain applied across the entire graphic (0 to 1)
21
+ * - u_innerGlow (float): Size of the heated area inside the input shape (0 to 1)
22
+ * - u_outerGlow (float): Size of the heated area outside the input shape (0 to 1)
23
+ *
24
+ * Vertex shader outputs (used in fragment shader):
25
+ * - v_imageUV (vec2): UV coordinates for sampling the source image, with fit, scale, rotation, and offset applied
26
+ * - v_objectUV (vec2): Object box UV coordinates with global sizing (scale, rotation, offsets, etc) applied
27
+ *
28
+ * Vertex shader uniforms:
29
+ * - u_resolution (vec2): Canvas resolution in pixels
30
+ * - u_pixelRatio (float): Device pixel ratio
31
+ * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
32
+ * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
33
+ * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
34
+ * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
35
+ * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
36
+ * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
37
+ * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
38
+ * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
39
+ * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
40
+ * - u_imageAspectRatio (float): Aspect ratio of the source image
41
+ *
42
+ */
7
43
  export declare const heatmapFragmentShader: string;
8
44
  export declare function toProcessedHeatmap(file: File | string): Promise<{
9
45
  blob: Blob;
@@ -127,6 +127,26 @@ float shadowShape(vec2 uv, float t, float contour) {
127
127
  return s;
128
128
  }
129
129
 
130
+ float blurEdge3x3(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius, float centerSample) {
131
+ vec2 texel = 1.0 / vec2(textureSize(tex, 0));
132
+ vec2 r = radius * texel;
133
+
134
+ float w1 = 1.0, w2 = 2.0, w4 = 4.0;
135
+ float norm = 16.0;
136
+ float sum = w4 * centerSample;
137
+
138
+ sum += w2 * textureGrad(tex, uv + vec2(0.0, -r.y), dudx, dudy).g;
139
+ sum += w2 * textureGrad(tex, uv + vec2(0.0, r.y), dudx, dudy).g;
140
+ sum += w2 * textureGrad(tex, uv + vec2(-r.x, 0.0), dudx, dudy).g;
141
+ sum += w2 * textureGrad(tex, uv + vec2(r.x, 0.0), dudx, dudy).g;
142
+
143
+ sum += w1 * textureGrad(tex, uv + vec2(-r.x, -r.y), dudx, dudy).g;
144
+ sum += w1 * textureGrad(tex, uv + vec2(r.x, -r.y), dudx, dudy).g;
145
+ sum += w1 * textureGrad(tex, uv + vec2(-r.x, r.y), dudx, dudy).g;
146
+ sum += w1 * textureGrad(tex, uv + vec2(r.x, r.y), dudx, dudy).g;
147
+
148
+ return sum / norm;
149
+ }
130
150
 
131
151
  void main() {
132
152
  vec2 uv = v_objectUV + .5;
@@ -139,6 +159,9 @@ void main() {
139
159
  float imgSoftFrame = getImgFrame(imgUV, .03);
140
160
 
141
161
  vec4 img = texture(u_image, imgUV);
162
+ vec2 dudx = dFdx(imgUV);
163
+ vec2 dudy = dFdy(imgUV);
164
+
142
165
  if (img.a == 0.) {
143
166
  fragColor = u_colorBack;
144
167
  return;
@@ -164,6 +187,9 @@ void main() {
164
187
  ) + vec2(.5);
165
188
 
166
189
  float shape = img[0];
190
+
191
+ img[1] = blurEdge3x3(u_image, imgUV, dudx, dudy, 8., img[1]);
192
+
167
193
  float outerBlur = 1. - mix(1., img[1], shape);
168
194
  float innerBlur = mix(img[1], 0., shape);
169
195
  float contour = mix(img[2], 0., shape);
@@ -260,25 +286,33 @@ function toProcessedHeatmap(file) {
260
286
  }
261
287
  ctx.fillStyle = "white";
262
288
  ctx.fillRect(0, 0, canvas.width, canvas.height);
263
- ctx.filter = "grayscale(100%) blur(" + maxBlur + "px)";
264
- ctx.drawImage(image, padding, padding, imgWidth, imgHeight);
265
- const bigBlurData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
266
- ctx.fillRect(0, 0, canvas.width, canvas.height);
267
- ctx.filter = "grayscale(100%) blur(" + Math.round(0.12 * maxBlur) + "px)";
268
- ctx.drawImage(image, padding, padding, imgWidth, imgHeight);
269
- const innerBlurSmallData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
270
- ctx.fillRect(0, 0, canvas.width, canvas.height);
271
- ctx.filter = "grayscale(100%) blur(5px)";
272
289
  ctx.drawImage(image, padding, padding, imgWidth, imgHeight);
273
- const contourData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
274
- let processedImageData = ctx.createImageData(canvas.width, canvas.height);
275
- const totalPixels = canvas.width * canvas.height;
290
+ const { width, height } = canvas;
291
+ const srcImageData = ctx.getImageData(0, 0, width, height);
292
+ const src = srcImageData.data;
293
+ const totalPixels = width * height;
294
+ const gray = new Uint8ClampedArray(totalPixels);
276
295
  for (let i = 0; i < totalPixels; i++) {
277
296
  const px = i * 4;
278
- processedImageData.data[px] = contourData[px];
279
- processedImageData.data[px + 1] = bigBlurData[px];
280
- processedImageData.data[px + 2] = innerBlurSmallData[px];
281
- processedImageData.data[px + 3] = 255;
297
+ const r = src[px] ?? 0;
298
+ const g = src[px + 1] ?? 0;
299
+ const b = src[px + 2] ?? 0;
300
+ gray[i] = 0.299 * r + 0.587 * g + 0.114 * b | 0;
301
+ }
302
+ const bigBlurRadius = maxBlur;
303
+ const innerBlurRadius = Math.max(1, Math.round(0.12 * maxBlur));
304
+ const contourRadius = 5;
305
+ const bigBlurGray = multiPassBlurGray(gray, width, height, bigBlurRadius, 3);
306
+ const innerBlurGray = multiPassBlurGray(gray, width, height, innerBlurRadius, 3);
307
+ const contourGray = multiPassBlurGray(gray, width, height, contourRadius, 1);
308
+ const processedImageData = ctx.createImageData(width, height);
309
+ const dst = processedImageData.data;
310
+ for (let i = 0; i < totalPixels; i++) {
311
+ const px = i * 4;
312
+ dst[px] = contourGray[i] ?? 0;
313
+ dst[px + 1] = bigBlurGray[i] ?? 0;
314
+ dst[px + 2] = innerBlurGray[i] ?? 0;
315
+ dst[px + 3] = 255;
282
316
  }
283
317
  ctx.putImageData(processedImageData, 0, 0);
284
318
  canvas.toBlob((blob) => {
@@ -295,6 +329,54 @@ function toProcessedHeatmap(file) {
295
329
  image.src = typeof file === "string" ? file : URL.createObjectURL(file);
296
330
  });
297
331
  }
332
+ function blurGray(gray, width, height, radius) {
333
+ if (radius <= 0) {
334
+ return gray.slice();
335
+ }
336
+ const out = new Uint8ClampedArray(width * height);
337
+ const integral = new Uint32Array(width * height);
338
+ for (let y = 0; y < height; y++) {
339
+ let rowSum = 0;
340
+ for (let x = 0; x < width; x++) {
341
+ const idx = y * width + x;
342
+ const v = gray[idx] ?? 0;
343
+ rowSum += v;
344
+ integral[idx] = rowSum + (y > 0 ? integral[idx - width] ?? 0 : 0);
345
+ }
346
+ }
347
+ for (let y = 0; y < height; y++) {
348
+ const y1 = Math.max(0, y - radius);
349
+ const y2 = Math.min(height - 1, y + radius);
350
+ for (let x = 0; x < width; x++) {
351
+ const x1 = Math.max(0, x - radius);
352
+ const x2 = Math.min(width - 1, x + radius);
353
+ const idxA = y2 * width + x2;
354
+ const idxB = y2 * width + (x1 - 1);
355
+ const idxC = (y1 - 1) * width + x2;
356
+ const idxD = (y1 - 1) * width + (x1 - 1);
357
+ const A = integral[idxA] ?? 0;
358
+ const B = x1 > 0 ? integral[idxB] ?? 0 : 0;
359
+ const C = y1 > 0 ? integral[idxC] ?? 0 : 0;
360
+ const D = x1 > 0 && y1 > 0 ? integral[idxD] ?? 0 : 0;
361
+ const sum = A - B - C + D;
362
+ const area = (x2 - x1 + 1) * (y2 - y1 + 1);
363
+ out[y * width + x] = Math.round(sum / area);
364
+ }
365
+ }
366
+ return out;
367
+ }
368
+ function multiPassBlurGray(gray, width, height, radius, passes) {
369
+ if (radius <= 0 || passes <= 1) {
370
+ return blurGray(gray, width, height, radius);
371
+ }
372
+ let input = gray;
373
+ let tmp = gray;
374
+ for (let p = 0; p < passes; p++) {
375
+ tmp = blurGray(input, width, height, radius);
376
+ input = tmp;
377
+ }
378
+ return tmp;
379
+ }
298
380
  export {
299
381
  heatmapFragmentShader,
300
382
  heatmapMeta,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/shaders/heatmap.ts"],
4
- "sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport type { ShaderSizingParams, ShaderSizingUniforms } from '../shader-sizing.js';\n\nexport const heatmapMeta = {\n maxColorCount: 10,\n} as const;\n\n// language=GLSL\nexport const heatmapFragmentShader: string = `#version 300 es\nprecision highp float;\n\nin mediump vec2 v_imageUV;\nin mediump vec2 v_objectUV;\nout vec4 fragColor;\n\nuniform sampler2D u_image;\nuniform float u_time;\nuniform mediump float u_imageAspectRatio;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colors[${heatmapMeta.maxColorCount}];\nuniform float u_colorsCount;\n\nuniform float u_angle;\nuniform float u_noise;\nuniform float u_innerGlow;\nuniform float u_outerGlow;\nuniform float u_contour;\n\n#define TWO_PI 6.28318530718\n#define PI 3.14159265358979323846\n\nfloat getImgFrame(vec2 uv, float th) {\n float frame = 1.;\n frame *= smoothstep(0., th, uv.y);\n frame *= 1. - smoothstep(1. - th, 1., uv.y);\n frame *= smoothstep(0., th, uv.x);\n frame *= 1. - smoothstep(1. - th, 1., uv.x);\n return frame;\n}\n\nfloat circle(vec2 uv, vec2 c, vec2 r) {\n return 1. - smoothstep(r[0], r[1], length(uv - c));\n}\n\nfloat lst(float edge0, float edge1, float x) {\n return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n}\n\nfloat sst(float edge0, float edge1, float x) {\n return smoothstep(edge0, edge1, x);\n}\n\nfloat shadowShape(vec2 uv, float t, float contour) {\n vec2 scaledUV = uv;\n\n // base shape tranjectory\n float posY = mix(-1., 2., t);\n\n // scaleX when it's moving down\n scaledUV.y -= .5;\n float mainCircleScale = sst(0., .8, posY) * lst(1.4, .9, posY);\n scaledUV *= vec2(1., 1. + 1.5 * mainCircleScale);\n scaledUV.y += .5;\n\n // base shape\n float innerR = .4;\n float outerR = 1. - .3 * (sst(.1, .2, t) * (1. - sst(.2, .5, t)));\n float s = circle(scaledUV, vec2(.5, posY - .2), vec2(innerR, outerR));\n float shapeSizing = sst(.2, .3, t) * sst(.6, .3, t);\n s = pow(s, 1.4);\n s *= 1.2;\n\n // flat gradient to take over the shadow shape\n float topFlattener = 0.;\n {\n float pos = posY - uv.y;\n float edge = 1.2;\n topFlattener = lst(-.4, 0., pos) * (1. - sst(.0, edge, pos));\n topFlattener = pow(topFlattener, 3.);\n float topFlattenerMixer = (1. - sst(.0, .3, pos));\n s = mix(topFlattener, s, topFlattenerMixer);\n }\n\n // apple right circle\n {\n float visibility = sst(.6, .7, t) * (1. - sst(.8, .9, t));\n float angle = -2. -t * TWO_PI;\n float rightCircle = circle(uv, vec2(.95 - .2 * cos(angle), .4 - .1 * sin(angle)), vec2(.15, .3));\n rightCircle *= visibility;\n s = mix(s, 0., rightCircle);\n }\n\n // apple top circle\n {\n float topCircle = circle(uv, vec2(.5, .19), vec2(.05, .25));\n topCircle += 2. * contour * circle(uv, vec2(.5, .19), vec2(.2, .5));\n float visibility = .55 * sst(.2, .3, t) * (1. - sst(.3, .45, t));\n topCircle *= visibility;\n s = mix(s, 0., topCircle);\n }\n\n float leafMask = circle(uv, vec2(.53, .13), vec2(.08, .19));\n leafMask = mix(leafMask, 0., 1. - sst(.4, .54, uv.x));\n leafMask = mix(0., leafMask, sst(.0, .2, uv.y));\n leafMask *= (sst(.5, 1.1, posY) * sst(1.5, 1.3, posY));\n s += leafMask;\n\n // apple bottom circle\n {\n float visibility = sst(.0, .4, t) * (1. - sst(.6, .8, t));\n s = mix(s, 0., visibility * circle(uv, vec2(.52, .92), vec2(.09, .25)));\n }\n\n // random balls that are invisible if apple logo is selected\n {\n float pos = sst(.0, .6, t) * (1. - sst(.6, 1., t));\n s = mix(s, .5, circle(uv, vec2(.0, 1.2 - .5 * pos), vec2(.1, .3)));\n s = mix(s, .0, circle(uv, vec2(1., .5 + .5 * pos), vec2(.1, .3)));\n\n s = mix(s, 1., circle(uv, vec2(.95, .2 + .2 * sst(.3, .4, t) * sst(.7, .5, t)), vec2(.07, .22)));\n s = mix(s, 1., circle(uv, vec2(.95, .2 + .2 * sst(.3, .4, t) * (1. - sst(.5, .7, t))), vec2(.07, .22)));\n s /= max(1e-4, sst(1., .85, uv.y));\n }\n\n s = clamp(0., 1., s);\n return s;\n}\n\n\nvoid main() {\n vec2 uv = v_objectUV + .5;\n uv.y = 1. - uv.y;\n\n vec2 imgUV = v_imageUV;\n imgUV -= .5;\n imgUV *= 0.5714285714285714;\n imgUV += .5;\n float imgSoftFrame = getImgFrame(imgUV, .03);\n\n vec4 img = texture(u_image, imgUV);\n if (img.a == 0.) {\n fragColor = u_colorBack;\n return;\n }\n\n float t = .1 * u_time;\n t -= .3;\n\n float tCopy = t + 1. / 3.;\n float tCopy2 = t + 2. / 3.;\n\n t = mod(t, 1.);\n tCopy = mod(tCopy, 1.);\n tCopy2 = mod(tCopy2, 1.);\n\n vec2 animationUV = imgUV - vec2(.5);\n float angle = -u_angle * PI / 180.;\n float cosA = cos(angle);\n float sinA = sin(angle);\n animationUV = vec2(\n animationUV.x * cosA - animationUV.y * sinA,\n animationUV.x * sinA + animationUV.y * cosA\n ) + vec2(.5);\n\n float shape = img[0];\n float outerBlur = 1. - mix(1., img[1], shape);\n float innerBlur = mix(img[1], 0., shape);\n float contour = mix(img[2], 0., shape);\n\n outerBlur *= imgSoftFrame;\n\n float shadow = shadowShape(animationUV, t, innerBlur);\n float shadowCopy = shadowShape(animationUV, tCopy, innerBlur);\n float shadowCopy2 = shadowShape(animationUV, tCopy2, innerBlur);\n\n float inner = .8 + .8 * innerBlur;\n inner = mix(inner, 0., shadow);\n inner = mix(inner, 0., shadowCopy);\n inner = mix(inner, 0., shadowCopy2);\n\n inner *= mix(0., 2., u_innerGlow);\n\n inner += (u_contour * 2.) * contour;\n inner = min(1., inner);\n inner *= (1. - shape);\n\n float outer = 0.;\n {\n t *= 3.;\n t = mod(t - .1, 1.);\n\n outer = .9 * pow(outerBlur, .8);\n float y = mod(animationUV.y - t, 1.);\n float animatedMask = sst(.3, .65, y) * (1. - sst(.65, 1., y));\n animatedMask = .5 + animatedMask;\n outer *= animatedMask;\n outer *= mix(0., 5., pow(u_outerGlow, 2.));\n outer *= imgSoftFrame;\n }\n\n inner = pow(inner, 1.2);\n float heat = clamp(inner + outer, 0., 1.);\n\n heat += (.005 + .35 * u_noise) * (fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453123) - .5);\n\n float mixer = heat * u_colorsCount;\n vec4 gradient = u_colors[0];\n gradient.rgb *= gradient.a;\n float outerShape = 0.;\n for (int i = 1; i < ${heatmapMeta.maxColorCount + 1}; i++) {\n if (i > int(u_colorsCount)) break;\n float m = clamp(mixer - float(i - 1), 0., 1.);\n if (i == 1) {\n outerShape = m;\n }\n vec4 c = u_colors[i - 1];\n c.rgb *= c.a;\n gradient = mix(gradient, c, m);\n }\n\n vec3 color = gradient.rgb * outerShape;\n float opacity = gradient.a * outerShape;\n\n vec3 bgColor = u_colorBack.rgb * u_colorBack.a;\n color = color + bgColor * (1.0 - opacity);\n opacity = opacity + u_colorBack.a * (1.0 - opacity);\n\n color += .02 * (fract(sin(dot(uv + 1., vec2(12.9898, 78.233))) * 43758.5453123) - .5);\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport function toProcessedHeatmap(file: File | string): Promise<{ blob: Blob }> {\n const canvas = document.createElement('canvas');\n const canvasSize = 1000;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.crossOrigin = 'anonymous';\n\n image.addEventListener('load', () => {\n if (typeof file === 'string' ? file.endsWith('.svg') : file.type === 'image/svg+xml') {\n // Force SVG to load at a high fidelity size if it's an SVG\n image.width = canvasSize;\n image.height = canvasSize;\n }\n\n const ratio = image.naturalWidth / image.naturalHeight;\n\n const maxBlur = Math.floor(canvasSize * 0.15);\n const padding = Math.ceil(maxBlur * 2.5);\n let imgWidth = canvasSize;\n let imgHeight = canvasSize;\n if (ratio > 1) {\n imgHeight = Math.floor(canvasSize / ratio);\n } else {\n imgWidth = Math.floor(canvasSize * ratio);\n }\n\n canvas.width = imgWidth + 2 * padding;\n canvas.height = imgHeight + 2 * padding;\n\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n if (!ctx) {\n throw new Error('Failed to get canvas 2d context');\n }\n\n ctx.fillStyle = 'white';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n ctx.filter = 'grayscale(100%) blur(' + maxBlur + 'px)';\n ctx.drawImage(image, padding, padding, imgWidth, imgHeight);\n const bigBlurData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;\n\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n ctx.filter = 'grayscale(100%) blur(' + Math.round(0.12 * maxBlur) + 'px)';\n ctx.drawImage(image, padding, padding, imgWidth, imgHeight);\n const innerBlurSmallData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;\n\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n ctx.filter = 'grayscale(100%) blur(5px)';\n ctx.drawImage(image, padding, padding, imgWidth, imgHeight);\n const contourData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;\n\n let processedImageData = ctx.createImageData(canvas.width, canvas.height);\n const totalPixels = canvas.width * canvas.height;\n for (let i = 0; i < totalPixels; i++) {\n const px = i * 4;\n processedImageData.data[px] = contourData[px]!;\n processedImageData.data[px + 1] = bigBlurData[px]!;\n processedImageData.data[px + 2] = innerBlurSmallData[px]!;\n processedImageData.data[px + 3] = 255;\n }\n ctx.putImageData(processedImageData, 0, 0);\n\n canvas.toBlob((blob) => {\n if (!blob) {\n reject(new Error('Failed to create PNG blob'));\n return;\n }\n\n resolve({ blob });\n }, 'image/png');\n });\n\n image.addEventListener('error', () => {\n reject(new Error('Failed to load image'));\n });\n\n image.src = typeof file === 'string' ? file : URL.createObjectURL(file);\n });\n}\n\nexport interface HeatmapUniforms extends ShaderSizingUniforms {\n u_image: HTMLImageElement | string;\n u_contour: number;\n u_angle: number;\n u_noise: number;\n u_innerGlow: number;\n u_outerGlow: number;\n u_colorBack: [number, number, number, number];\n u_colors: vec4[];\n u_colorsCount: number;\n}\n\nexport interface HeatmapParams extends ShaderSizingParams, ShaderMotionParams {\n image: HTMLImageElement | string;\n contour?: number;\n angle?: number;\n noise?: number;\n innerGlow?: number;\n outerGlow?: number;\n colorBack?: string;\n colors?: string[];\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport type { ShaderSizingParams, ShaderSizingUniforms } from '../shader-sizing.js';\n\nexport const heatmapMeta = {\n maxColorCount: 10,\n} as const;\n\n/**\n * A glowing gradient of colors flowing through an input shape.\n * The effect creates a smoothly animated wave of intensity across the image.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_image (sampler2D): Pre-processed source image texture (R = contour, G = outer blur, B = inner blur)\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colors (vec4[]): Up to 10 heatmap colors in RGBA\n * - u_colorsCount (float): Number of active colors\n * - u_contour (float): Heat intensity near the edges of the input shape (0 to 1)\n * - u_angle (float): Direction of the heatwaves in degrees (0 to 360)\n * - u_noise (float): Grain applied across the entire graphic (0 to 1)\n * - u_innerGlow (float): Size of the heated area inside the input shape (0 to 1)\n * - u_outerGlow (float): Size of the heated area outside the input shape (0 to 1)\n *\n * Vertex shader outputs (used in fragment shader):\n * - v_imageUV (vec2): UV coordinates for sampling the source image, with fit, scale, rotation, and offset applied\n * - v_objectUV (vec2): Object box UV coordinates with global sizing (scale, rotation, offsets, etc) applied\n *\n * Vertex shader uniforms:\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_pixelRatio (float): Device pixel ratio\n * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)\n * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)\n * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas\n * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas\n * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)\n * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)\n * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)\n * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)\n * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n *\n */\n\n// language=GLSL\nexport const heatmapFragmentShader: string = `#version 300 es\nprecision highp float;\n\nin mediump vec2 v_imageUV;\nin mediump vec2 v_objectUV;\nout vec4 fragColor;\n\nuniform sampler2D u_image;\nuniform float u_time;\nuniform mediump float u_imageAspectRatio;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colors[${ heatmapMeta.maxColorCount }];\nuniform float u_colorsCount;\n\nuniform float u_angle;\nuniform float u_noise;\nuniform float u_innerGlow;\nuniform float u_outerGlow;\nuniform float u_contour;\n\n#define TWO_PI 6.28318530718\n#define PI 3.14159265358979323846\n\nfloat getImgFrame(vec2 uv, float th) {\n float frame = 1.;\n frame *= smoothstep(0., th, uv.y);\n frame *= 1. - smoothstep(1. - th, 1., uv.y);\n frame *= smoothstep(0., th, uv.x);\n frame *= 1. - smoothstep(1. - th, 1., uv.x);\n return frame;\n}\n\nfloat circle(vec2 uv, vec2 c, vec2 r) {\n return 1. - smoothstep(r[0], r[1], length(uv - c));\n}\n\nfloat lst(float edge0, float edge1, float x) {\n return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n}\n\nfloat sst(float edge0, float edge1, float x) {\n return smoothstep(edge0, edge1, x);\n}\n\nfloat shadowShape(vec2 uv, float t, float contour) {\n vec2 scaledUV = uv;\n\n // base shape tranjectory\n float posY = mix(-1., 2., t);\n\n // scaleX when it's moving down\n scaledUV.y -= .5;\n float mainCircleScale = sst(0., .8, posY) * lst(1.4, .9, posY);\n scaledUV *= vec2(1., 1. + 1.5 * mainCircleScale);\n scaledUV.y += .5;\n\n // base shape\n float innerR = .4;\n float outerR = 1. - .3 * (sst(.1, .2, t) * (1. - sst(.2, .5, t)));\n float s = circle(scaledUV, vec2(.5, posY - .2), vec2(innerR, outerR));\n float shapeSizing = sst(.2, .3, t) * sst(.6, .3, t);\n s = pow(s, 1.4);\n s *= 1.2;\n\n // flat gradient to take over the shadow shape\n float topFlattener = 0.;\n {\n float pos = posY - uv.y;\n float edge = 1.2;\n topFlattener = lst(-.4, 0., pos) * (1. - sst(.0, edge, pos));\n topFlattener = pow(topFlattener, 3.);\n float topFlattenerMixer = (1. - sst(.0, .3, pos));\n s = mix(topFlattener, s, topFlattenerMixer);\n }\n\n // apple right circle\n {\n float visibility = sst(.6, .7, t) * (1. - sst(.8, .9, t));\n float angle = -2. -t * TWO_PI;\n float rightCircle = circle(uv, vec2(.95 - .2 * cos(angle), .4 - .1 * sin(angle)), vec2(.15, .3));\n rightCircle *= visibility;\n s = mix(s, 0., rightCircle);\n }\n\n // apple top circle\n {\n float topCircle = circle(uv, vec2(.5, .19), vec2(.05, .25));\n topCircle += 2. * contour * circle(uv, vec2(.5, .19), vec2(.2, .5));\n float visibility = .55 * sst(.2, .3, t) * (1. - sst(.3, .45, t));\n topCircle *= visibility;\n s = mix(s, 0., topCircle);\n }\n\n float leafMask = circle(uv, vec2(.53, .13), vec2(.08, .19));\n leafMask = mix(leafMask, 0., 1. - sst(.4, .54, uv.x));\n leafMask = mix(0., leafMask, sst(.0, .2, uv.y));\n leafMask *= (sst(.5, 1.1, posY) * sst(1.5, 1.3, posY));\n s += leafMask;\n\n // apple bottom circle\n {\n float visibility = sst(.0, .4, t) * (1. - sst(.6, .8, t));\n s = mix(s, 0., visibility * circle(uv, vec2(.52, .92), vec2(.09, .25)));\n }\n\n // random balls that are invisible if apple logo is selected\n {\n float pos = sst(.0, .6, t) * (1. - sst(.6, 1., t));\n s = mix(s, .5, circle(uv, vec2(.0, 1.2 - .5 * pos), vec2(.1, .3)));\n s = mix(s, .0, circle(uv, vec2(1., .5 + .5 * pos), vec2(.1, .3)));\n\n s = mix(s, 1., circle(uv, vec2(.95, .2 + .2 * sst(.3, .4, t) * sst(.7, .5, t)), vec2(.07, .22)));\n s = mix(s, 1., circle(uv, vec2(.95, .2 + .2 * sst(.3, .4, t) * (1. - sst(.5, .7, t))), vec2(.07, .22)));\n s /= max(1e-4, sst(1., .85, uv.y));\n }\n\n s = clamp(0., 1., s);\n return s;\n}\n\nfloat blurEdge3x3(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius, float centerSample) {\n vec2 texel = 1.0 / vec2(textureSize(tex, 0));\n vec2 r = radius * texel;\n\n float w1 = 1.0, w2 = 2.0, w4 = 4.0;\n float norm = 16.0;\n float sum = w4 * centerSample;\n\n sum += w2 * textureGrad(tex, uv + vec2(0.0, -r.y), dudx, dudy).g;\n sum += w2 * textureGrad(tex, uv + vec2(0.0, r.y), dudx, dudy).g;\n sum += w2 * textureGrad(tex, uv + vec2(-r.x, 0.0), dudx, dudy).g;\n sum += w2 * textureGrad(tex, uv + vec2(r.x, 0.0), dudx, dudy).g;\n\n sum += w1 * textureGrad(tex, uv + vec2(-r.x, -r.y), dudx, dudy).g;\n sum += w1 * textureGrad(tex, uv + vec2(r.x, -r.y), dudx, dudy).g;\n sum += w1 * textureGrad(tex, uv + vec2(-r.x, r.y), dudx, dudy).g;\n sum += w1 * textureGrad(tex, uv + vec2(r.x, r.y), dudx, dudy).g;\n\n return sum / norm;\n}\n\nvoid main() {\n vec2 uv = v_objectUV + .5;\n uv.y = 1. - uv.y;\n\n vec2 imgUV = v_imageUV;\n imgUV -= .5;\n imgUV *= 0.5714285714285714;\n imgUV += .5;\n float imgSoftFrame = getImgFrame(imgUV, .03);\n\n vec4 img = texture(u_image, imgUV);\n vec2 dudx = dFdx(imgUV);\n vec2 dudy = dFdy(imgUV);\n\n if (img.a == 0.) {\n fragColor = u_colorBack;\n return;\n }\n\n float t = .1 * u_time;\n t -= .3;\n\n float tCopy = t + 1. / 3.;\n float tCopy2 = t + 2. / 3.;\n\n t = mod(t, 1.);\n tCopy = mod(tCopy, 1.);\n tCopy2 = mod(tCopy2, 1.);\n\n vec2 animationUV = imgUV - vec2(.5);\n float angle = -u_angle * PI / 180.;\n float cosA = cos(angle);\n float sinA = sin(angle);\n animationUV = vec2(\n animationUV.x * cosA - animationUV.y * sinA,\n animationUV.x * sinA + animationUV.y * cosA\n ) + vec2(.5);\n\n float shape = img[0];\n\n img[1] = blurEdge3x3(u_image, imgUV, dudx, dudy, 8., img[1]);\n\n float outerBlur = 1. - mix(1., img[1], shape);\n float innerBlur = mix(img[1], 0., shape);\n float contour = mix(img[2], 0., shape);\n\n outerBlur *= imgSoftFrame;\n\n float shadow = shadowShape(animationUV, t, innerBlur);\n float shadowCopy = shadowShape(animationUV, tCopy, innerBlur);\n float shadowCopy2 = shadowShape(animationUV, tCopy2, innerBlur);\n\n float inner = .8 + .8 * innerBlur;\n inner = mix(inner, 0., shadow);\n inner = mix(inner, 0., shadowCopy);\n inner = mix(inner, 0., shadowCopy2);\n\n inner *= mix(0., 2., u_innerGlow);\n\n inner += (u_contour * 2.) * contour;\n inner = min(1., inner);\n inner *= (1. - shape);\n\n float outer = 0.;\n {\n t *= 3.;\n t = mod(t - .1, 1.);\n\n outer = .9 * pow(outerBlur, .8);\n float y = mod(animationUV.y - t, 1.);\n float animatedMask = sst(.3, .65, y) * (1. - sst(.65, 1., y));\n animatedMask = .5 + animatedMask;\n outer *= animatedMask;\n outer *= mix(0., 5., pow(u_outerGlow, 2.));\n outer *= imgSoftFrame;\n }\n\n inner = pow(inner, 1.2);\n float heat = clamp(inner + outer, 0., 1.);\n\n heat += (.005 + .35 * u_noise) * (fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453123) - .5);\n\n float mixer = heat * u_colorsCount;\n vec4 gradient = u_colors[0];\n gradient.rgb *= gradient.a;\n float outerShape = 0.;\n for (int i = 1; i < ${ heatmapMeta.maxColorCount + 1 }; i++) {\n if (i > int(u_colorsCount)) break;\n float m = clamp(mixer - float(i - 1), 0., 1.);\n if (i == 1) {\n outerShape = m;\n }\n vec4 c = u_colors[i - 1];\n c.rgb *= c.a;\n gradient = mix(gradient, c, m);\n }\n\n vec3 color = gradient.rgb * outerShape;\n float opacity = gradient.a * outerShape;\n\n vec3 bgColor = u_colorBack.rgb * u_colorBack.a;\n color = color + bgColor * (1.0 - opacity);\n opacity = opacity + u_colorBack.a * (1.0 - opacity);\n\n color += .02 * (fract(sin(dot(uv + 1., vec2(12.9898, 78.233))) * 43758.5453123) - .5);\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport function toProcessedHeatmap(file: File | string): Promise<{ blob: Blob }> {\n const canvas = document.createElement('canvas');\n const canvasSize = 1000;\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.crossOrigin = 'anonymous';\n\n image.addEventListener('load', () => {\n if (typeof file === 'string' ? file.endsWith('.svg') : file.type === 'image/svg+xml') {\n // Force SVG to load at a high fidelity size if it's an SVG\n image.width = canvasSize;\n image.height = canvasSize;\n }\n\n const ratio = image.naturalWidth / image.naturalHeight;\n\n const maxBlur = Math.floor(canvasSize * 0.15);\n const padding = Math.ceil(maxBlur * 2.5);\n let imgWidth = canvasSize;\n let imgHeight = canvasSize;\n if (ratio > 1) {\n imgHeight = Math.floor(canvasSize / ratio);\n } else {\n imgWidth = Math.floor(canvasSize * ratio);\n }\n\n canvas.width = imgWidth + 2 * padding;\n canvas.height = imgHeight + 2 * padding;\n\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n if (!ctx) {\n throw new Error('Failed to get canvas 2d context');\n }\n\n // 1) Draw original image once, no filters\n ctx.fillStyle = 'white';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(image, padding, padding, imgWidth, imgHeight);\n\n const { width, height } = canvas;\n const srcImageData = ctx.getImageData(0, 0, width, height);\n const src = srcImageData.data; // RGBA\n\n // 2) Build grayscale array (luma)\n const totalPixels = width * height;\n const gray = new Uint8ClampedArray(totalPixels);\n for (let i = 0; i < totalPixels; i++) {\n const px = i * 4;\n const r = src[px] ?? 0;\n const g = src[px + 1] ?? 0;\n const b = src[px + 2] ?? 0;\n // Standard luma conversion\n gray[i] = (0.299 * r + 0.587 * g + 0.114 * b) | 0;\n }\n\n // 3) Blur grayscale for each \"filter\" you previously used\n const bigBlurRadius = maxBlur;\n const innerBlurRadius = Math.max(1, Math.round(0.12 * maxBlur));\n const contourRadius = 5;\n\n const bigBlurGray = multiPassBlurGray(gray, width, height, bigBlurRadius, 3);\n const innerBlurGray = multiPassBlurGray(gray, width, height, innerBlurRadius, 3);\n const contourGray = multiPassBlurGray(gray, width, height, contourRadius, 1);\n\n // 4) Combine into final ImageData\n const processedImageData = ctx.createImageData(width, height);\n const dst = processedImageData.data;\n\n for (let i = 0; i < totalPixels; i++) {\n const px = i * 4;\n dst[px] = contourGray[i] ?? 0;\n dst[px + 1] = bigBlurGray[i] ?? 0;\n dst[px + 2] = innerBlurGray[i] ?? 0;\n dst[px + 3] = 255;\n }\n\n ctx.putImageData(processedImageData, 0, 0);\n\n canvas.toBlob((blob) => {\n if (!blob) {\n reject(new Error('Failed to create PNG blob'));\n return;\n }\n resolve({ blob });\n }, 'image/png');\n });\n\n image.addEventListener('error', () => {\n reject(new Error('Failed to load image'));\n });\n\n image.src = typeof file === 'string' ? file : URL.createObjectURL(file);\n });\n}\n\n/**\n * Fast box blur for grayscale images using an integral image.\n * gray: Uint8ClampedArray of length width * height\n * radius: blur radius in pixels\n */\nfunction blurGray(gray: Uint8ClampedArray, width: number, height: number, radius: number): Uint8ClampedArray {\n if (radius <= 0) {\n return gray.slice();\n }\n\n const out = new Uint8ClampedArray(width * height);\n const integral = new Uint32Array(width * height);\n\n // Build integral image\n for (let y = 0; y < height; y++) {\n let rowSum = 0;\n for (let x = 0; x < width; x++) {\n const idx = y * width + x;\n const v = gray[idx] ?? 0;\n rowSum += v;\n integral[idx] = rowSum + (y > 0 ? (integral[idx - width] ?? 0) : 0);\n }\n }\n\n // Blur using integral image\n for (let y = 0; y < height; y++) {\n const y1 = Math.max(0, y - radius);\n const y2 = Math.min(height - 1, y + radius);\n for (let x = 0; x < width; x++) {\n const x1 = Math.max(0, x - radius);\n const x2 = Math.min(width - 1, x + radius);\n\n const idxA = y2 * width + x2;\n const idxB = y2 * width + (x1 - 1);\n const idxC = (y1 - 1) * width + x2;\n const idxD = (y1 - 1) * width + (x1 - 1);\n\n const A = integral[idxA] ?? 0;\n const B = x1 > 0 ? (integral[idxB] ?? 0) : 0;\n const C = y1 > 0 ? (integral[idxC] ?? 0) : 0;\n const D = x1 > 0 && y1 > 0 ? (integral[idxD] ?? 0) : 0;\n\n const sum = A - B - C + D;\n const area = (x2 - x1 + 1) * (y2 - y1 + 1);\n out[y * width + x] = Math.round(sum / area);\n }\n }\n\n return out;\n}\n\nfunction multiPassBlurGray(\n gray: Uint8ClampedArray,\n width: number,\n height: number,\n radius: number,\n passes: number\n): Uint8ClampedArray {\n if (radius <= 0 || passes <= 1) {\n return blurGray(gray, width, height, radius);\n }\n\n let input = gray;\n let tmp: Uint8ClampedArray = gray;\n\n for (let p = 0; p < passes; p++) {\n tmp = blurGray(input, width, height, radius);\n input = tmp;\n }\n\n return tmp;\n}\n\nexport interface HeatmapUniforms extends ShaderSizingUniforms {\n u_image: HTMLImageElement | string;\n u_contour: number;\n u_angle: number;\n u_noise: number;\n u_innerGlow: number;\n u_outerGlow: number;\n u_colorBack: [number, number, number, number];\n u_colors: vec4[];\n u_colorsCount: number;\n}\n\nexport interface HeatmapParams extends ShaderSizingParams, ShaderMotionParams {\n image: HTMLImageElement | string;\n contour?: number;\n angle?: number;\n noise?: number;\n innerGlow?: number;\n outerGlow?: number;\n colorBack?: string;\n colors?: string[];\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,23 +1,37 @@
1
1
  import type { ShaderMotionParams } from '../shader-mount.js';
2
2
  import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';
3
3
  /**
4
- * Dithering effect over user texture using 3-color palette
5
- * or colors sampled from original image
4
+ * A dithering image filter with support for 4 dithering modes and multiple color palettes
5
+ * (2-color, 3-color, and multicolor options, using either predefined colors or colors sampled
6
+ * from the original image).
6
7
  *
7
- * Uniforms:
8
- * - u_colorBack, u_colorFront, u_colorHighlight (RGBA)
9
- * (u_colorHighlight to be the lightest parts of u_colorFront pixels)
10
- * - pxSize: px size set relative to canvas resolution
11
- * - u_type (float used as integer)
12
- * ---- 1: random dithering
13
- * ---- 2: 2x2 Bayer matrix
14
- * ---- 3: 4x4 Bayer matrix
15
- * ---- 4: 8x8 Bayer matrix
16
- * - u_originalColors - switching between 3 colors palette and original image colors
17
- * - u_colorSteps - number of colors to use (applies to both color modes)
8
+ * SIZING NOTE: This shader performs sizing in the fragment shader (not vertex shader) to keep
9
+ * u_pxSize in consistent actual pixels. The pixel grid is computed from gl_FragCoord before any
10
+ * transforms, so scaling/rotating only affects the underlying image.
11
+ * No vertex shader outputs (v_imageUV, v_objectUV, etc.) are used.
12
+ *
13
+ * Fragment shader uniforms:
14
+ * - u_resolution (vec2): Canvas resolution in pixels
15
+ * - u_pixelRatio (float): Device pixel ratio
16
+ * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
17
+ * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
18
+ * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
19
+ * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
20
+ * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
21
+ * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
22
+ * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
23
+ * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
24
+ * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
25
+ * - u_image (sampler2D): Source image texture
26
+ * - u_imageAspectRatio (float): Aspect ratio of the source image
27
+ * - u_colorFront (vec4): Foreground color in RGBA
28
+ * - u_colorBack (vec4): Background color in RGBA
29
+ * - u_colorHighlight (vec4): Secondary foreground color in RGBA (set same as colorFront for classic 2-color dithering)
30
+ * - u_originalColors (bool): Use the original colors of the image instead of the color palette
31
+ * - u_type (float): Dithering type (1 = random, 2 = 2x2 Bayer, 3 = 4x4 Bayer, 4 = 8x8 Bayer)
32
+ * - u_pxSize (float): Pixel size of dithering grid (0.5 to 20)
33
+ * - u_colorSteps (float): Number of colors to use, applies to both color modes (1 to 7)
18
34
  *
19
- * Note: pixelization is applied to the shapes BEFORE dithering,
20
- * meaning pixels don't react to scaling and fit
21
35
  */
22
36
  export declare const imageDitheringFragmentShader: string;
23
37
  export interface ImageDitheringUniforms extends ShaderSizingUniforms {