@paper-design/shaders 0.0.80 → 0.0.82

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 (85) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +7 -2
  3. package/dist/index.js.map +2 -2
  4. package/dist/shader-mount.d.ts +2 -0
  5. package/dist/shader-mount.js +16 -5
  6. package/dist/shader-mount.js.map +2 -2
  7. package/dist/shaders/color-panels.d.ts +1 -1
  8. package/dist/shaders/color-panels.js +1 -1
  9. package/dist/shaders/color-panels.js.map +1 -1
  10. package/dist/shaders/dot-grid.d.ts +2 -2
  11. package/dist/shaders/dot-grid.js.map +1 -1
  12. package/dist/shaders/dot-orbit.d.ts +2 -2
  13. package/dist/shaders/dot-orbit.js.map +1 -1
  14. package/dist/shaders/fluted-glass.d.ts +6 -8
  15. package/dist/shaders/fluted-glass.js +18 -15
  16. package/dist/shaders/fluted-glass.js.map +2 -2
  17. package/dist/shaders/gem-smoke.d.ts +8 -6
  18. package/dist/shaders/gem-smoke.js +5 -17
  19. package/dist/shaders/gem-smoke.js.map +2 -2
  20. package/dist/shaders/god-rays.d.ts +3 -3
  21. package/dist/shaders/god-rays.js.map +1 -1
  22. package/dist/shaders/grain-gradient.d.ts +0 -2
  23. package/dist/shaders/grain-gradient.js +0 -2
  24. package/dist/shaders/grain-gradient.js.map +2 -2
  25. package/dist/shaders/halftone-cmyk.d.ts +11 -12
  26. package/dist/shaders/halftone-cmyk.js +5 -2
  27. package/dist/shaders/halftone-cmyk.js.map +2 -2
  28. package/dist/shaders/halftone-dots.d.ts +5 -7
  29. package/dist/shaders/halftone-dots.js +17 -17
  30. package/dist/shaders/halftone-dots.js.map +2 -2
  31. package/dist/shaders/heatmap.d.ts +2 -3
  32. package/dist/shaders/heatmap.js +0 -1
  33. package/dist/shaders/heatmap.js.map +2 -2
  34. package/dist/shaders/image-dithering.d.ts +5 -7
  35. package/dist/shaders/image-dithering.js +0 -2
  36. package/dist/shaders/image-dithering.js.map +2 -2
  37. package/dist/shaders/lens-distortion.d.ts +15 -15
  38. package/dist/shaders/lens-distortion.js.map +1 -1
  39. package/dist/shaders/liquid-metal.d.ts +2 -3
  40. package/dist/shaders/liquid-metal.js +4 -1
  41. package/dist/shaders/liquid-metal.js.map +2 -2
  42. package/dist/shaders/mesh-gradient.js +15 -11
  43. package/dist/shaders/mesh-gradient.js.map +2 -2
  44. package/dist/shaders/metaballs.d.ts +2 -2
  45. package/dist/shaders/metaballs.js +0 -1
  46. package/dist/shaders/metaballs.js.map +2 -2
  47. package/dist/shaders/neuro-noise.d.ts +1 -3
  48. package/dist/shaders/neuro-noise.js +0 -2
  49. package/dist/shaders/neuro-noise.js.map +2 -2
  50. package/dist/shaders/paper-texture.d.ts +65 -34
  51. package/dist/shaders/paper-texture.js +443 -164
  52. package/dist/shaders/paper-texture.js.map +2 -2
  53. package/dist/shaders/perlin-noise.d.ts +3 -3
  54. package/dist/shaders/perlin-noise.js +0 -6
  55. package/dist/shaders/perlin-noise.js.map +2 -2
  56. package/dist/shaders/pulsing-border.d.ts +2 -2
  57. package/dist/shaders/pulsing-border.js.map +1 -1
  58. package/dist/shaders/simplex-noise.d.ts +2 -3
  59. package/dist/shaders/simplex-noise.js +11 -19
  60. package/dist/shaders/simplex-noise.js.map +2 -2
  61. package/dist/shaders/smoke-ring.d.ts +1 -1
  62. package/dist/shaders/smoke-ring.js.map +1 -1
  63. package/dist/shaders/spiral.d.ts +3 -3
  64. package/dist/shaders/spiral.js +3 -1
  65. package/dist/shaders/spiral.js.map +2 -2
  66. package/dist/shaders/static-mesh-gradient.d.ts +2 -2
  67. package/dist/shaders/static-mesh-gradient.js +15 -12
  68. package/dist/shaders/static-mesh-gradient.js.map +2 -2
  69. package/dist/shaders/static-radial-gradient.d.ts +4 -4
  70. package/dist/shaders/static-radial-gradient.js +15 -11
  71. package/dist/shaders/static-radial-gradient.js.map +2 -2
  72. package/dist/shaders/swirl.d.ts +3 -3
  73. package/dist/shaders/swirl.js +3 -1
  74. package/dist/shaders/swirl.js.map +2 -2
  75. package/dist/shaders/voronoi.d.ts +3 -3
  76. package/dist/shaders/voronoi.js.map +1 -1
  77. package/dist/shaders/warp.d.ts +2 -4
  78. package/dist/shaders/warp.js +0 -2
  79. package/dist/shaders/warp.js.map +2 -2
  80. package/dist/shaders/water.d.ts +6 -6
  81. package/dist/shaders/water.js +4 -2
  82. package/dist/shaders/water.js.map +2 -2
  83. package/dist/shaders/waves.d.ts +3 -3
  84. package/dist/shaders/waves.js.map +1 -1
  85. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/shaders/image-dithering.ts"],
4
- "sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { proceduralHash21, declarePI } from '../shader-utils.js';\n\n/**\n * A dithering image filter with support for 4 dithering modes and multiple color palettes\n * (2-color, 3-color, and multicolor options, using either predefined colors or colors sampled\n * from the original image).\n *\n * SIZING NOTE: This shader performs sizing in the fragment shader (not vertex shader) to keep\n * u_pxSize in consistent actual pixels. The pixel grid is computed from gl_FragCoord before any\n * transforms, so scaling/rotating only affects the underlying image.\n * No vertex shader outputs (v_imageUV, v_objectUV, etc.) are used.\n *\n * Fragment 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_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_colorHighlight (vec4): Secondary foreground color in RGBA (set same as colorFront for classic 2-color dithering)\n * - u_originalColors (bool): Use the original colors of the image instead of the color palette\n * - u_inverted (bool): Inverts the image luminance, doesn't affect the color scheme; not effective at zero contrast\n * - u_type (float): Dithering type (1 = random, 2 = 2x2 Bayer, 3 = 4x4 Bayer, 4 = 8x8 Bayer)\n * - u_pxSize (float): Pixel size of dithering grid (0.5 to 20)\n * - u_colorSteps (float): Number of colors to use, applies to both color modes (1 to 7)\n *\n */\n\n// language=GLSL\nexport const imageDitheringFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\nuniform float u_originX;\nuniform float u_originY;\nuniform float u_worldWidth;\nuniform float u_worldHeight;\nuniform float u_fit;\n\nuniform float u_scale;\nuniform float u_rotation;\nuniform float u_offsetX;\nuniform float u_offsetY;\n\nuniform vec4 u_colorFront;\nuniform vec4 u_colorBack;\nuniform vec4 u_colorHighlight;\n\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\n\nuniform float u_type;\nuniform float u_pxSize;\nuniform bool u_originalColors;\nuniform bool u_inverted;\nuniform float u_colorSteps;\n\nout vec4 fragColor;\n\n\n${ proceduralHash21 }\n${ declarePI }\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\nvec2 getImageUV(vec2 uv) {\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.) { // contain\n imageBoxSize.x = min(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;\n } else if (u_fit == 2.) { // cover\n imageBoxSize.x = max(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;\n } else {\n imageBoxSize.x = min(10.0, 10.0 / u_imageAspectRatio * 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.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\nconst int bayer2x2[4] = int[4](0, 2, 3, 1);\nconst int bayer4x4[16] = int[16](\n0, 8, 2, 10,\n12, 4, 14, 6,\n3, 11, 1, 9,\n15, 7, 13, 5\n);\n\nconst int bayer8x8[64] = int[64](\n0, 32, 8, 40, 2, 34, 10, 42,\n48, 16, 56, 24, 50, 18, 58, 26,\n12, 44, 4, 36, 14, 46, 6, 38,\n60, 28, 52, 20, 62, 30, 54, 22,\n3, 35, 11, 43, 1, 33, 9, 41,\n51, 19, 59, 27, 49, 17, 57, 25,\n15, 47, 7, 39, 13, 45, 5, 37,\n63, 31, 55, 23, 61, 29, 53, 21\n);\n\nfloat getBayerValue(vec2 uv, int size) {\n ivec2 pos = ivec2(fract(uv / float(size)) * float(size));\n int index = pos.y * size + pos.x;\n\n if (size == 2) {\n return float(bayer2x2[index]) / 4.0;\n } else if (size == 4) {\n return float(bayer4x4[index]) / 16.0;\n } else if (size == 8) {\n return float(bayer8x8[index]) / 64.0;\n }\n return 0.0;\n}\n\n\nvoid main() {\n\n float pxSize = u_pxSize * u_pixelRatio;\n vec2 pxSizeUV = gl_FragCoord.xy - .5 * u_resolution;\n pxSizeUV /= pxSize;\n vec2 canvasPixelizedUV = (floor(pxSizeUV) + .5) * pxSize;\n vec2 normalizedUV = canvasPixelizedUV / u_resolution;\n\n vec2 imageUV = getImageUV(normalizedUV);\n vec2 ditheringNoiseUV = canvasPixelizedUV;\n vec4 image = texture(u_image, imageUV);\n float frame = getUvFrame(imageUV, pxSize / u_resolution);\n\n int type = int(floor(u_type));\n float dithering = 0.0;\n\n float lum = dot(vec3(.2126, .7152, .0722), image.rgb);\n lum = u_inverted ? (1. - lum) : lum;\n\n switch (type) {\n case 1: {\n dithering = step(hash21(ditheringNoiseUV), lum);\n } break;\n case 2:\n dithering = getBayerValue(pxSizeUV, 2);\n break;\n case 3:\n dithering = getBayerValue(pxSizeUV, 4);\n break;\n default :\n dithering = getBayerValue(pxSizeUV, 8);\n break;\n }\n\n float colorSteps = max(floor(u_colorSteps), 1.);\n vec3 color = vec3(0.0);\n float opacity = 1.;\n\n dithering -= .5;\n float brightness = clamp(lum + dithering / colorSteps, 0.0, 1.0);\n brightness = mix(0.0, brightness, frame);\n brightness = mix(0.0, brightness, image.a);\n float quantLum = floor(brightness * colorSteps + 0.5) / colorSteps;\n quantLum = mix(0.0, quantLum, frame);\n\n if (u_originalColors == true) {\n vec3 normColor = image.rgb / max(lum, 0.001);\n color = normColor * quantLum;\n\n float quantAlpha = floor(image.a * colorSteps + 0.5) / colorSteps;\n opacity = mix(quantLum, 1., quantAlpha);\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 vec3 hlColor = u_colorHighlight.rgb * u_colorHighlight.a;\n float hlOpacity = u_colorHighlight.a;\n\n fgColor = mix(fgColor, hlColor, step(1.02 - .02 * u_colorSteps, brightness));\n fgOpacity = mix(fgOpacity, hlOpacity, step(1.02 - .02 * u_colorSteps, brightness));\n\n color = fgColor * quantLum;\n opacity = fgOpacity * quantLum;\n color += bgColor * (1.0 - opacity);\n opacity += bgOpacity * (1.0 - opacity);\n }\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport interface ImageDitheringUniforms extends ShaderSizingUniforms {\n u_image: HTMLImageElement | string;\n u_colorFront: [number, number, number, number];\n u_colorBack: [number, number, number, number];\n u_colorHighlight: [number, number, number, number];\n u_type: (typeof DitheringTypes)[DitheringType];\n u_pxSize: number;\n u_colorSteps: number;\n u_originalColors: boolean;\n u_inverted: boolean;\n}\n\nexport interface ImageDitheringParams extends ShaderSizingParams, ShaderMotionParams {\n image: HTMLImageElement | string;\n colorFront?: string;\n colorBack?: string;\n colorHighlight?: string;\n type?: DitheringType;\n size?: number;\n colorSteps?: number;\n originalColors?: boolean;\n inverted?: boolean;\n}\n\nexport const DitheringTypes = {\n 'random': 1,\n '2x2': 2,\n '4x4': 3,\n '8x8': 4,\n} as const;\n\nexport type DitheringType = keyof typeof DitheringTypes;\n"],
5
- "mappings": ";;;;;AACA,eAAmE;AACnE,SAAS,kBAAkB,iBAAiB;AAsCrC,MAAM,+BAAuC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgCjD,gBAAiB;AAAA,EACjB,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+KN,MAAM,iBAAiB;AAAA,EAC5B,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;",
4
+ "sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { proceduralHash21, declarePI } from '../shader-utils.js';\n\n/**\n * A dithering image filter with support for 4 dithering modes and multiple color palettes\n * (2-color, 3-color, and multicolor options, using either predefined colors or colors sampled\n * from the original image).\n *\n * SIZING NOTE: This shader performs sizing in the fragment shader (not vertex shader) to keep\n * u_pxSize in consistent actual pixels. The pixel grid is computed from gl_FragCoord before any\n * transforms, so scaling/rotating only affects the underlying image.\n * No vertex shader outputs (v_imageUV, v_objectUV, etc.) are used.\n *\n * Fragment 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.1 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_image (sampler2D): Source image texture\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n * - u_colorFront (vec4): Foreground color in RGBA, needs originalColors off\n * - u_colorBack (vec4): Background color in RGBA, needs originalColors off\n * - u_colorHighlight (vec4): Secondary foreground color in RGBA (set same as colorFront for classic 2-color dithering), needs originalColors off\n * - u_originalColors (bool): Use the original colors of the image instead of the color palette\n * - u_inverted (bool): Inverts the image luminance, doesn't affect the color scheme\n * - u_type (float): Dithering type (1 = random, 2 = 2x2 Bayer, 3 = 4x4 Bayer, 4 = 8x8 Bayer)\n * - u_pxSize (float): Pixel size of dithering grid (0.5 to 20)\n * - u_colorSteps (float): Number of colors to use, applies to both color modes (1 to 7)\n *\n */\n\n// language=GLSL\nexport const imageDitheringFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform vec2 u_resolution;\nuniform float u_pixelRatio;\nuniform float u_originX;\nuniform float u_originY;\nuniform float u_fit;\n\nuniform float u_scale;\nuniform float u_rotation;\nuniform float u_offsetX;\nuniform float u_offsetY;\n\nuniform vec4 u_colorFront;\nuniform vec4 u_colorBack;\nuniform vec4 u_colorHighlight;\n\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\n\nuniform float u_type;\nuniform float u_pxSize;\nuniform bool u_originalColors;\nuniform bool u_inverted;\nuniform float u_colorSteps;\n\nout vec4 fragColor;\n\n\n${ proceduralHash21 }\n${ declarePI }\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\nvec2 getImageUV(vec2 uv) {\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.) { // contain\n imageBoxSize.x = min(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;\n } else if (u_fit == 2.) { // cover\n imageBoxSize.x = max(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;\n } else {\n imageBoxSize.x = min(10.0, 10.0 / u_imageAspectRatio * 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.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\nconst int bayer2x2[4] = int[4](0, 2, 3, 1);\nconst int bayer4x4[16] = int[16](\n0, 8, 2, 10,\n12, 4, 14, 6,\n3, 11, 1, 9,\n15, 7, 13, 5\n);\n\nconst int bayer8x8[64] = int[64](\n0, 32, 8, 40, 2, 34, 10, 42,\n48, 16, 56, 24, 50, 18, 58, 26,\n12, 44, 4, 36, 14, 46, 6, 38,\n60, 28, 52, 20, 62, 30, 54, 22,\n3, 35, 11, 43, 1, 33, 9, 41,\n51, 19, 59, 27, 49, 17, 57, 25,\n15, 47, 7, 39, 13, 45, 5, 37,\n63, 31, 55, 23, 61, 29, 53, 21\n);\n\nfloat getBayerValue(vec2 uv, int size) {\n ivec2 pos = ivec2(fract(uv / float(size)) * float(size));\n int index = pos.y * size + pos.x;\n\n if (size == 2) {\n return float(bayer2x2[index]) / 4.0;\n } else if (size == 4) {\n return float(bayer4x4[index]) / 16.0;\n } else if (size == 8) {\n return float(bayer8x8[index]) / 64.0;\n }\n return 0.0;\n}\n\n\nvoid main() {\n\n float pxSize = u_pxSize * u_pixelRatio;\n vec2 pxSizeUV = gl_FragCoord.xy - .5 * u_resolution;\n pxSizeUV /= pxSize;\n vec2 canvasPixelizedUV = (floor(pxSizeUV) + .5) * pxSize;\n vec2 normalizedUV = canvasPixelizedUV / u_resolution;\n\n vec2 imageUV = getImageUV(normalizedUV);\n vec2 ditheringNoiseUV = canvasPixelizedUV;\n vec4 image = texture(u_image, imageUV);\n float frame = getUvFrame(imageUV, pxSize / u_resolution);\n\n int type = int(floor(u_type));\n float dithering = 0.0;\n\n float lum = dot(vec3(.2126, .7152, .0722), image.rgb);\n lum = u_inverted ? (1. - lum) : lum;\n\n switch (type) {\n case 1: {\n dithering = step(hash21(ditheringNoiseUV), lum);\n } break;\n case 2:\n dithering = getBayerValue(pxSizeUV, 2);\n break;\n case 3:\n dithering = getBayerValue(pxSizeUV, 4);\n break;\n default :\n dithering = getBayerValue(pxSizeUV, 8);\n break;\n }\n\n float colorSteps = max(floor(u_colorSteps), 1.);\n vec3 color = vec3(0.0);\n float opacity = 1.;\n\n dithering -= .5;\n float brightness = clamp(lum + dithering / colorSteps, 0.0, 1.0);\n brightness = mix(0.0, brightness, frame);\n brightness = mix(0.0, brightness, image.a);\n float quantLum = floor(brightness * colorSteps + 0.5) / colorSteps;\n quantLum = mix(0.0, quantLum, frame);\n\n if (u_originalColors == true) {\n vec3 normColor = image.rgb / max(lum, 0.001);\n color = normColor * quantLum;\n\n float quantAlpha = floor(image.a * colorSteps + 0.5) / colorSteps;\n opacity = mix(quantLum, 1., quantAlpha);\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 vec3 hlColor = u_colorHighlight.rgb * u_colorHighlight.a;\n float hlOpacity = u_colorHighlight.a;\n\n fgColor = mix(fgColor, hlColor, step(1.02 - .02 * u_colorSteps, brightness));\n fgOpacity = mix(fgOpacity, hlOpacity, step(1.02 - .02 * u_colorSteps, brightness));\n\n color = fgColor * quantLum;\n opacity = fgOpacity * quantLum;\n color += bgColor * (1.0 - opacity);\n opacity += bgOpacity * (1.0 - opacity);\n }\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport interface ImageDitheringUniforms extends ShaderSizingUniforms {\n u_image: HTMLImageElement | string;\n u_colorFront: [number, number, number, number];\n u_colorBack: [number, number, number, number];\n u_colorHighlight: [number, number, number, number];\n u_type: (typeof DitheringTypes)[DitheringType];\n u_pxSize: number;\n u_colorSteps: number;\n u_originalColors: boolean;\n u_inverted: boolean;\n}\n\nexport interface ImageDitheringParams extends ShaderSizingParams, ShaderMotionParams {\n image: HTMLImageElement | string;\n colorFront?: string;\n colorBack?: string;\n colorHighlight?: string;\n type?: DitheringType;\n size?: number;\n colorSteps?: number;\n originalColors?: boolean;\n inverted?: boolean;\n}\n\nexport const DitheringTypes = {\n 'random': 1,\n '2x2': 2,\n '4x4': 3,\n '8x8': 4,\n} as const;\n\nexport type DitheringType = keyof typeof DitheringTypes;\n"],
5
+ "mappings": ";;;;;AACA,eAAmE;AACnE,SAAS,kBAAkB,iBAAiB;AAoCrC,MAAM,+BAAuC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BjD,gBAAiB;AAAA,EACjB,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+KN,MAAM,iBAAiB;AAAA,EAC5B,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -12,22 +12,22 @@ export declare const lensDistortionMeta: {
12
12
  * Fragment shader uniforms:
13
13
  * - u_image (sampler2D): Source image texture
14
14
  * - u_spread (float): Strength of the color split; how far the color layers are pushed apart; 0 is off (0 to 1)
15
- * - u_bias (float): Shifts the colors toward one end of the spread; 0 spaces them evenly (-1 to 1)
16
- * - u_angle (float): Direction of the spread in degrees (0 to 360)
17
- * - u_perspective (float): Shapes the spread direction from a straight line (0) to a radial burst out from the centre (1) (0 to 1)
18
- * - u_count (float): Number of sampled color layers along the spread; higher is smoother and costlier (2 to 50)
19
- * - u_dispersion (float): Overall amount of color dispersion; 1 gives each layer its own color from the spectrum, 0 keeps the original image color (0 to 1)
20
- * - u_dispersionShift (float): Balance of the dispersion between a soft circular zone at the centre and the rest of the image; 0 applies it evenly, -1 keeps it in the centre only, 1 keeps it at the edges only (-1 to 1)
21
- * - u_dispersionColor (float): Rotates the colors around the hue wheel, 0 to 1 for a full turn
22
- * - u_focusCenter (float): Reduces the spread in a circular zone at the centre; 0 keeps it full to the centre (0 to 1)
23
- * - u_focusEdges (float): Reduces the spread toward the edges; 0 keeps it full to the edges, 1 restores the original image there (0 to 1)
24
- * - u_swirl (float): Rotates the color layers around the centre by an angle growing along the spread; 0 is off (-1 to 1)
25
- * - u_noise (float): Scatters the spread direction with noise; 0 is off (0 to 1)
26
- * - u_noiseFrequency (float): Frequency of the noise, 0 to 1 mapped internally to 0 to 18; higher is finer (no effect with noise = 0)
27
- * - u_noiseOffset (float): Offsets the noise pattern for a different seed, 0 to 1 mapped internally to 0 to 30 (no effect with noise = 0)
15
+ * - u_bias (float): Shifts the colors toward one end of the spread; 0 spaces them evenly, needs spread > 0, count > 2 (-1 to 1)
16
+ * - u_angle (float): Direction of the spread in degrees, needs spread > 0, perspective < 1 (0 to 360)
17
+ * - u_perspective (float): Shapes the spread direction from a straight line (0) to a radial burst out from the centre (1), needs spread > 0 (0 to 1)
18
+ * - u_count (float): Number of sampled color layers along the spread; higher is smoother and costlier, needs spread > 0 (2 to 50)
19
+ * - u_dispersion (float): Overall amount of color dispersion; 1 gives each layer its own color from the spectrum, 0 keeps the original image color, needs spread > 0 (0 to 1)
20
+ * - u_dispersionShift (float): Balance of the dispersion between a soft circular zone at the centre and the rest of the image; 0 applies it evenly, -1 keeps it in the centre only, 1 keeps it at the edges only, needs spread > 0, dispersion > 0 (-1 to 1)
21
+ * - u_dispersionColor (float): Rotates the colors around the hue wheel, 0 to 1 for a full turn, needs spread > 0, dispersion > 0
22
+ * - u_focusCenter (float): Reduces the spread in a circular zone at the centre; 0 keeps it full to the centre, needs spread > 0 (0 to 1)
23
+ * - u_focusEdges (float): Reduces the spread toward the edges; 0 keeps it full to the edges, 1 restores the original image there, needs spread > 0 (0 to 1)
24
+ * - u_swirl (float): Rotates the color layers around the centre by an angle growing along the spread; 0 is off, needs spread > 0 (-1 to 1)
25
+ * - u_noise (float): Scatters the spread direction with noise; 0 is off, needs spread > 0 (0 to 1)
26
+ * - u_noiseFrequency (float): Frequency of the noise, 0 to 1 mapped internally to 0 to 18; higher is finer, needs spread > 0, noise > 0
27
+ * - u_noiseOffset (float): Offsets the noise pattern for a different seed, 0 to 1 mapped internally to 0 to 30, needs spread > 0, noise > 0
28
28
  * - u_lensBulge (float): Radial lens warp of the image geometry; positive bulges out like a fisheye/barrel, negative pinches in like a pincushion; strong positive values fade out the corners (-1 to 1)
29
29
  * - u_lensCircle (float): Squeezes pixels outside the inscribed circle inward so the outline becomes a circle; also turns the spread radial and damps it near the rim; 0 is off, 1 is full (0 to 1)
30
- * - u_grainMixer (float): Scatters the spread with grain noise, breaking up the edges of the color layers (0 to 1)
30
+ * - u_grainMixer (float): Scatters the spread with grain noise, breaking up the edges of the color layers, needs spread > 0 (0 to 1)
31
31
  * - u_grainOverlay (float): Post-processing black/white grain overlay (0 to 1)
32
32
  * - u_imageX (float): Pans the image horizontally behind the effect; 0 is centred (-1 to 1)
33
33
  * - u_imageY (float): Pans the image vertically behind the effect; 0 is centred (-1 to 1)
@@ -41,7 +41,7 @@ export declare const lensDistortionMeta: {
41
41
  * - u_originX (float): Reference point for positioning world width in the canvas (0 to 1)
42
42
  * - u_originY (float): Reference point for positioning world height in the canvas (0 to 1)
43
43
  * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
44
- * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
44
+ * - u_scale (float): Overall zoom level of the graphics (0.1 to 4)
45
45
  * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
46
46
  * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
47
47
  * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/shaders/lens-distortion.ts"],
4
- "sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, proceduralHash21, rotation2 } from '../shader-utils.js';\n\nexport const lensDistortionMeta = {\n maxSamples: 50,\n} as const;\n\n/**\n *\n * Lens Distortion image filter separates an image into shifting color layers\n * (recreating the chromatic aberration of a lens) and warps the image geometry,\n * curving it outward or inward like barrel and pincushion distortion.\n *\n * Fragment shader uniforms:\n * - u_image (sampler2D): Source image texture\n * - u_spread (float): Strength of the color split; how far the color layers are pushed apart; 0 is off (0 to 1)\n * - u_bias (float): Shifts the colors toward one end of the spread; 0 spaces them evenly (-1 to 1)\n * - u_angle (float): Direction of the spread in degrees (0 to 360)\n * - u_perspective (float): Shapes the spread direction from a straight line (0) to a radial burst out from the centre (1) (0 to 1)\n * - u_count (float): Number of sampled color layers along the spread; higher is smoother and costlier (2 to 50)\n * - u_dispersion (float): Overall amount of color dispersion; 1 gives each layer its own color from the spectrum, 0 keeps the original image color (0 to 1)\n * - u_dispersionShift (float): Balance of the dispersion between a soft circular zone at the centre and the rest of the image; 0 applies it evenly, -1 keeps it in the centre only, 1 keeps it at the edges only (-1 to 1)\n * - u_dispersionColor (float): Rotates the colors around the hue wheel, 0 to 1 for a full turn\n * - u_focusCenter (float): Reduces the spread in a circular zone at the centre; 0 keeps it full to the centre (0 to 1)\n * - u_focusEdges (float): Reduces the spread toward the edges; 0 keeps it full to the edges, 1 restores the original image there (0 to 1)\n * - u_swirl (float): Rotates the color layers around the centre by an angle growing along the spread; 0 is off (-1 to 1)\n * - u_noise (float): Scatters the spread direction with noise; 0 is off (0 to 1)\n * - u_noiseFrequency (float): Frequency of the noise, 0 to 1 mapped internally to 0 to 18; higher is finer (no effect with noise = 0)\n * - u_noiseOffset (float): Offsets the noise pattern for a different seed, 0 to 1 mapped internally to 0 to 30 (no effect with noise = 0)\n * - u_lensBulge (float): Radial lens warp of the image geometry; positive bulges out like a fisheye/barrel, negative pinches in like a pincushion; strong positive values fade out the corners (-1 to 1)\n * - u_lensCircle (float): Squeezes pixels outside the inscribed circle inward so the outline becomes a circle; also turns the spread radial and damps it near the rim; 0 is off, 1 is full (0 to 1)\n * - u_grainMixer (float): Scatters the spread with grain noise, breaking up the edges of the color layers (0 to 1)\n * - u_grainOverlay (float): Post-processing black/white grain overlay (0 to 1)\n * - u_imageX (float): Pans the image horizontally behind the effect; 0 is centred (-1 to 1)\n * - u_imageY (float): Pans the image vertically behind the effect; 0 is centred (-1 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 lensDistortionFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\nuniform float u_spread;\nuniform float u_bias;\nuniform float u_angle;\nuniform float u_perspective;\nuniform float u_count;\nuniform float u_dispersion;\nuniform float u_dispersionShift;\nuniform float u_dispersionColor;\nuniform float u_focusCenter;\nuniform float u_focusEdges;\nuniform float u_swirl;\nuniform float u_noise;\nuniform float u_noiseFrequency;\nuniform float u_noiseOffset;\nuniform float u_lensBulge;\nuniform float u_lensCircle;\nuniform float u_grainMixer;\nuniform float u_grainOverlay;\nuniform float u_imageX;\nuniform float u_imageY;\n\nin vec2 v_imageUV;\n\nout vec4 fragColor;\n\n${declarePI}\n${proceduralHash21}\n${rotation2}\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.));\n float c = hash21(i + vec2(0., 1.));\n float d = hash21(i + vec2(1., 1.));\n vec2 u = f * f * (3. - 2. * f);\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat getUvFrame(vec2 uv) {\n vec2 invAA = 1. / clamp(fwidth(uv), 1e-5, .02);\n vec2 lo = clamp(uv * invAA + .5, 0., 1.);\n vec2 hi = clamp((1. - uv) * invAA + .5, 0., 1.);\n return lo.x * hi.x * lo.y * hi.y;\n}\n\nvec4 sampleOverWhite(vec2 uv) {\n vec4 img = texture(u_image, uv);\n float cover = img.a * getUvFrame(uv);\n vec3 colorOverWhite = mix(vec3(1.), img.rgb, cover);\n return vec4(colorOverWhite, cover);\n}\n\nvec3 hueColor(float hue) {\n vec3 rgb = clamp(abs(mod(hue * 6. + vec3(0., 4., 2.), 6.) - 3.) - 1., 0., 1.);\n rgb = rgb * rgb * (3. - 2. * rgb);\n return rgb;\n}\n \nfloat boxInradius() {\n return .5 * min(u_imageAspectRatio, 1.);\n}\n\nfloat boxOutradius() {\n return .5 * length(vec2(u_imageAspectRatio, 1.));\n}\n\nfloat spreadReach() {\n return .7 * pow(u_spread, 1.3 + 2.7 * u_spread);\n}\n\nfloat innerCircleMask(float radius, float inradius) {\n return 1. - smoothstep(.5 * inradius, 1.1 * inradius, radius);\n}\n\nfloat dispersionCurve(float t, float biasPow) {\n float mirroredT = u_bias < 0. ? 1. - t : t;\n float curved = pow(mirroredT, biasPow);\n return u_bias < 0. ? 1. - curved : curved;\n}\n\nvec2 getSpread(vec2 fromCenter, float radius, vec2 warpedUV, float edgeAA, float inradius, float outradius, float reach, out float outStrength) {\n float angleRad = radians(u_angle);\n vec2 uniformDir = vec2(cos(angleRad), sin(angleRad));\n\n float invInradius = 1. / inradius;\n vec2 radialDir = fromCenter * invInradius;\n float maxLen = (outradius + reach) * invInradius;\n float radialLen = radius * invInradius;\n if (radialLen > maxLen) radialDir *= maxLen / radialLen;\n vec2 spreadDir = mix(uniformDir, radialDir, u_perspective);\n\n float bandProximity = smoothstep(inradius * .8, inradius, radius);\n float lensCircleMaxing = bandProximity * u_lensCircle * u_lensCircle * u_lensCircle;\n spreadDir = mix(spreadDir, radialDir, lensCircleMaxing);\n\n vec2 warpedAbs = abs(warpedUV - .5);\n float inner = mix(1., smoothstep(0., mix(inradius, outradius, u_focusCenter), radius), u_focusCenter);\n float boxDist = max(warpedAbs.x, warpedAbs.y) * 2.;\n float outer = mix(1., 1. - min(boxDist, 1.), u_focusEdges);\n float strength = inner * outer;\n\n strength *= mix(1., mix(.15, .03, max(-u_lensBulge, 0.)), lensCircleMaxing);\n\n vec2 outside = max(warpedAbs - .5, 0.);\n float margin = max(reach * length(spreadDir) * (1. - u_lensCircle), edgeAA);\n strength *= 1. - smoothstep(0., margin, length(outside));\n\n outStrength = strength;\n vec2 axis = spreadDir * (reach * strength);\n\n if (u_noise > 0.) {\n float turn = (valueNoise(fromCenter * u_noiseFrequency * 18. + u_noiseOffset * 30.) - .5) * 2. * u_noise;\n float cs = cos(turn), sn = sin(turn);\n axis = mat2(cs, -sn, sn, cs) * axis;\n }\n\n axis.x /= u_imageAspectRatio;\n return axis;\n}\n\nvec2 lensWarp(vec2 fromCenter, float radius, float inradius, out float bulgeFade) {\n bulgeFade = 1.;\n if (u_lensBulge == 0. && u_lensCircle <= 0.) return fromCenter;\n if (radius < 1e-5) return fromCenter;\n \n float r = radius;\n\n if (u_lensBulge != 0.) {\n float rn = radius / inradius;\n float bulge = abs(u_lensBulge) * (u_lensBulge > 0. ? 1.4 : 1.2);\n if (u_lensBulge > 0.) bulgeFade = 1. - smoothstep(1.45, 1.53, rn * bulge);\n float map = u_lensBulge > 0.\n ? tan(min(rn * bulge, 1.53)) / tan(bulge)\n : atan(rn * tan(bulge)) / bulge;\n float bulgeScale = map / rn;\n fromCenter *= bulgeScale;\n r *= bulgeScale;\n }\n\n if (u_lensCircle > 0.) {\n vec2 dir = fromCenter / max(r, 1e-5);\n vec2 halfBox = vec2(u_imageAspectRatio, 1.) * .5;\n float rBox = min(halfBox.x / max(abs(dir.x), 1e-4), halfBox.y / max(abs(dir.y), 1e-4));\n float band = inradius * mix(.03, .2, .5 * (u_lensBulge + 1.));\n float innerEdge = inradius - band;\n float over = smoothstep(0., 1., (r - innerEdge) / band);\n float g = r + (rBox - inradius) * pow(over, 14.);\n fromCenter = dir * mix(r, g, u_lensCircle);\n }\n\n return fromCenter;\n}\n\nvoid main() {\n vec2 uv = v_imageUV;\n\n float invAspect = 1. / u_imageAspectRatio;\n float inradius = boxInradius();\n float outradius = boxOutradius();\n float reach = spreadReach();\n\n vec2 fromCenter = uv - .5;\n fromCenter.x *= u_imageAspectRatio;\n float radius = length(fromCenter);\n\n float bulgeFade;\n vec2 warpedFromCenter = lensWarp(fromCenter, radius, inradius, bulgeFade);\n vec2 baseUV = vec2(warpedFromCenter.x * invAspect, warpedFromCenter.y) + .5;\n\n vec2 baseDerivative = fwidth(baseUV);\n float edgeAA = clamp(2. * max(baseDerivative.x, baseDerivative.y), .001, .02);\n\n float spreadStrength;\n vec2 spreadAxis = getSpread(fromCenter, radius, baseUV, edgeAA, inradius, outradius, reach, spreadStrength);\n\n vec2 grainUV = vec2(0.);\n if (u_grainMixer > 0. || u_grainOverlay > 0.) {\n vec2 dudx = dFdx(v_imageUV);\n vec2 dudy = dFdy(v_imageUV);\n grainUV = (v_imageUV - .5) * (.8 / vec2(length(dudx), length(dudy))) + .5;\n }\n\n if (u_grainMixer > 0.) {\n float grainSeed = valueNoise(grainUV);\n vec2 jit = fract(grainSeed * vec2(157.31, 113.57)) * 2. - 1.;\n spreadAxis += jit * .3 * u_grainMixer * length(spreadAxis);\n }\n\n float count = floor(u_count);\n int countInteger = int(count);\n\n float invCount = 1. / count;\n float invSpan = 1. / max(count - 1., 1.);\n float hueBase = u_dispersionColor + .5;\n float biasPower = 1. + 2. * abs(u_bias);\n vec2 tapBias = .5 - vec2(u_imageX, u_imageY);\n\n float centerAmount = clamp(1. - u_dispersionShift, 0., 1.);\n float edgesAmount = clamp(1. + u_dispersionShift, 0., 1.);\n float dispersion = u_dispersion * mix(edgesAmount, centerAmount, innerCircleMask(radius, inradius));\n float dispersionPower = pow(dispersion, .8);\n\n float warpedRadius = length(warpedFromCenter);\n float swirlAngle = u_swirl * .4 * PI * spreadStrength * u_spread * min(1., inradius / max(warpedRadius, 1e-4));\n\n vec3 colorSum = vec3(0.);\n vec3 weightSum = vec3(0.);\n float coverSum = 0.;\n\n for (int i = 0; i < ${lensDistortionMeta.maxSamples}; i++) {\n if (i >= countInteger) break;\n\n float layer = float(i);\n float hue = hueBase + layer * invCount;\n\n float spread = layer * invSpan;\n if (u_bias != 0.) spread = dispersionCurve(spread, biasPower);\n\n // mix(axis, -axis, spread) is axis scaled by the signed position along the fan\n float fanPos = 1. - 2. * spread;\n vec2 tapUV = baseUV + spreadAxis * fanPos - .5;\n\n if (u_swirl != 0.) {\n tapUV.x *= u_imageAspectRatio;\n tapUV = rotate(tapUV, swirlAngle * fanPos);\n tapUV.x *= invAspect;\n }\n\n vec4 tap = sampleOverWhite(tapUV + tapBias);\n vec3 weight = 1. - dispersionPower * hueColor(hue);\n\n colorSum += tap.rgb * weight;\n weightSum += weight;\n coverSum += tap.a;\n }\n\n vec3 color = colorSum / max(weightSum, 1e-4);\n float coverAvg = coverSum * invCount;\n\n float ground = min(color.r, min(color.g, color.b));\n float alpha = max(coverAvg, 1. - ground);\n vec3 premult = max(color - (1. - alpha), 0.);\n fragColor = vec4(premult, alpha) * bulgeFade;\n\n if (u_grainOverlay > 0.) {\n float grain = valueNoise(rotate(grainUV, 1.) + vec2(3.));\n grain = mix(grain, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);\n grain = pow(grain, 1.3);\n float grainV = grain * 2. - 1.;\n float grainStrength = pow(u_grainOverlay * abs(grainV), .8) * fragColor.a;\n fragColor.rgb = mix(fragColor.rgb, vec3(step(0., grainV)) * fragColor.a, .35 * grainStrength);\n }\n}\n`;\n\nexport interface LensDistortionUniforms extends ShaderSizingUniforms {\n u_image: HTMLImageElement | string | undefined;\n u_spread: number;\n u_bias: number;\n u_angle: number;\n u_perspective: number;\n u_count: number;\n u_dispersion: number;\n u_dispersionShift: number;\n u_dispersionColor: number;\n u_focusCenter: number;\n u_focusEdges: number;\n u_swirl: number;\n u_noise: number;\n u_noiseFrequency: number;\n u_noiseOffset: number;\n u_lensBulge: number;\n u_lensCircle: number;\n u_grainMixer: number;\n u_grainOverlay: number;\n u_imageX: number;\n u_imageY: number;\n}\n\nexport interface LensDistortionParams extends ShaderSizingParams, ShaderMotionParams {\n image?: HTMLImageElement | string;\n spread?: number;\n bias?: number;\n angle?: number;\n perspective?: number;\n count?: number;\n dispersion?: number;\n dispersionShift?: number;\n dispersionColor?: number;\n focusCenter?: number;\n focusEdges?: number;\n swirl?: number;\n noise?: number;\n noiseFrequency?: number;\n noiseOffset?: number;\n lensBulge?: number;\n lensCircle?: number;\n grainMixer?: number;\n grainOverlay?: number;\n imageX?: number;\n imageY?: number;\n}\n"],
4
+ "sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, proceduralHash21, rotation2 } from '../shader-utils.js';\n\nexport const lensDistortionMeta = {\n maxSamples: 50,\n} as const;\n\n/**\n *\n * Lens Distortion image filter separates an image into shifting color layers\n * (recreating the chromatic aberration of a lens) and warps the image geometry,\n * curving it outward or inward like barrel and pincushion distortion.\n *\n * Fragment shader uniforms:\n * - u_image (sampler2D): Source image texture\n * - u_spread (float): Strength of the color split; how far the color layers are pushed apart; 0 is off (0 to 1)\n * - u_bias (float): Shifts the colors toward one end of the spread; 0 spaces them evenly, needs spread > 0, count > 2 (-1 to 1)\n * - u_angle (float): Direction of the spread in degrees, needs spread > 0, perspective < 1 (0 to 360)\n * - u_perspective (float): Shapes the spread direction from a straight line (0) to a radial burst out from the centre (1), needs spread > 0 (0 to 1)\n * - u_count (float): Number of sampled color layers along the spread; higher is smoother and costlier, needs spread > 0 (2 to 50)\n * - u_dispersion (float): Overall amount of color dispersion; 1 gives each layer its own color from the spectrum, 0 keeps the original image color, needs spread > 0 (0 to 1)\n * - u_dispersionShift (float): Balance of the dispersion between a soft circular zone at the centre and the rest of the image; 0 applies it evenly, -1 keeps it in the centre only, 1 keeps it at the edges only, needs spread > 0, dispersion > 0 (-1 to 1)\n * - u_dispersionColor (float): Rotates the colors around the hue wheel, 0 to 1 for a full turn, needs spread > 0, dispersion > 0\n * - u_focusCenter (float): Reduces the spread in a circular zone at the centre; 0 keeps it full to the centre, needs spread > 0 (0 to 1)\n * - u_focusEdges (float): Reduces the spread toward the edges; 0 keeps it full to the edges, 1 restores the original image there, needs spread > 0 (0 to 1)\n * - u_swirl (float): Rotates the color layers around the centre by an angle growing along the spread; 0 is off, needs spread > 0 (-1 to 1)\n * - u_noise (float): Scatters the spread direction with noise; 0 is off, needs spread > 0 (0 to 1)\n * - u_noiseFrequency (float): Frequency of the noise, 0 to 1 mapped internally to 0 to 18; higher is finer, needs spread > 0, noise > 0\n * - u_noiseOffset (float): Offsets the noise pattern for a different seed, 0 to 1 mapped internally to 0 to 30, needs spread > 0, noise > 0\n * - u_lensBulge (float): Radial lens warp of the image geometry; positive bulges out like a fisheye/barrel, negative pinches in like a pincushion; strong positive values fade out the corners (-1 to 1)\n * - u_lensCircle (float): Squeezes pixels outside the inscribed circle inward so the outline becomes a circle; also turns the spread radial and damps it near the rim; 0 is off, 1 is full (0 to 1)\n * - u_grainMixer (float): Scatters the spread with grain noise, breaking up the edges of the color layers, needs spread > 0 (0 to 1)\n * - u_grainOverlay (float): Post-processing black/white grain overlay (0 to 1)\n * - u_imageX (float): Pans the image horizontally behind the effect; 0 is centred (-1 to 1)\n * - u_imageY (float): Pans the image vertically behind the effect; 0 is centred (-1 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.1 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 lensDistortionFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\nuniform float u_spread;\nuniform float u_bias;\nuniform float u_angle;\nuniform float u_perspective;\nuniform float u_count;\nuniform float u_dispersion;\nuniform float u_dispersionShift;\nuniform float u_dispersionColor;\nuniform float u_focusCenter;\nuniform float u_focusEdges;\nuniform float u_swirl;\nuniform float u_noise;\nuniform float u_noiseFrequency;\nuniform float u_noiseOffset;\nuniform float u_lensBulge;\nuniform float u_lensCircle;\nuniform float u_grainMixer;\nuniform float u_grainOverlay;\nuniform float u_imageX;\nuniform float u_imageY;\n\nin vec2 v_imageUV;\n\nout vec4 fragColor;\n\n${declarePI}\n${proceduralHash21}\n${rotation2}\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.));\n float c = hash21(i + vec2(0., 1.));\n float d = hash21(i + vec2(1., 1.));\n vec2 u = f * f * (3. - 2. * f);\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat getUvFrame(vec2 uv) {\n vec2 invAA = 1. / clamp(fwidth(uv), 1e-5, .02);\n vec2 lo = clamp(uv * invAA + .5, 0., 1.);\n vec2 hi = clamp((1. - uv) * invAA + .5, 0., 1.);\n return lo.x * hi.x * lo.y * hi.y;\n}\n\nvec4 sampleOverWhite(vec2 uv) {\n vec4 img = texture(u_image, uv);\n float cover = img.a * getUvFrame(uv);\n vec3 colorOverWhite = mix(vec3(1.), img.rgb, cover);\n return vec4(colorOverWhite, cover);\n}\n\nvec3 hueColor(float hue) {\n vec3 rgb = clamp(abs(mod(hue * 6. + vec3(0., 4., 2.), 6.) - 3.) - 1., 0., 1.);\n rgb = rgb * rgb * (3. - 2. * rgb);\n return rgb;\n}\n \nfloat boxInradius() {\n return .5 * min(u_imageAspectRatio, 1.);\n}\n\nfloat boxOutradius() {\n return .5 * length(vec2(u_imageAspectRatio, 1.));\n}\n\nfloat spreadReach() {\n return .7 * pow(u_spread, 1.3 + 2.7 * u_spread);\n}\n\nfloat innerCircleMask(float radius, float inradius) {\n return 1. - smoothstep(.5 * inradius, 1.1 * inradius, radius);\n}\n\nfloat dispersionCurve(float t, float biasPow) {\n float mirroredT = u_bias < 0. ? 1. - t : t;\n float curved = pow(mirroredT, biasPow);\n return u_bias < 0. ? 1. - curved : curved;\n}\n\nvec2 getSpread(vec2 fromCenter, float radius, vec2 warpedUV, float edgeAA, float inradius, float outradius, float reach, out float outStrength) {\n float angleRad = radians(u_angle);\n vec2 uniformDir = vec2(cos(angleRad), sin(angleRad));\n\n float invInradius = 1. / inradius;\n vec2 radialDir = fromCenter * invInradius;\n float maxLen = (outradius + reach) * invInradius;\n float radialLen = radius * invInradius;\n if (radialLen > maxLen) radialDir *= maxLen / radialLen;\n vec2 spreadDir = mix(uniformDir, radialDir, u_perspective);\n\n float bandProximity = smoothstep(inradius * .8, inradius, radius);\n float lensCircleMaxing = bandProximity * u_lensCircle * u_lensCircle * u_lensCircle;\n spreadDir = mix(spreadDir, radialDir, lensCircleMaxing);\n\n vec2 warpedAbs = abs(warpedUV - .5);\n float inner = mix(1., smoothstep(0., mix(inradius, outradius, u_focusCenter), radius), u_focusCenter);\n float boxDist = max(warpedAbs.x, warpedAbs.y) * 2.;\n float outer = mix(1., 1. - min(boxDist, 1.), u_focusEdges);\n float strength = inner * outer;\n\n strength *= mix(1., mix(.15, .03, max(-u_lensBulge, 0.)), lensCircleMaxing);\n\n vec2 outside = max(warpedAbs - .5, 0.);\n float margin = max(reach * length(spreadDir) * (1. - u_lensCircle), edgeAA);\n strength *= 1. - smoothstep(0., margin, length(outside));\n\n outStrength = strength;\n vec2 axis = spreadDir * (reach * strength);\n\n if (u_noise > 0.) {\n float turn = (valueNoise(fromCenter * u_noiseFrequency * 18. + u_noiseOffset * 30.) - .5) * 2. * u_noise;\n float cs = cos(turn), sn = sin(turn);\n axis = mat2(cs, -sn, sn, cs) * axis;\n }\n\n axis.x /= u_imageAspectRatio;\n return axis;\n}\n\nvec2 lensWarp(vec2 fromCenter, float radius, float inradius, out float bulgeFade) {\n bulgeFade = 1.;\n if (u_lensBulge == 0. && u_lensCircle <= 0.) return fromCenter;\n if (radius < 1e-5) return fromCenter;\n \n float r = radius;\n\n if (u_lensBulge != 0.) {\n float rn = radius / inradius;\n float bulge = abs(u_lensBulge) * (u_lensBulge > 0. ? 1.4 : 1.2);\n if (u_lensBulge > 0.) bulgeFade = 1. - smoothstep(1.45, 1.53, rn * bulge);\n float map = u_lensBulge > 0.\n ? tan(min(rn * bulge, 1.53)) / tan(bulge)\n : atan(rn * tan(bulge)) / bulge;\n float bulgeScale = map / rn;\n fromCenter *= bulgeScale;\n r *= bulgeScale;\n }\n\n if (u_lensCircle > 0.) {\n vec2 dir = fromCenter / max(r, 1e-5);\n vec2 halfBox = vec2(u_imageAspectRatio, 1.) * .5;\n float rBox = min(halfBox.x / max(abs(dir.x), 1e-4), halfBox.y / max(abs(dir.y), 1e-4));\n float band = inradius * mix(.03, .2, .5 * (u_lensBulge + 1.));\n float innerEdge = inradius - band;\n float over = smoothstep(0., 1., (r - innerEdge) / band);\n float g = r + (rBox - inradius) * pow(over, 14.);\n fromCenter = dir * mix(r, g, u_lensCircle);\n }\n\n return fromCenter;\n}\n\nvoid main() {\n vec2 uv = v_imageUV;\n\n float invAspect = 1. / u_imageAspectRatio;\n float inradius = boxInradius();\n float outradius = boxOutradius();\n float reach = spreadReach();\n\n vec2 fromCenter = uv - .5;\n fromCenter.x *= u_imageAspectRatio;\n float radius = length(fromCenter);\n\n float bulgeFade;\n vec2 warpedFromCenter = lensWarp(fromCenter, radius, inradius, bulgeFade);\n vec2 baseUV = vec2(warpedFromCenter.x * invAspect, warpedFromCenter.y) + .5;\n\n vec2 baseDerivative = fwidth(baseUV);\n float edgeAA = clamp(2. * max(baseDerivative.x, baseDerivative.y), .001, .02);\n\n float spreadStrength;\n vec2 spreadAxis = getSpread(fromCenter, radius, baseUV, edgeAA, inradius, outradius, reach, spreadStrength);\n\n vec2 grainUV = vec2(0.);\n if (u_grainMixer > 0. || u_grainOverlay > 0.) {\n vec2 dudx = dFdx(v_imageUV);\n vec2 dudy = dFdy(v_imageUV);\n grainUV = (v_imageUV - .5) * (.8 / vec2(length(dudx), length(dudy))) + .5;\n }\n\n if (u_grainMixer > 0.) {\n float grainSeed = valueNoise(grainUV);\n vec2 jit = fract(grainSeed * vec2(157.31, 113.57)) * 2. - 1.;\n spreadAxis += jit * .3 * u_grainMixer * length(spreadAxis);\n }\n\n float count = floor(u_count);\n int countInteger = int(count);\n\n float invCount = 1. / count;\n float invSpan = 1. / max(count - 1., 1.);\n float hueBase = u_dispersionColor + .5;\n float biasPower = 1. + 2. * abs(u_bias);\n vec2 tapBias = .5 - vec2(u_imageX, u_imageY);\n\n float centerAmount = clamp(1. - u_dispersionShift, 0., 1.);\n float edgesAmount = clamp(1. + u_dispersionShift, 0., 1.);\n float dispersion = u_dispersion * mix(edgesAmount, centerAmount, innerCircleMask(radius, inradius));\n float dispersionPower = pow(dispersion, .8);\n\n float warpedRadius = length(warpedFromCenter);\n float swirlAngle = u_swirl * .4 * PI * spreadStrength * u_spread * min(1., inradius / max(warpedRadius, 1e-4));\n\n vec3 colorSum = vec3(0.);\n vec3 weightSum = vec3(0.);\n float coverSum = 0.;\n\n for (int i = 0; i < ${lensDistortionMeta.maxSamples}; i++) {\n if (i >= countInteger) break;\n\n float layer = float(i);\n float hue = hueBase + layer * invCount;\n\n float spread = layer * invSpan;\n if (u_bias != 0.) spread = dispersionCurve(spread, biasPower);\n\n // mix(axis, -axis, spread) is axis scaled by the signed position along the fan\n float fanPos = 1. - 2. * spread;\n vec2 tapUV = baseUV + spreadAxis * fanPos - .5;\n\n if (u_swirl != 0.) {\n tapUV.x *= u_imageAspectRatio;\n tapUV = rotate(tapUV, swirlAngle * fanPos);\n tapUV.x *= invAspect;\n }\n\n vec4 tap = sampleOverWhite(tapUV + tapBias);\n vec3 weight = 1. - dispersionPower * hueColor(hue);\n\n colorSum += tap.rgb * weight;\n weightSum += weight;\n coverSum += tap.a;\n }\n\n vec3 color = colorSum / max(weightSum, 1e-4);\n float coverAvg = coverSum * invCount;\n\n float ground = min(color.r, min(color.g, color.b));\n float alpha = max(coverAvg, 1. - ground);\n vec3 premult = max(color - (1. - alpha), 0.);\n fragColor = vec4(premult, alpha) * bulgeFade;\n\n if (u_grainOverlay > 0.) {\n float grain = valueNoise(rotate(grainUV, 1.) + vec2(3.));\n grain = mix(grain, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);\n grain = pow(grain, 1.3);\n float grainV = grain * 2. - 1.;\n float grainStrength = pow(u_grainOverlay * abs(grainV), .8) * fragColor.a;\n fragColor.rgb = mix(fragColor.rgb, vec3(step(0., grainV)) * fragColor.a, .35 * grainStrength);\n }\n}\n`;\n\nexport interface LensDistortionUniforms extends ShaderSizingUniforms {\n u_image: HTMLImageElement | string | undefined;\n u_spread: number;\n u_bias: number;\n u_angle: number;\n u_perspective: number;\n u_count: number;\n u_dispersion: number;\n u_dispersionShift: number;\n u_dispersionColor: number;\n u_focusCenter: number;\n u_focusEdges: number;\n u_swirl: number;\n u_noise: number;\n u_noiseFrequency: number;\n u_noiseOffset: number;\n u_lensBulge: number;\n u_lensCircle: number;\n u_grainMixer: number;\n u_grainOverlay: number;\n u_imageX: number;\n u_imageY: number;\n}\n\nexport interface LensDistortionParams extends ShaderSizingParams, ShaderMotionParams {\n image?: HTMLImageElement | string;\n spread?: number;\n bias?: number;\n angle?: number;\n perspective?: number;\n count?: number;\n dispersion?: number;\n dispersionShift?: number;\n dispersionColor?: number;\n focusCenter?: number;\n focusEdges?: number;\n swirl?: number;\n noise?: number;\n noiseFrequency?: number;\n noiseOffset?: number;\n lensBulge?: number;\n lensCircle?: number;\n grainMixer?: number;\n grainOverlay?: number;\n imageX?: number;\n imageY?: number;\n}\n"],
5
5
  "mappings": ";;;;;AACA,eAAmE;AACnE,SAAS,WAAW,kBAAkB,iBAAiB;AAEhD,MAAM,qBAAqB;AAAA,EAChC,YAAY;AACd;AAiDO,MAAM,+BAAuC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8BlD,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAwLa,mBAAmB,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -9,7 +9,6 @@ import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-si
9
9
  * - u_time (float): Animation time
10
10
  * - u_resolution (vec2): Canvas resolution in pixels
11
11
  * - u_image (sampler2D): Pre-processed source image texture (R = edge gradient, G = opacity)
12
- * - u_imageAspectRatio (float): Aspect ratio of the source image
13
12
  * - u_colorBack (vec4): Background color in RGBA
14
13
  * - u_colorTint (vec4): Overlay color in RGBA (color burn blending used)
15
14
  * - u_repetition (float): Density of pattern stripes (1 to 10)
@@ -19,7 +18,7 @@ import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-si
19
18
  * - u_distortion (float): Noise distortion over the stripes pattern (0 to 1)
20
19
  * - u_contour (float): Strength of the distortion on the shape edges (0 to 1)
21
20
  * - u_angle (float): Direction of pattern animation in degrees (0 to 360)
22
- * - u_shape (float): Predefined shape when no image provided (0 = none, 1 = circle, 2 = daisy, 3 = diamond, 4 = metaballs)
21
+ * - u_shape (float): Predefined shape mask, needs no image (0 = none, 1 = circle, 2 = daisy, 3 = diamond, 4 = metaballs)
23
22
  * - u_isImage (bool): Whether an image is being used as the effect mask
24
23
  *
25
24
  * Vertex shader outputs (used in fragment shader):
@@ -36,7 +35,7 @@ import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-si
36
35
  * - u_worldWidth (float): Virtual width of the graphic before it's scaled to fit the canvas
37
36
  * - u_worldHeight (float): Virtual height of the graphic before it's scaled to fit the canvas
38
37
  * - u_fit (float): How to fit the rendered shader into the canvas dimensions (0 = none, 1 = contain, 2 = cover)
39
- * - u_scale (float): Overall zoom level of the graphics (0.01 to 4)
38
+ * - u_scale (float): Overall zoom level of the graphics (0.1 to 4)
40
39
  * - u_rotation (float): Overall rotation angle of the graphics in degrees (0 to 360)
41
40
  * - u_offsetX (float): Horizontal offset of the graphics center (-1 to 1)
42
41
  * - u_offsetY (float): Vertical offset of the graphics center (-1 to 1)
@@ -9,7 +9,6 @@ const liquidMetalFragmentShader = `#version 300 es
9
9
  precision mediump float;
10
10
 
11
11
  uniform sampler2D u_image;
12
- uniform float u_imageAspectRatio;
13
12
 
14
13
  uniform vec2 u_resolution;
15
14
  uniform float u_time;
@@ -129,6 +128,9 @@ void main() {
129
128
  rotatedUV.x * sinA + rotatedUV.y * cosA
130
129
  ) + vec2(.5);
131
130
 
131
+ // u_contour is applied in 2 separate ranges:
132
+ // - 0 to .4 sets the edge hardness, saturated above .4 (both branches below)
133
+ // - .5 to 1 warps the stripes direction along the edges, inactive below .5 (see u_contour range 2)
132
134
  if (u_isImage == true) {
133
135
  float edgeRaw = img.r;
134
136
  edge = blurEdge3x3(u_image, uv, dudx, dudy, 6., edgeRaw);
@@ -261,6 +263,7 @@ void main() {
261
263
  direction += diagBLtoTR;
262
264
  float contour = 0.;
263
265
  direction -= 2. * noise * diagBLtoTR * (smoothstep(0., 1., edge) * (1.0 - smoothstep(0., 1., edge)));
266
+ // u_contour range 2
264
267
  direction *= mix(1., 1. - edge, smoothstep(.5, 1., u_contour));
265
268
  direction -= 1.7 * edge * smoothstep(.5, 1., u_contour);
266
269
  direction += .2 * pow(u_contour, 4.) * (1.0 - smoothstep(0., 1., edge));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/shaders/liquid-metal.ts"],
4
- "sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, rotation2, simplexNoise, colorBandingFix } from '../shader-utils.js';\n\n/**\n * Futuristic liquid metal material applied to uploaded logo or abstract shape.\n * Fluid motion imitation applied over user image with animated stripe pattern\n * getting distorted along shape edges.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_image (sampler2D): Pre-processed source image texture (R = edge gradient, G = opacity)\n * - u_imageAspectRatio (float): Aspect ratio of the source image\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colorTint (vec4): Overlay color in RGBA (color burn blending used)\n * - u_repetition (float): Density of pattern stripes (1 to 10)\n * - u_softness (float): Color transition sharpness, 0 = hard edge, 1 = smooth gradient (0 to 1)\n * - u_shiftRed (float): R-channel dispersion (-1 to 1)\n * - u_shiftBlue (float): B-channel dispersion (-1 to 1)\n * - u_distortion (float): Noise distortion over the stripes pattern (0 to 1)\n * - u_contour (float): Strength of the distortion on the shape edges (0 to 1)\n * - u_angle (float): Direction of pattern animation in degrees (0 to 360)\n * - u_shape (float): Predefined shape when no image provided (0 = none, 1 = circle, 2 = daisy, 3 = diamond, 4 = metaballs)\n * - u_isImage (bool): Whether an image is being used as the effect mask\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 (used when no image)\n * - v_responsiveUV (vec2): Responsive UV coordinates that adapt to canvas aspect ratio (used for canvas-fill mode)\n * - v_responsiveBoxGivenSize (vec2): TBD\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 liquidMetalFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform sampler2D u_image;\nuniform float u_imageAspectRatio;\n\nuniform vec2 u_resolution;\nuniform float u_time;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colorTint;\n\nuniform float u_softness;\nuniform float u_repetition;\nuniform float u_shiftRed;\nuniform float u_shiftBlue;\nuniform float u_distortion;\nuniform float u_contour;\nuniform float u_angle;\n\nuniform float u_shape;\nuniform bool u_isImage;\n\nin vec2 v_objectUV;\nin vec2 v_responsiveUV;\nin vec2 v_responsiveBoxGivenSize;\nin vec2 v_imageUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ rotation2 }\n${ simplexNoise }\n\nfloat getColorChanges(float c1, float c2, float stripe_p, vec3 w, float blur, float bump, float tint) {\n\n float ch = mix(c2, c1, smoothstep(.0, 2. * blur, stripe_p));\n\n float border = w[0];\n ch = mix(ch, c2, smoothstep(border, border + 2. * blur, stripe_p));\n\n if (u_isImage == true) {\n bump = smoothstep(.2, .8, bump);\n }\n border = w[0] + .4 * (1. - bump) * w[1];\n ch = mix(ch, c1, smoothstep(border, border + 2. * blur, stripe_p));\n\n border = w[0] + .5 * (1. - bump) * w[1];\n ch = mix(ch, c2, smoothstep(border, border + 2. * blur, stripe_p));\n\n border = w[0] + w[1];\n ch = mix(ch, c1, smoothstep(border, border + 2. * blur, stripe_p));\n\n float gradient_t = (stripe_p - w[0] - w[1]) / w[2];\n float gradient = mix(c1, c2, smoothstep(0., 1., gradient_t));\n ch = mix(ch, gradient, smoothstep(border, border + .5 * blur, stripe_p));\n\n // Tint color is applied with color burn blending\n ch = mix(ch, 1. - min(1., (1. - ch) / max(tint, 0.0001)), u_colorTint.a);\n return ch;\n}\n\nfloat getImgFrame(vec2 uv, float th) {\n float frame = 1.;\n frame *= smoothstep(0., th, uv.y);\n frame *= 1.0 - smoothstep(1. - th, 1., uv.y);\n frame *= smoothstep(0., th, uv.x);\n frame *= 1.0 - smoothstep(1. - th, 1., uv.x);\n return frame;\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).r;\n sum += w2 * textureGrad(tex, uv + vec2(0.0, r.y), dudx, dudy).r;\n sum += w2 * textureGrad(tex, uv + vec2(-r.x, 0.0), dudx, dudy).r;\n sum += w2 * textureGrad(tex, uv + vec2(r.x, 0.0), dudx, dudy).r;\n\n sum += w1 * textureGrad(tex, uv + vec2(-r.x, -r.y), dudx, dudy).r;\n sum += w1 * textureGrad(tex, uv + vec2(r.x, -r.y), dudx, dudy).r;\n sum += w1 * textureGrad(tex, uv + vec2(-r.x, r.y), dudx, dudy).r;\n sum += w1 * textureGrad(tex, uv + vec2(r.x, r.y), dudx, dudy).r;\n\n return sum / norm;\n}\n\nfloat lst(float edge0, float edge1, float x) {\n return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n}\n\nvoid main() {\n\n const float firstFrameOffset = 2.8;\n float t = .3 * (u_time + firstFrameOffset);\n\n vec2 uv = v_imageUV;\n vec2 dudx = dFdx(v_imageUV);\n vec2 dudy = dFdy(v_imageUV);\n vec4 img = textureGrad(u_image, uv, dudx, dudy);\n\n if (u_isImage == false) {\n uv = v_objectUV + .5;\n uv.y = 1. - uv.y;\n }\n\n float cycleWidth = u_repetition;\n float edge = 0.;\n float contOffset = 1.;\n\n vec2 rotatedUV = uv - vec2(.5);\n float angle = (-u_angle + 70.) * PI / 180.;\n float cosA = cos(angle);\n float sinA = sin(angle);\n rotatedUV = vec2(\n rotatedUV.x * cosA - rotatedUV.y * sinA,\n rotatedUV.x * sinA + rotatedUV.y * cosA\n ) + vec2(.5);\n\n if (u_isImage == true) {\n float edgeRaw = img.r;\n edge = blurEdge3x3(u_image, uv, dudx, dudy, 6., edgeRaw);\n edge = pow(edge, 1.6);\n edge *= mix(0.0, 1.0, smoothstep(0.0, 0.4, u_contour));\n } else {\n if (u_shape < 1.) {\n // full-fill on canvas\n vec2 borderUV = v_responsiveUV + .5;\n float ratio = v_responsiveBoxGivenSize.x / v_responsiveBoxGivenSize.y;\n vec2 mask = min(borderUV, 1. - borderUV);\n vec2 pixel_thickness = min(250. / v_responsiveBoxGivenSize, vec2(.5));\n float maskX = smoothstep(0.0, pixel_thickness.x, mask.x);\n float maskY = smoothstep(0.0, pixel_thickness.y, mask.y);\n maskX = pow(maskX, .25);\n maskY = pow(maskY, .25);\n edge = clamp(1. - maskX * maskY, 0., 1.);\n\n uv = v_responsiveUV;\n if (ratio > 1.) {\n uv.y /= ratio;\n } else {\n uv.x *= ratio;\n }\n uv += .5;\n uv.y = 1. - uv.y;\n\n cycleWidth *= 2.;\n contOffset = 1.5;\n\n } else if (u_shape < 2.) {\n // circle\n vec2 shapeUV = uv - .5;\n shapeUV *= .67;\n edge = pow(clamp(3. * length(shapeUV), 0., 1.), 18.);\n } else if (u_shape < 3.) {\n // daisy\n vec2 shapeUV = uv - .5;\n shapeUV *= 1.68;\n\n float r = length(shapeUV) * 2.;\n float a = atan(shapeUV.y, shapeUV.x) + .2;\n r *= (1. + .05 * sin(3. * a + 2. * t));\n float f = abs(cos(a * 3.));\n edge = smoothstep(f, f + .7, r);\n edge *= edge;\n\n uv *= .8;\n cycleWidth *= 1.6;\n\n } else if (u_shape < 4.) {\n // diamond\n vec2 shapeUV = uv - .5;\n shapeUV = rotate(shapeUV, .25 * PI);\n shapeUV *= 1.42;\n shapeUV += .5;\n vec2 mask = min(shapeUV, 1. - shapeUV);\n vec2 pixel_thickness = vec2(.15);\n float maskX = smoothstep(0.0, pixel_thickness.x, mask.x);\n float maskY = smoothstep(0.0, pixel_thickness.y, mask.y);\n maskX = pow(maskX, .25);\n maskY = pow(maskY, .25);\n edge = clamp(1. - maskX * maskY, 0., 1.);\n } else if (u_shape < 5.) {\n // metaballs\n vec2 shapeUV = uv - .5;\n shapeUV *= 1.3;\n edge = 0.;\n for (int i = 0; i < 5; i++) {\n float fi = float(i);\n float speed = 1.5 + 2./3. * sin(fi * 12.345);\n float angle = -fi * 1.5;\n vec2 dir1 = vec2(cos(angle), sin(angle));\n vec2 dir2 = vec2(cos(angle + 1.57), sin(angle + 1.));\n vec2 traj = .4 * (dir1 * sin(t * speed + fi * 1.23) + dir2 * cos(t * (speed * 0.7) + fi * 2.17));\n float d = length(shapeUV + traj);\n edge += pow(1.0 - clamp(d, 0.0, 1.0), 4.0);\n }\n edge = 1. - smoothstep(.65, .9, edge);\n edge = pow(edge, 4.);\n }\n\n edge = mix(smoothstep(.9 - 2. * fwidth(edge), .9, edge), edge, smoothstep(0.0, 0.4, u_contour));\n\n }\n\n float opacity = 0.;\n if (u_isImage == true) {\n opacity = img.g;\n float frame = getImgFrame(v_imageUV, 0.);\n opacity *= frame;\n } else {\n opacity = 1. - smoothstep(.9 - 2. * fwidth(edge), .9, edge);\n if (u_shape < 2.) {\n edge = 1.2 * edge;\n } else if (u_shape < 5.) {\n edge = 1.8 * pow(edge, 1.5);\n }\n }\n\n float diagBLtoTR = rotatedUV.x - rotatedUV.y;\n float diagTLtoBR = rotatedUV.x + rotatedUV.y;\n\n vec3 color = vec3(0.);\n vec3 color1 = vec3(.98, 0.98, 1.);\n vec3 color2 = vec3(.1, .1, .1 + .1 * smoothstep(.7, 1.3, diagTLtoBR));\n\n vec2 grad_uv = uv - .5;\n\n float dist = length(grad_uv + vec2(0., .2 * diagBLtoTR));\n grad_uv = rotate(grad_uv, (.25 - .2 * diagBLtoTR) * PI);\n float direction = grad_uv.x;\n\n float bump = pow(1.8 * dist, 1.2);\n bump = 1. - bump;\n bump *= pow(uv.y, .3);\n\n\n float thin_strip_1_ratio = .12 / cycleWidth * (1. - .4 * bump);\n float thin_strip_2_ratio = .07 / cycleWidth * (1. + .4 * bump);\n float wide_strip_ratio = (1. - thin_strip_1_ratio - thin_strip_2_ratio);\n\n float thin_strip_1_width = cycleWidth * thin_strip_1_ratio;\n float thin_strip_2_width = cycleWidth * thin_strip_2_ratio;\n\n float noise = snoise(uv - t);\n\n edge += (1. - edge) * u_distortion * noise;\n\n direction += diagBLtoTR;\n float contour = 0.;\n direction -= 2. * noise * diagBLtoTR * (smoothstep(0., 1., edge) * (1.0 - smoothstep(0., 1., edge)));\n direction *= mix(1., 1. - edge, smoothstep(.5, 1., u_contour));\n direction -= 1.7 * edge * smoothstep(.5, 1., u_contour);\n direction += .2 * pow(u_contour, 4.) * (1.0 - smoothstep(0., 1., edge));\n\n bump *= clamp(pow(uv.y, .1), .3, 1.);\n direction *= (.1 + (1.1 - edge) * bump);\n\n direction *= (.4 + .6 * (1.0 - smoothstep(.5, 1., edge)));\n direction += .18 * (smoothstep(.1, .2, uv.y) * (1.0 - smoothstep(.2, .4, uv.y)));\n direction += .03 * (smoothstep(.1, .2, 1. - uv.y) * (1.0 - smoothstep(.2, .4, 1. - uv.y)));\n\n direction *= (.5 + .5 * pow(uv.y, 2.));\n direction *= cycleWidth;\n direction -= t;\n\n\n float colorDispersion = (1. - bump);\n colorDispersion = clamp(colorDispersion, 0., 1.);\n float dispersionRed = colorDispersion;\n dispersionRed += .03 * bump * noise;\n dispersionRed += 5. * (smoothstep(-.1, .2, uv.y) * (1.0 - smoothstep(.1, .5, uv.y))) * (smoothstep(.4, .6, bump) * (1.0 - smoothstep(.4, 1., bump)));\n dispersionRed -= diagBLtoTR;\n\n float dispersionBlue = colorDispersion;\n dispersionBlue *= 1.3;\n dispersionBlue += (smoothstep(0., .4, uv.y) * (1.0 - smoothstep(.1, .8, uv.y))) * (smoothstep(.4, .6, bump) * (1.0 - smoothstep(.4, .8, bump)));\n dispersionBlue -= .2 * edge;\n\n dispersionRed *= (u_shiftRed / 20.);\n dispersionBlue *= (u_shiftBlue / 20.);\n\n float blur = 0.;\n float rExtraBlur = 0.;\n float gExtraBlur = 0.;\n if (u_isImage == true) {\n float softness = 0.05 * u_softness;\n blur = softness + .5 * smoothstep(1., 10., u_repetition) * smoothstep(.0, 1., edge);\n float smallCanvasT = 1.0 - smoothstep(100., 500., min(u_resolution.x, u_resolution.y));\n blur += smallCanvasT * smoothstep(.0, 1., edge);\n rExtraBlur = softness * (0.05 + .1 * (u_shiftRed / 20.) * bump);\n gExtraBlur = softness * 0.05 / max(0.001, abs(1. - diagBLtoTR));\n } else {\n blur = u_softness / 15. + .3 * contour;\n }\n\n vec3 w = vec3(thin_strip_1_width, thin_strip_2_width, wide_strip_ratio);\n w[1] -= .02 * smoothstep(.0, 1., edge + bump);\n float stripe_r = fract(direction + dispersionRed);\n float r = getColorChanges(color1.r, color2.r, stripe_r, w, blur + fwidth(stripe_r) + rExtraBlur, bump, u_colorTint.r);\n float stripe_g = fract(direction);\n float g = getColorChanges(color1.g, color2.g, stripe_g, w, blur + fwidth(stripe_g) + gExtraBlur, bump, u_colorTint.g);\n float stripe_b = fract(direction - dispersionBlue);\n float b = getColorChanges(color1.b, color2.b, stripe_b, w, blur + fwidth(stripe_b), bump, u_colorTint.b);\n\n color = vec3(r, g, b);\n color *= opacity;\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\n ${ colorBandingFix }\n\n fragColor = vec4(color, opacity);\n}\n`;\n\n// Configuration for Poisson solver\nexport const POISSON_CONFIG_OPTIMIZED = {\n measurePerformance: false, // Set to true to see performance metrics\n workingSize: 512, // Size to solve Poisson at (will upscale to original size)\n iterations: 40, // SOR converges ~2-20x faster than standard Gauss-Seidel\n};\n\n// Precomputed pixel data for sparse processing\ninterface SparsePixelData {\n interiorPixels: Uint32Array; // Indices of interior pixels\n boundaryPixels: Uint32Array; // Indices of boundary pixels\n pixelCount: number;\n // Neighbor indices for each interior pixel (4 neighbors per pixel)\n // Layout: [east, west, north, south] for each pixel\n neighborIndices: Int32Array;\n}\n\nexport function toProcessedLiquidMetal(file: File | string): Promise<{ imageData: ImageData; pngBlob: Blob }> {\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n const isBlob = typeof file === 'string' && file.startsWith('blob:');\n\n return new Promise((resolve, reject) => {\n if (!file || !ctx) {\n reject(new Error('Invalid file or canvas context'));\n return;\n }\n\n const blobContentTypePromise = isBlob && fetch(file).then((res) => res.headers.get('Content-Type'));\n const img = new Image();\n img.crossOrigin = 'anonymous';\n const totalStartTime = performance.now();\n\n img.onload = async () => {\n // Force SVG to load at a high fidelity size if it's an SVG\n let isSVG;\n\n const blobContentType = await blobContentTypePromise;\n\n if (blobContentType) {\n isSVG = blobContentType === 'image/svg+xml';\n } else if (typeof file === 'string') {\n isSVG = file.endsWith('.svg') || file.startsWith('data:image/svg+xml');\n } else {\n isSVG = file.type === 'image/svg+xml';\n }\n\n let originalWidth = img.width || img.naturalWidth;\n let originalHeight = img.height || img.naturalHeight;\n\n if (isSVG) {\n // Scale SVG to max dimension while preserving aspect ratio\n const svgMaxSize = 4096;\n const aspectRatio = originalWidth / originalHeight;\n\n if (originalWidth > originalHeight) {\n originalWidth = svgMaxSize;\n originalHeight = svgMaxSize / aspectRatio;\n } else {\n originalHeight = svgMaxSize;\n originalWidth = svgMaxSize * aspectRatio;\n }\n\n img.width = originalWidth;\n img.height = originalHeight;\n }\n\n // Always scale to working resolution for consistency\n const minDimension = Math.min(originalWidth, originalHeight);\n const targetSize = POISSON_CONFIG_OPTIMIZED.workingSize;\n\n // Calculate scale to fit within workingSize\n const scaleFactor = targetSize / minDimension;\n const width = Math.round(originalWidth * scaleFactor);\n const height = Math.round(originalHeight * scaleFactor);\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n console.log(`[Processing Mode]`);\n console.log(` Original: ${originalWidth}\u00D7${originalHeight}`);\n console.log(` Working: ${width}\u00D7${height} (${(scaleFactor * 100).toFixed(1)}% scale)`);\n if (scaleFactor < 1) {\n console.log(` Speedup: ~${Math.round(1 / (scaleFactor * scaleFactor))}\u00D7`);\n }\n }\n\n canvas.width = originalWidth;\n canvas.height = originalHeight;\n\n // Use a smaller canvas for shape detection and Poisson solving\n const shapeCanvas = document.createElement('canvas');\n shapeCanvas.width = width;\n shapeCanvas.height = height;\n\n const shapeCtx = shapeCanvas.getContext('2d')!;\n shapeCtx.drawImage(img, 0, 0, width, height);\n\n // 1) Build optimized masks using TypedArrays\n const startMask = performance.now();\n\n const shapeImageData = shapeCtx.getImageData(0, 0, width, height);\n const data = shapeImageData.data;\n\n // Use Uint8Array for masks (1 byte per pixel vs 8+ bytes for boolean array)\n const shapeMask = new Uint8Array(width * height);\n const boundaryMask = new Uint8Array(width * height);\n\n // First pass: identify shape pixels\n let shapePixelCount = 0;\n for (let i = 0, idx = 0; i < data.length; i += 4, idx++) {\n const a = data[i + 3];\n const isShape = a === 0 ? 0 : 1;\n shapeMask[idx] = isShape;\n shapePixelCount += isShape;\n }\n\n // 2) Optimized boundary detection using sparse approach\n // Only check shape pixels, not all pixels\n const boundaryIndices: number[] = [];\n const interiorIndices: number[] = [];\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = y * width + x;\n if (!shapeMask[idx]) continue;\n\n // Check if pixel is on boundary (optimized: early exit)\n let isBoundary = false;\n\n // Check 4-connected neighbors first (most common case)\n if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {\n isBoundary = true;\n } else {\n // Check all 8 neighbors (including diagonals) for comprehensive boundary detection\n isBoundary =\n !shapeMask[idx - 1] || // left\n !shapeMask[idx + 1] || // right\n !shapeMask[idx - width] || // top\n !shapeMask[idx + width] || // bottom\n !shapeMask[idx - width - 1] || // top-left\n !shapeMask[idx - width + 1] || // top-right\n !shapeMask[idx + width - 1] || // bottom-left\n !shapeMask[idx + width + 1]; // bottom-right\n }\n\n if (isBoundary) {\n boundaryMask[idx] = 1;\n boundaryIndices.push(idx);\n } else {\n interiorIndices.push(idx);\n }\n }\n }\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n console.log(`[Mask Building] Time: ${(performance.now() - startMask).toFixed(2)}ms`);\n console.log(\n ` Shape pixels: ${shapePixelCount} / ${width * height} (${((shapePixelCount / (width * height)) * 100).toFixed(1)}%)`\n );\n console.log(` Interior pixels: ${interiorIndices.length}`);\n console.log(` Boundary pixels: ${boundaryIndices.length}`);\n }\n\n // 3) Precompute sparse data structure for solver\n const sparseData = buildSparseData(\n shapeMask,\n boundaryMask,\n new Uint32Array(interiorIndices),\n new Uint32Array(boundaryIndices),\n width,\n height\n );\n\n // 4) Solve Poisson equation with optimized sparse solver\n const startSolve = performance.now();\n const u = solvePoissonSparse(sparseData, shapeMask, boundaryMask, width, height);\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n console.log(`[Poisson Solve] Time: ${(performance.now() - startSolve).toFixed(2)}ms`);\n }\n\n // 5) Generate output image\n let maxVal = 0;\n let finalImageData: ImageData;\n\n // Only check shape pixels for max value\n for (let i = 0; i < interiorIndices.length; i++) {\n const idx = interiorIndices[i]!;\n if (u[idx]! > maxVal) maxVal = u[idx]!;\n }\n\n // Create gradient image at working resolution\n const tempCanvas = document.createElement('canvas');\n tempCanvas.width = width;\n tempCanvas.height = height;\n const tempCtx = tempCanvas.getContext('2d')!;\n\n const tempImg = tempCtx.createImageData(width, height);\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = y * width + x;\n const px = idx * 4;\n\n if (!shapeMask[idx]) {\n tempImg.data[px] = 255;\n tempImg.data[px + 1] = 255;\n tempImg.data[px + 2] = 255;\n tempImg.data[px + 3] = 0; // Alpha = 0 for background\n } else {\n const poissonRatio = u[idx]! / maxVal;\n const gray = 255 * (1 - poissonRatio);\n tempImg.data[px] = gray;\n tempImg.data[px + 1] = gray;\n tempImg.data[px + 2] = gray;\n tempImg.data[px + 3] = 255; // Alpha = 255 for shape\n }\n }\n }\n tempCtx.putImageData(tempImg, 0, 0);\n\n // Upscale to original resolution with smooth interpolation\n ctx.imageSmoothingEnabled = true;\n ctx.imageSmoothingQuality = 'high';\n ctx.drawImage(tempCanvas, 0, 0, width, height, 0, 0, originalWidth, originalHeight);\n\n // Now get the upscaled image data for final output\n const outImg = ctx.getImageData(0, 0, originalWidth, originalHeight);\n\n // Re-apply edges from original resolution with anti-aliasing\n // This ensures edges are pixel-perfect while gradient is smooth\n const originalCanvas = document.createElement('canvas');\n originalCanvas.width = originalWidth;\n originalCanvas.height = originalHeight;\n const originalCtx = originalCanvas.getContext('2d')!;\n // originalCtx.fillStyle = \"white\";\n // originalCtx.fillRect(0, 0, originalWidth, originalHeight);\n originalCtx.drawImage(img, 0, 0, originalWidth, originalHeight);\n const originalData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);\n\n // Process each pixel: Red channel = gradient, Alpha channel = original alpha\n for (let i = 0; i < outImg.data.length; i += 4) {\n const a = originalData.data[i + 3]!;\n // Use only alpha to determine background vs shape\n const upscaledAlpha = outImg.data[i + 3]!;\n if (a === 0) {\n // Background pixel\n outImg.data[i] = 255;\n outImg.data[i + 1] = 0;\n } else {\n // Red channel carries the gradient\n // Check if upscale missed this pixel by looking at alpha channel\n // If upscaled alpha is 0, the low-res version thought this was background\n outImg.data[i] = upscaledAlpha === 0 ? 0 : outImg.data[i]!; // gradient or 0\n outImg.data[i + 1] = a; // original alpha\n }\n\n // Unused channels fixed\n outImg.data[i + 2] = 255;\n outImg.data[i + 3] = 255;\n }\n\n ctx.putImageData(outImg, 0, 0);\n finalImageData = outImg;\n canvas.toBlob((blob) => {\n if (!blob) {\n reject(new Error('Failed to create PNG blob'));\n return;\n }\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n const totalTime = performance.now() - totalStartTime;\n console.log(`[Total Processing Time] ${totalTime.toFixed(2)}ms`);\n if (scaleFactor < 1) {\n const estimatedFullResTime = totalTime * Math.pow((originalWidth * originalHeight) / (width * height), 1.5);\n console.log(`[Estimated time at full resolution] ~${estimatedFullResTime.toFixed(0)}ms`);\n console.log(\n `[Time saved] ~${(estimatedFullResTime - totalTime).toFixed(0)}ms (${Math.round(estimatedFullResTime / totalTime)}\u00D7 faster)`\n );\n }\n }\n\n resolve({\n imageData: finalImageData,\n pngBlob: blob,\n });\n }, 'image/png');\n };\n\n img.onerror = () => reject(new Error('Failed to load image'));\n img.src = typeof file === 'string' ? file : URL.createObjectURL(file);\n });\n}\n\nfunction buildSparseData(\n shapeMask: Uint8Array,\n boundaryMask: Uint8Array,\n interiorPixels: Uint32Array,\n boundaryPixels: Uint32Array,\n width: number,\n height: number\n): SparsePixelData {\n const pixelCount = interiorPixels.length;\n\n // Build neighbor indices for sparse processing\n // For each interior pixel, store indices of its 4 neighbors\n // Use -1 for out-of-bounds or non-shape neighbors\n const neighborIndices = new Int32Array(pixelCount * 4);\n\n for (let i = 0; i < pixelCount; i++) {\n const idx = interiorPixels[i]!;\n const x = idx % width;\n const y = Math.floor(idx / width);\n\n // East neighbor\n neighborIndices[i * 4 + 0] = x < width - 1 && shapeMask[idx + 1] ? idx + 1 : -1;\n // West neighbor\n neighborIndices[i * 4 + 1] = x > 0 && shapeMask[idx - 1] ? idx - 1 : -1;\n // North neighbor\n neighborIndices[i * 4 + 2] = y > 0 && shapeMask[idx - width] ? idx - width : -1;\n // South neighbor\n neighborIndices[i * 4 + 3] = y < height - 1 && shapeMask[idx + width] ? idx + width : -1;\n }\n\n return {\n interiorPixels,\n boundaryPixels,\n pixelCount,\n neighborIndices,\n };\n}\n\nfunction solvePoissonSparse(\n sparseData: SparsePixelData,\n shapeMask: Uint8Array,\n boundaryMask: Uint8Array,\n width: number,\n height: number\n): Float32Array {\n // This controls how smooth the falloff gradient will be and extend into the shape\n const ITERATIONS = POISSON_CONFIG_OPTIMIZED.iterations;\n\n // Keep C constant - only iterations control gradient spread\n const C = 0.01;\n\n const u = new Float32Array(width * height);\n const { interiorPixels, neighborIndices, pixelCount } = sparseData;\n\n // Performance tracking\n const startTime = performance.now();\n\n // Red-Black SOR for better symmetry with fewer iterations\n // omega between 1.8-1.95 typically gives best convergence for Poisson\n const omega = 1.9;\n\n // Pre-classify pixels as red or black for efficient processing\n const redPixels: number[] = [];\n const blackPixels: number[] = [];\n\n for (let i = 0; i < pixelCount; i++) {\n const idx = interiorPixels[i]!;\n const x = idx % width;\n const y = Math.floor(idx / width);\n\n if ((x + y) % 2 === 0) {\n redPixels.push(i);\n } else {\n blackPixels.push(i);\n }\n }\n\n for (let iter = 0; iter < ITERATIONS; iter++) {\n // Red pass: update red pixels\n for (const i of redPixels) {\n const idx = interiorPixels[i]!;\n\n // Get precomputed neighbor indices\n const eastIdx = neighborIndices[i * 4 + 0]!;\n const westIdx = neighborIndices[i * 4 + 1]!;\n const northIdx = neighborIndices[i * 4 + 2]!;\n const southIdx = neighborIndices[i * 4 + 3]!;\n\n // Sum neighbors (use 0 for out-of-bounds)\n let sumN = 0;\n if (eastIdx >= 0) sumN += u[eastIdx]!;\n if (westIdx >= 0) sumN += u[westIdx]!;\n if (northIdx >= 0) sumN += u[northIdx]!;\n if (southIdx >= 0) sumN += u[southIdx]!;\n\n // SOR update: blend new value with old value\n const newValue = (C + sumN) / 4;\n u[idx] = omega * newValue + (1 - omega) * u[idx]!;\n }\n\n // Black pass: update black pixels\n for (const i of blackPixels) {\n const idx = interiorPixels[i]!;\n\n // Get precomputed neighbor indices\n const eastIdx = neighborIndices[i * 4 + 0]!;\n const westIdx = neighborIndices[i * 4 + 1]!;\n const northIdx = neighborIndices[i * 4 + 2]!;\n const southIdx = neighborIndices[i * 4 + 3]!;\n\n // Sum neighbors (use 0 for out-of-bounds)\n let sumN = 0;\n if (eastIdx >= 0) sumN += u[eastIdx]!;\n if (westIdx >= 0) sumN += u[westIdx]!;\n if (northIdx >= 0) sumN += u[northIdx]!;\n if (southIdx >= 0) sumN += u[southIdx]!;\n\n // SOR update: blend new value with old value\n const newValue = (C + sumN) / 4;\n u[idx] = omega * newValue + (1 - omega) * u[idx]!;\n }\n }\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n const elapsed = performance.now() - startTime;\n\n console.log(`[Optimized Poisson Solver (SOR \u03C9=${omega})]`);\n console.log(` Working size: ${width}\u00D7${height}`);\n console.log(` Iterations: ${ITERATIONS}`);\n console.log(` Time: ${elapsed.toFixed(2)}ms`);\n console.log(` Interior pixels processed: ${pixelCount}`);\n console.log(` Speed: ${((ITERATIONS * pixelCount) / (elapsed * 1000)).toFixed(2)} Mpixels/sec`);\n }\n\n return u;\n}\n\nexport interface LiquidMetalUniforms extends ShaderSizingUniforms {\n u_colorBack: [number, number, number, number];\n u_colorTint: [number, number, number, number];\n u_image: HTMLImageElement | string | undefined;\n u_repetition: number;\n u_shiftRed: number;\n u_shiftBlue: number;\n u_contour: number;\n u_softness: number;\n u_distortion: number;\n u_angle: number;\n u_shape: (typeof LiquidMetalShapes)[LiquidMetalShape];\n u_isImage: boolean;\n}\n\nexport interface LiquidMetalParams extends ShaderSizingParams, ShaderMotionParams {\n colorBack?: string;\n colorTint?: string;\n image?: HTMLImageElement | string | undefined;\n repetition?: number;\n shiftRed?: number;\n shiftBlue?: number;\n contour?: number;\n softness?: number;\n distortion?: number;\n angle?: number;\n shape?: LiquidMetalShape;\n}\n\nexport const LiquidMetalShapes = {\n none: 0,\n circle: 1,\n daisy: 2,\n diamond: 3,\n metaballs: 4,\n} as const;\n\nexport type LiquidMetalShape = keyof typeof LiquidMetalShapes;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, rotation2, simplexNoise, colorBandingFix } from '../shader-utils.js';\n\n/**\n * Futuristic liquid metal material applied to uploaded logo or abstract shape.\n * Fluid motion imitation applied over user image with animated stripe pattern\n * getting distorted along shape edges.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_resolution (vec2): Canvas resolution in pixels\n * - u_image (sampler2D): Pre-processed source image texture (R = edge gradient, G = opacity)\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colorTint (vec4): Overlay color in RGBA (color burn blending used)\n * - u_repetition (float): Density of pattern stripes (1 to 10)\n * - u_softness (float): Color transition sharpness, 0 = hard edge, 1 = smooth gradient (0 to 1)\n * - u_shiftRed (float): R-channel dispersion (-1 to 1)\n * - u_shiftBlue (float): B-channel dispersion (-1 to 1)\n * - u_distortion (float): Noise distortion over the stripes pattern (0 to 1)\n * - u_contour (float): Strength of the distortion on the shape edges (0 to 1)\n * - u_angle (float): Direction of pattern animation in degrees (0 to 360)\n * - u_shape (float): Predefined shape mask, needs no image (0 = none, 1 = circle, 2 = daisy, 3 = diamond, 4 = metaballs)\n * - u_isImage (bool): Whether an image is being used as the effect mask\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 (used when no image)\n * - v_responsiveUV (vec2): Responsive UV coordinates that adapt to canvas aspect ratio (used for canvas-fill mode)\n * - v_responsiveBoxGivenSize (vec2): TBD\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.1 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 liquidMetalFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform sampler2D u_image;\n\nuniform vec2 u_resolution;\nuniform float u_time;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colorTint;\n\nuniform float u_softness;\nuniform float u_repetition;\nuniform float u_shiftRed;\nuniform float u_shiftBlue;\nuniform float u_distortion;\nuniform float u_contour;\nuniform float u_angle;\n\nuniform float u_shape;\nuniform bool u_isImage;\n\nin vec2 v_objectUV;\nin vec2 v_responsiveUV;\nin vec2 v_responsiveBoxGivenSize;\nin vec2 v_imageUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ rotation2 }\n${ simplexNoise }\n\nfloat getColorChanges(float c1, float c2, float stripe_p, vec3 w, float blur, float bump, float tint) {\n\n float ch = mix(c2, c1, smoothstep(.0, 2. * blur, stripe_p));\n\n float border = w[0];\n ch = mix(ch, c2, smoothstep(border, border + 2. * blur, stripe_p));\n\n if (u_isImage == true) {\n bump = smoothstep(.2, .8, bump);\n }\n border = w[0] + .4 * (1. - bump) * w[1];\n ch = mix(ch, c1, smoothstep(border, border + 2. * blur, stripe_p));\n\n border = w[0] + .5 * (1. - bump) * w[1];\n ch = mix(ch, c2, smoothstep(border, border + 2. * blur, stripe_p));\n\n border = w[0] + w[1];\n ch = mix(ch, c1, smoothstep(border, border + 2. * blur, stripe_p));\n\n float gradient_t = (stripe_p - w[0] - w[1]) / w[2];\n float gradient = mix(c1, c2, smoothstep(0., 1., gradient_t));\n ch = mix(ch, gradient, smoothstep(border, border + .5 * blur, stripe_p));\n\n // Tint color is applied with color burn blending\n ch = mix(ch, 1. - min(1., (1. - ch) / max(tint, 0.0001)), u_colorTint.a);\n return ch;\n}\n\nfloat getImgFrame(vec2 uv, float th) {\n float frame = 1.;\n frame *= smoothstep(0., th, uv.y);\n frame *= 1.0 - smoothstep(1. - th, 1., uv.y);\n frame *= smoothstep(0., th, uv.x);\n frame *= 1.0 - smoothstep(1. - th, 1., uv.x);\n return frame;\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).r;\n sum += w2 * textureGrad(tex, uv + vec2(0.0, r.y), dudx, dudy).r;\n sum += w2 * textureGrad(tex, uv + vec2(-r.x, 0.0), dudx, dudy).r;\n sum += w2 * textureGrad(tex, uv + vec2(r.x, 0.0), dudx, dudy).r;\n\n sum += w1 * textureGrad(tex, uv + vec2(-r.x, -r.y), dudx, dudy).r;\n sum += w1 * textureGrad(tex, uv + vec2(r.x, -r.y), dudx, dudy).r;\n sum += w1 * textureGrad(tex, uv + vec2(-r.x, r.y), dudx, dudy).r;\n sum += w1 * textureGrad(tex, uv + vec2(r.x, r.y), dudx, dudy).r;\n\n return sum / norm;\n}\n\nfloat lst(float edge0, float edge1, float x) {\n return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n}\n\nvoid main() {\n\n const float firstFrameOffset = 2.8;\n float t = .3 * (u_time + firstFrameOffset);\n\n vec2 uv = v_imageUV;\n vec2 dudx = dFdx(v_imageUV);\n vec2 dudy = dFdy(v_imageUV);\n vec4 img = textureGrad(u_image, uv, dudx, dudy);\n\n if (u_isImage == false) {\n uv = v_objectUV + .5;\n uv.y = 1. - uv.y;\n }\n\n float cycleWidth = u_repetition;\n float edge = 0.;\n float contOffset = 1.;\n\n vec2 rotatedUV = uv - vec2(.5);\n float angle = (-u_angle + 70.) * PI / 180.;\n float cosA = cos(angle);\n float sinA = sin(angle);\n rotatedUV = vec2(\n rotatedUV.x * cosA - rotatedUV.y * sinA,\n rotatedUV.x * sinA + rotatedUV.y * cosA\n ) + vec2(.5);\n\n // u_contour is applied in 2 separate ranges:\n // - 0 to .4 sets the edge hardness, saturated above .4 (both branches below)\n // - .5 to 1 warps the stripes direction along the edges, inactive below .5 (see u_contour range 2)\n if (u_isImage == true) {\n float edgeRaw = img.r;\n edge = blurEdge3x3(u_image, uv, dudx, dudy, 6., edgeRaw);\n edge = pow(edge, 1.6);\n edge *= mix(0.0, 1.0, smoothstep(0.0, 0.4, u_contour));\n } else {\n if (u_shape < 1.) {\n // full-fill on canvas\n vec2 borderUV = v_responsiveUV + .5;\n float ratio = v_responsiveBoxGivenSize.x / v_responsiveBoxGivenSize.y;\n vec2 mask = min(borderUV, 1. - borderUV);\n vec2 pixel_thickness = min(250. / v_responsiveBoxGivenSize, vec2(.5));\n float maskX = smoothstep(0.0, pixel_thickness.x, mask.x);\n float maskY = smoothstep(0.0, pixel_thickness.y, mask.y);\n maskX = pow(maskX, .25);\n maskY = pow(maskY, .25);\n edge = clamp(1. - maskX * maskY, 0., 1.);\n\n uv = v_responsiveUV;\n if (ratio > 1.) {\n uv.y /= ratio;\n } else {\n uv.x *= ratio;\n }\n uv += .5;\n uv.y = 1. - uv.y;\n\n cycleWidth *= 2.;\n contOffset = 1.5;\n\n } else if (u_shape < 2.) {\n // circle\n vec2 shapeUV = uv - .5;\n shapeUV *= .67;\n edge = pow(clamp(3. * length(shapeUV), 0., 1.), 18.);\n } else if (u_shape < 3.) {\n // daisy\n vec2 shapeUV = uv - .5;\n shapeUV *= 1.68;\n\n float r = length(shapeUV) * 2.;\n float a = atan(shapeUV.y, shapeUV.x) + .2;\n r *= (1. + .05 * sin(3. * a + 2. * t));\n float f = abs(cos(a * 3.));\n edge = smoothstep(f, f + .7, r);\n edge *= edge;\n\n uv *= .8;\n cycleWidth *= 1.6;\n\n } else if (u_shape < 4.) {\n // diamond\n vec2 shapeUV = uv - .5;\n shapeUV = rotate(shapeUV, .25 * PI);\n shapeUV *= 1.42;\n shapeUV += .5;\n vec2 mask = min(shapeUV, 1. - shapeUV);\n vec2 pixel_thickness = vec2(.15);\n float maskX = smoothstep(0.0, pixel_thickness.x, mask.x);\n float maskY = smoothstep(0.0, pixel_thickness.y, mask.y);\n maskX = pow(maskX, .25);\n maskY = pow(maskY, .25);\n edge = clamp(1. - maskX * maskY, 0., 1.);\n } else if (u_shape < 5.) {\n // metaballs\n vec2 shapeUV = uv - .5;\n shapeUV *= 1.3;\n edge = 0.;\n for (int i = 0; i < 5; i++) {\n float fi = float(i);\n float speed = 1.5 + 2./3. * sin(fi * 12.345);\n float angle = -fi * 1.5;\n vec2 dir1 = vec2(cos(angle), sin(angle));\n vec2 dir2 = vec2(cos(angle + 1.57), sin(angle + 1.));\n vec2 traj = .4 * (dir1 * sin(t * speed + fi * 1.23) + dir2 * cos(t * (speed * 0.7) + fi * 2.17));\n float d = length(shapeUV + traj);\n edge += pow(1.0 - clamp(d, 0.0, 1.0), 4.0);\n }\n edge = 1. - smoothstep(.65, .9, edge);\n edge = pow(edge, 4.);\n }\n\n edge = mix(smoothstep(.9 - 2. * fwidth(edge), .9, edge), edge, smoothstep(0.0, 0.4, u_contour));\n\n }\n\n float opacity = 0.;\n if (u_isImage == true) {\n opacity = img.g;\n float frame = getImgFrame(v_imageUV, 0.);\n opacity *= frame;\n } else {\n opacity = 1. - smoothstep(.9 - 2. * fwidth(edge), .9, edge);\n if (u_shape < 2.) {\n edge = 1.2 * edge;\n } else if (u_shape < 5.) {\n edge = 1.8 * pow(edge, 1.5);\n }\n }\n\n float diagBLtoTR = rotatedUV.x - rotatedUV.y;\n float diagTLtoBR = rotatedUV.x + rotatedUV.y;\n\n vec3 color = vec3(0.);\n vec3 color1 = vec3(.98, 0.98, 1.);\n vec3 color2 = vec3(.1, .1, .1 + .1 * smoothstep(.7, 1.3, diagTLtoBR));\n\n vec2 grad_uv = uv - .5;\n\n float dist = length(grad_uv + vec2(0., .2 * diagBLtoTR));\n grad_uv = rotate(grad_uv, (.25 - .2 * diagBLtoTR) * PI);\n float direction = grad_uv.x;\n\n float bump = pow(1.8 * dist, 1.2);\n bump = 1. - bump;\n bump *= pow(uv.y, .3);\n\n\n float thin_strip_1_ratio = .12 / cycleWidth * (1. - .4 * bump);\n float thin_strip_2_ratio = .07 / cycleWidth * (1. + .4 * bump);\n float wide_strip_ratio = (1. - thin_strip_1_ratio - thin_strip_2_ratio);\n\n float thin_strip_1_width = cycleWidth * thin_strip_1_ratio;\n float thin_strip_2_width = cycleWidth * thin_strip_2_ratio;\n\n float noise = snoise(uv - t);\n\n edge += (1. - edge) * u_distortion * noise;\n\n direction += diagBLtoTR;\n float contour = 0.;\n direction -= 2. * noise * diagBLtoTR * (smoothstep(0., 1., edge) * (1.0 - smoothstep(0., 1., edge)));\n // u_contour range 2\n direction *= mix(1., 1. - edge, smoothstep(.5, 1., u_contour));\n direction -= 1.7 * edge * smoothstep(.5, 1., u_contour);\n direction += .2 * pow(u_contour, 4.) * (1.0 - smoothstep(0., 1., edge));\n\n bump *= clamp(pow(uv.y, .1), .3, 1.);\n direction *= (.1 + (1.1 - edge) * bump);\n\n direction *= (.4 + .6 * (1.0 - smoothstep(.5, 1., edge)));\n direction += .18 * (smoothstep(.1, .2, uv.y) * (1.0 - smoothstep(.2, .4, uv.y)));\n direction += .03 * (smoothstep(.1, .2, 1. - uv.y) * (1.0 - smoothstep(.2, .4, 1. - uv.y)));\n\n direction *= (.5 + .5 * pow(uv.y, 2.));\n direction *= cycleWidth;\n direction -= t;\n\n\n float colorDispersion = (1. - bump);\n colorDispersion = clamp(colorDispersion, 0., 1.);\n float dispersionRed = colorDispersion;\n dispersionRed += .03 * bump * noise;\n dispersionRed += 5. * (smoothstep(-.1, .2, uv.y) * (1.0 - smoothstep(.1, .5, uv.y))) * (smoothstep(.4, .6, bump) * (1.0 - smoothstep(.4, 1., bump)));\n dispersionRed -= diagBLtoTR;\n\n float dispersionBlue = colorDispersion;\n dispersionBlue *= 1.3;\n dispersionBlue += (smoothstep(0., .4, uv.y) * (1.0 - smoothstep(.1, .8, uv.y))) * (smoothstep(.4, .6, bump) * (1.0 - smoothstep(.4, .8, bump)));\n dispersionBlue -= .2 * edge;\n\n dispersionRed *= (u_shiftRed / 20.);\n dispersionBlue *= (u_shiftBlue / 20.);\n\n float blur = 0.;\n float rExtraBlur = 0.;\n float gExtraBlur = 0.;\n if (u_isImage == true) {\n float softness = 0.05 * u_softness;\n blur = softness + .5 * smoothstep(1., 10., u_repetition) * smoothstep(.0, 1., edge);\n float smallCanvasT = 1.0 - smoothstep(100., 500., min(u_resolution.x, u_resolution.y));\n blur += smallCanvasT * smoothstep(.0, 1., edge);\n rExtraBlur = softness * (0.05 + .1 * (u_shiftRed / 20.) * bump);\n gExtraBlur = softness * 0.05 / max(0.001, abs(1. - diagBLtoTR));\n } else {\n blur = u_softness / 15. + .3 * contour;\n }\n\n vec3 w = vec3(thin_strip_1_width, thin_strip_2_width, wide_strip_ratio);\n w[1] -= .02 * smoothstep(.0, 1., edge + bump);\n float stripe_r = fract(direction + dispersionRed);\n float r = getColorChanges(color1.r, color2.r, stripe_r, w, blur + fwidth(stripe_r) + rExtraBlur, bump, u_colorTint.r);\n float stripe_g = fract(direction);\n float g = getColorChanges(color1.g, color2.g, stripe_g, w, blur + fwidth(stripe_g) + gExtraBlur, bump, u_colorTint.g);\n float stripe_b = fract(direction - dispersionBlue);\n float b = getColorChanges(color1.b, color2.b, stripe_b, w, blur + fwidth(stripe_b), bump, u_colorTint.b);\n\n color = vec3(r, g, b);\n color *= opacity;\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\n ${ colorBandingFix }\n\n fragColor = vec4(color, opacity);\n}\n`;\n\n// Configuration for Poisson solver\nexport const POISSON_CONFIG_OPTIMIZED = {\n measurePerformance: false, // Set to true to see performance metrics\n workingSize: 512, // Size to solve Poisson at (will upscale to original size)\n iterations: 40, // SOR converges ~2-20x faster than standard Gauss-Seidel\n};\n\n// Precomputed pixel data for sparse processing\ninterface SparsePixelData {\n interiorPixels: Uint32Array; // Indices of interior pixels\n boundaryPixels: Uint32Array; // Indices of boundary pixels\n pixelCount: number;\n // Neighbor indices for each interior pixel (4 neighbors per pixel)\n // Layout: [east, west, north, south] for each pixel\n neighborIndices: Int32Array;\n}\n\nexport function toProcessedLiquidMetal(file: File | string): Promise<{ imageData: ImageData; pngBlob: Blob }> {\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n const isBlob = typeof file === 'string' && file.startsWith('blob:');\n\n return new Promise((resolve, reject) => {\n if (!file || !ctx) {\n reject(new Error('Invalid file or canvas context'));\n return;\n }\n\n const blobContentTypePromise = isBlob && fetch(file).then((res) => res.headers.get('Content-Type'));\n const img = new Image();\n img.crossOrigin = 'anonymous';\n const totalStartTime = performance.now();\n\n img.onload = async () => {\n // Force SVG to load at a high fidelity size if it's an SVG\n let isSVG;\n\n const blobContentType = await blobContentTypePromise;\n\n if (blobContentType) {\n isSVG = blobContentType === 'image/svg+xml';\n } else if (typeof file === 'string') {\n isSVG = file.endsWith('.svg') || file.startsWith('data:image/svg+xml');\n } else {\n isSVG = file.type === 'image/svg+xml';\n }\n\n let originalWidth = img.width || img.naturalWidth;\n let originalHeight = img.height || img.naturalHeight;\n\n if (isSVG) {\n // Scale SVG to max dimension while preserving aspect ratio\n const svgMaxSize = 4096;\n const aspectRatio = originalWidth / originalHeight;\n\n if (originalWidth > originalHeight) {\n originalWidth = svgMaxSize;\n originalHeight = svgMaxSize / aspectRatio;\n } else {\n originalHeight = svgMaxSize;\n originalWidth = svgMaxSize * aspectRatio;\n }\n\n img.width = originalWidth;\n img.height = originalHeight;\n }\n\n // Always scale to working resolution for consistency\n const minDimension = Math.min(originalWidth, originalHeight);\n const targetSize = POISSON_CONFIG_OPTIMIZED.workingSize;\n\n // Calculate scale to fit within workingSize\n const scaleFactor = targetSize / minDimension;\n const width = Math.round(originalWidth * scaleFactor);\n const height = Math.round(originalHeight * scaleFactor);\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n console.log(`[Processing Mode]`);\n console.log(` Original: ${originalWidth}\u00D7${originalHeight}`);\n console.log(` Working: ${width}\u00D7${height} (${(scaleFactor * 100).toFixed(1)}% scale)`);\n if (scaleFactor < 1) {\n console.log(` Speedup: ~${Math.round(1 / (scaleFactor * scaleFactor))}\u00D7`);\n }\n }\n\n canvas.width = originalWidth;\n canvas.height = originalHeight;\n\n // Use a smaller canvas for shape detection and Poisson solving\n const shapeCanvas = document.createElement('canvas');\n shapeCanvas.width = width;\n shapeCanvas.height = height;\n\n const shapeCtx = shapeCanvas.getContext('2d')!;\n shapeCtx.drawImage(img, 0, 0, width, height);\n\n // 1) Build optimized masks using TypedArrays\n const startMask = performance.now();\n\n const shapeImageData = shapeCtx.getImageData(0, 0, width, height);\n const data = shapeImageData.data;\n\n // Use Uint8Array for masks (1 byte per pixel vs 8+ bytes for boolean array)\n const shapeMask = new Uint8Array(width * height);\n const boundaryMask = new Uint8Array(width * height);\n\n // First pass: identify shape pixels\n let shapePixelCount = 0;\n for (let i = 0, idx = 0; i < data.length; i += 4, idx++) {\n const a = data[i + 3];\n const isShape = a === 0 ? 0 : 1;\n shapeMask[idx] = isShape;\n shapePixelCount += isShape;\n }\n\n // 2) Optimized boundary detection using sparse approach\n // Only check shape pixels, not all pixels\n const boundaryIndices: number[] = [];\n const interiorIndices: number[] = [];\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = y * width + x;\n if (!shapeMask[idx]) continue;\n\n // Check if pixel is on boundary (optimized: early exit)\n let isBoundary = false;\n\n // Check 4-connected neighbors first (most common case)\n if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {\n isBoundary = true;\n } else {\n // Check all 8 neighbors (including diagonals) for comprehensive boundary detection\n isBoundary =\n !shapeMask[idx - 1] || // left\n !shapeMask[idx + 1] || // right\n !shapeMask[idx - width] || // top\n !shapeMask[idx + width] || // bottom\n !shapeMask[idx - width - 1] || // top-left\n !shapeMask[idx - width + 1] || // top-right\n !shapeMask[idx + width - 1] || // bottom-left\n !shapeMask[idx + width + 1]; // bottom-right\n }\n\n if (isBoundary) {\n boundaryMask[idx] = 1;\n boundaryIndices.push(idx);\n } else {\n interiorIndices.push(idx);\n }\n }\n }\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n console.log(`[Mask Building] Time: ${(performance.now() - startMask).toFixed(2)}ms`);\n console.log(\n ` Shape pixels: ${shapePixelCount} / ${width * height} (${((shapePixelCount / (width * height)) * 100).toFixed(1)}%)`\n );\n console.log(` Interior pixels: ${interiorIndices.length}`);\n console.log(` Boundary pixels: ${boundaryIndices.length}`);\n }\n\n // 3) Precompute sparse data structure for solver\n const sparseData = buildSparseData(\n shapeMask,\n boundaryMask,\n new Uint32Array(interiorIndices),\n new Uint32Array(boundaryIndices),\n width,\n height\n );\n\n // 4) Solve Poisson equation with optimized sparse solver\n const startSolve = performance.now();\n const u = solvePoissonSparse(sparseData, shapeMask, boundaryMask, width, height);\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n console.log(`[Poisson Solve] Time: ${(performance.now() - startSolve).toFixed(2)}ms`);\n }\n\n // 5) Generate output image\n let maxVal = 0;\n let finalImageData: ImageData;\n\n // Only check shape pixels for max value\n for (let i = 0; i < interiorIndices.length; i++) {\n const idx = interiorIndices[i]!;\n if (u[idx]! > maxVal) maxVal = u[idx]!;\n }\n\n // Create gradient image at working resolution\n const tempCanvas = document.createElement('canvas');\n tempCanvas.width = width;\n tempCanvas.height = height;\n const tempCtx = tempCanvas.getContext('2d')!;\n\n const tempImg = tempCtx.createImageData(width, height);\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = y * width + x;\n const px = idx * 4;\n\n if (!shapeMask[idx]) {\n tempImg.data[px] = 255;\n tempImg.data[px + 1] = 255;\n tempImg.data[px + 2] = 255;\n tempImg.data[px + 3] = 0; // Alpha = 0 for background\n } else {\n const poissonRatio = u[idx]! / maxVal;\n const gray = 255 * (1 - poissonRatio);\n tempImg.data[px] = gray;\n tempImg.data[px + 1] = gray;\n tempImg.data[px + 2] = gray;\n tempImg.data[px + 3] = 255; // Alpha = 255 for shape\n }\n }\n }\n tempCtx.putImageData(tempImg, 0, 0);\n\n // Upscale to original resolution with smooth interpolation\n ctx.imageSmoothingEnabled = true;\n ctx.imageSmoothingQuality = 'high';\n ctx.drawImage(tempCanvas, 0, 0, width, height, 0, 0, originalWidth, originalHeight);\n\n // Now get the upscaled image data for final output\n const outImg = ctx.getImageData(0, 0, originalWidth, originalHeight);\n\n // Re-apply edges from original resolution with anti-aliasing\n // This ensures edges are pixel-perfect while gradient is smooth\n const originalCanvas = document.createElement('canvas');\n originalCanvas.width = originalWidth;\n originalCanvas.height = originalHeight;\n const originalCtx = originalCanvas.getContext('2d')!;\n // originalCtx.fillStyle = \"white\";\n // originalCtx.fillRect(0, 0, originalWidth, originalHeight);\n originalCtx.drawImage(img, 0, 0, originalWidth, originalHeight);\n const originalData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);\n\n // Process each pixel: Red channel = gradient, Alpha channel = original alpha\n for (let i = 0; i < outImg.data.length; i += 4) {\n const a = originalData.data[i + 3]!;\n // Use only alpha to determine background vs shape\n const upscaledAlpha = outImg.data[i + 3]!;\n if (a === 0) {\n // Background pixel\n outImg.data[i] = 255;\n outImg.data[i + 1] = 0;\n } else {\n // Red channel carries the gradient\n // Check if upscale missed this pixel by looking at alpha channel\n // If upscaled alpha is 0, the low-res version thought this was background\n outImg.data[i] = upscaledAlpha === 0 ? 0 : outImg.data[i]!; // gradient or 0\n outImg.data[i + 1] = a; // original alpha\n }\n\n // Unused channels fixed\n outImg.data[i + 2] = 255;\n outImg.data[i + 3] = 255;\n }\n\n ctx.putImageData(outImg, 0, 0);\n finalImageData = outImg;\n canvas.toBlob((blob) => {\n if (!blob) {\n reject(new Error('Failed to create PNG blob'));\n return;\n }\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n const totalTime = performance.now() - totalStartTime;\n console.log(`[Total Processing Time] ${totalTime.toFixed(2)}ms`);\n if (scaleFactor < 1) {\n const estimatedFullResTime = totalTime * Math.pow((originalWidth * originalHeight) / (width * height), 1.5);\n console.log(`[Estimated time at full resolution] ~${estimatedFullResTime.toFixed(0)}ms`);\n console.log(\n `[Time saved] ~${(estimatedFullResTime - totalTime).toFixed(0)}ms (${Math.round(estimatedFullResTime / totalTime)}\u00D7 faster)`\n );\n }\n }\n\n resolve({\n imageData: finalImageData,\n pngBlob: blob,\n });\n }, 'image/png');\n };\n\n img.onerror = () => reject(new Error('Failed to load image'));\n img.src = typeof file === 'string' ? file : URL.createObjectURL(file);\n });\n}\n\nfunction buildSparseData(\n shapeMask: Uint8Array,\n boundaryMask: Uint8Array,\n interiorPixels: Uint32Array,\n boundaryPixels: Uint32Array,\n width: number,\n height: number\n): SparsePixelData {\n const pixelCount = interiorPixels.length;\n\n // Build neighbor indices for sparse processing\n // For each interior pixel, store indices of its 4 neighbors\n // Use -1 for out-of-bounds or non-shape neighbors\n const neighborIndices = new Int32Array(pixelCount * 4);\n\n for (let i = 0; i < pixelCount; i++) {\n const idx = interiorPixels[i]!;\n const x = idx % width;\n const y = Math.floor(idx / width);\n\n // East neighbor\n neighborIndices[i * 4 + 0] = x < width - 1 && shapeMask[idx + 1] ? idx + 1 : -1;\n // West neighbor\n neighborIndices[i * 4 + 1] = x > 0 && shapeMask[idx - 1] ? idx - 1 : -1;\n // North neighbor\n neighborIndices[i * 4 + 2] = y > 0 && shapeMask[idx - width] ? idx - width : -1;\n // South neighbor\n neighborIndices[i * 4 + 3] = y < height - 1 && shapeMask[idx + width] ? idx + width : -1;\n }\n\n return {\n interiorPixels,\n boundaryPixels,\n pixelCount,\n neighborIndices,\n };\n}\n\nfunction solvePoissonSparse(\n sparseData: SparsePixelData,\n shapeMask: Uint8Array,\n boundaryMask: Uint8Array,\n width: number,\n height: number\n): Float32Array {\n // This controls how smooth the falloff gradient will be and extend into the shape\n const ITERATIONS = POISSON_CONFIG_OPTIMIZED.iterations;\n\n // Keep C constant - only iterations control gradient spread\n const C = 0.01;\n\n const u = new Float32Array(width * height);\n const { interiorPixels, neighborIndices, pixelCount } = sparseData;\n\n // Performance tracking\n const startTime = performance.now();\n\n // Red-Black SOR for better symmetry with fewer iterations\n // omega between 1.8-1.95 typically gives best convergence for Poisson\n const omega = 1.9;\n\n // Pre-classify pixels as red or black for efficient processing\n const redPixels: number[] = [];\n const blackPixels: number[] = [];\n\n for (let i = 0; i < pixelCount; i++) {\n const idx = interiorPixels[i]!;\n const x = idx % width;\n const y = Math.floor(idx / width);\n\n if ((x + y) % 2 === 0) {\n redPixels.push(i);\n } else {\n blackPixels.push(i);\n }\n }\n\n for (let iter = 0; iter < ITERATIONS; iter++) {\n // Red pass: update red pixels\n for (const i of redPixels) {\n const idx = interiorPixels[i]!;\n\n // Get precomputed neighbor indices\n const eastIdx = neighborIndices[i * 4 + 0]!;\n const westIdx = neighborIndices[i * 4 + 1]!;\n const northIdx = neighborIndices[i * 4 + 2]!;\n const southIdx = neighborIndices[i * 4 + 3]!;\n\n // Sum neighbors (use 0 for out-of-bounds)\n let sumN = 0;\n if (eastIdx >= 0) sumN += u[eastIdx]!;\n if (westIdx >= 0) sumN += u[westIdx]!;\n if (northIdx >= 0) sumN += u[northIdx]!;\n if (southIdx >= 0) sumN += u[southIdx]!;\n\n // SOR update: blend new value with old value\n const newValue = (C + sumN) / 4;\n u[idx] = omega * newValue + (1 - omega) * u[idx]!;\n }\n\n // Black pass: update black pixels\n for (const i of blackPixels) {\n const idx = interiorPixels[i]!;\n\n // Get precomputed neighbor indices\n const eastIdx = neighborIndices[i * 4 + 0]!;\n const westIdx = neighborIndices[i * 4 + 1]!;\n const northIdx = neighborIndices[i * 4 + 2]!;\n const southIdx = neighborIndices[i * 4 + 3]!;\n\n // Sum neighbors (use 0 for out-of-bounds)\n let sumN = 0;\n if (eastIdx >= 0) sumN += u[eastIdx]!;\n if (westIdx >= 0) sumN += u[westIdx]!;\n if (northIdx >= 0) sumN += u[northIdx]!;\n if (southIdx >= 0) sumN += u[southIdx]!;\n\n // SOR update: blend new value with old value\n const newValue = (C + sumN) / 4;\n u[idx] = omega * newValue + (1 - omega) * u[idx]!;\n }\n }\n\n if (POISSON_CONFIG_OPTIMIZED.measurePerformance) {\n const elapsed = performance.now() - startTime;\n\n console.log(`[Optimized Poisson Solver (SOR \u03C9=${omega})]`);\n console.log(` Working size: ${width}\u00D7${height}`);\n console.log(` Iterations: ${ITERATIONS}`);\n console.log(` Time: ${elapsed.toFixed(2)}ms`);\n console.log(` Interior pixels processed: ${pixelCount}`);\n console.log(` Speed: ${((ITERATIONS * pixelCount) / (elapsed * 1000)).toFixed(2)} Mpixels/sec`);\n }\n\n return u;\n}\n\nexport interface LiquidMetalUniforms extends ShaderSizingUniforms {\n u_colorBack: [number, number, number, number];\n u_colorTint: [number, number, number, number];\n u_image: HTMLImageElement | string | undefined;\n u_repetition: number;\n u_shiftRed: number;\n u_shiftBlue: number;\n u_contour: number;\n u_softness: number;\n u_distortion: number;\n u_angle: number;\n u_shape: (typeof LiquidMetalShapes)[LiquidMetalShape];\n u_isImage: boolean;\n}\n\nexport interface LiquidMetalParams extends ShaderSizingParams, ShaderMotionParams {\n colorBack?: string;\n colorTint?: string;\n image?: HTMLImageElement | string | undefined;\n repetition?: number;\n shiftRed?: number;\n shiftBlue?: number;\n contour?: number;\n softness?: number;\n distortion?: number;\n angle?: number;\n shape?: LiquidMetalShape;\n}\n\nexport const LiquidMetalShapes = {\n none: 0,\n circle: 1,\n daisy: 2,\n diamond: 3,\n metaballs: 4,\n} as const;\n\nexport type LiquidMetalShape = keyof typeof LiquidMetalShapes;\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -62,8 +62,10 @@ void main() {
62
62
  uv += .5;
63
63
  vec2 grainUV = uv * 1000.;
64
64
 
65
- float grain = noise(grainUV, vec2(0.));
66
- float mixerGrain = .4 * u_grainMixer * (grain - .5);
65
+ float mixerGrain = 0.;
66
+ if (u_grainMixer > 0.) {
67
+ mixerGrain = .4 * u_grainMixer * (noise(grainUV, vec2(0.)) - .5);
68
+ }
67
69
 
68
70
  const float firstFrameOffset = 41.5;
69
71
  float t = .5 * (u_time + firstFrameOffset);
@@ -104,17 +106,19 @@ void main() {
104
106
  color /= max(1e-4, totalWeight);
105
107
  opacity /= max(1e-4, totalWeight);
106
108
 
107
- float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.));
108
- grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);
109
- grainOverlay = pow(grainOverlay, 1.3);
109
+ if (u_grainOverlay > 0.) {
110
+ float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.));
111
+ grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5);
112
+ grainOverlay = pow(grainOverlay, 1.3);
110
113
 
111
- float grainOverlayV = grainOverlay * 2. - 1.;
112
- vec3 grainOverlayColor = vec3(step(0., grainOverlayV));
113
- float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV);
114
- grainOverlayStrength = pow(grainOverlayStrength, .8);
115
- color = mix(color, grainOverlayColor, .35 * grainOverlayStrength);
114
+ float grainOverlayV = grainOverlay * 2. - 1.;
115
+ vec3 grainOverlayColor = vec3(step(0., grainOverlayV));
116
+ float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV);
117
+ grainOverlayStrength = pow(grainOverlayStrength, .8);
118
+ color = mix(color, grainOverlayColor, .35 * grainOverlayStrength);
116
119
 
117
- opacity += .5 * grainOverlayStrength;
120
+ opacity += .5 * grainOverlayStrength;
121
+ }
118
122
  opacity = clamp(opacity, 0., 1.);
119
123
 
120
124
  fragColor = vec4(color, opacity);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/shaders/mesh-gradient.ts"],
4
- "sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport {\n type ShaderSizingParams,\n type ShaderSizingUniforms,\n} from '../shader-sizing.js';\nimport { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js';\n\nexport const meshGradientMeta = {\n maxColorCount: 10,\n} as const;\n\n/**\n * A flowing composition of color spots, moving along distinct trajectories\n * and transformed by organic distortion.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_colors (vec4[]): Up to 10 color spots in RGBA\n * - u_colorsCount (float): Number of active colors\n * - u_distortion (float): Power of organic noise distortion (0 to 1)\n * - u_swirl (float): Power of vortex distortion (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 *\n * Vertex shader outputs (used in fragment shader):\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 *\n */\n\n// language=GLSL\nexport const meshGradientFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform float u_time;\n\nuniform vec4 u_colors[${meshGradientMeta.maxColorCount}];\nuniform float u_colorsCount;\n\nuniform float u_distortion;\nuniform float u_swirl;\nuniform float u_grainMixer;\nuniform float u_grainOverlay;\n\nin vec2 v_objectUV;\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 noise(vec2 n, vec2 seedOffset) {\n return valueNoise(n + seedOffset);\n}\n\nvec2 getPosition(int i, float t) {\n float a = float(i) * .37;\n float b = .6 + fract(float(i) / 3.) * .9;\n float c = .8 + fract(float(i + 1) / 4.);\n\n float x = sin(t * b + a);\n float y = cos(t * c + a * 1.5);\n\n return .5 + .5 * vec2(x, y);\n}\n\nvoid main() {\n vec2 uv = v_objectUV;\n uv += .5;\n vec2 grainUV = uv * 1000.;\n\n float grain = noise(grainUV, vec2(0.));\n float mixerGrain = .4 * u_grainMixer * (grain - .5);\n\n const float firstFrameOffset = 41.5;\n float t = .5 * (u_time + firstFrameOffset);\n\n float radius = smoothstep(0., 1., length(uv - .5));\n float center = 1. - radius;\n for (float i = 1.; i <= 2.; i++) {\n uv.x += u_distortion * center / i * sin(t + i * .4 * smoothstep(.0, 1., uv.y)) * cos(.2 * t + i * 2.4 * smoothstep(.0, 1., uv.y));\n uv.y += u_distortion * center / i * cos(t + i * 2. * smoothstep(.0, 1., uv.x));\n }\n\n vec2 uvRotated = uv;\n uvRotated -= vec2(.5);\n float angle = 3. * u_swirl * radius;\n uvRotated = rotate(uvRotated, -angle);\n uvRotated += vec2(.5);\n\n vec3 color = vec3(0.);\n float opacity = 0.;\n float totalWeight = 0.;\n\n for (int i = 0; i < ${meshGradientMeta.maxColorCount}; i++) {\n if (i >= int(u_colorsCount)) break;\n\n vec2 pos = getPosition(i, t) + mixerGrain;\n vec3 colorFraction = u_colors[i].rgb * u_colors[i].a;\n float opacityFraction = u_colors[i].a;\n\n float dist = length(uvRotated - pos);\n\n dist = pow(dist, 3.5);\n float weight = 1. / (dist + 1e-3);\n color += colorFraction * weight;\n opacity += opacityFraction * weight;\n totalWeight += weight;\n }\n\n color /= max(1e-4, totalWeight);\n opacity /= max(1e-4, totalWeight);\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, .35 * grainOverlayStrength);\n\n opacity += .5 * grainOverlayStrength;\n opacity = clamp(opacity, 0., 1.);\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport interface MeshGradientUniforms extends ShaderSizingUniforms {\n u_colors: vec4[];\n u_colorsCount: number;\n u_distortion: number;\n u_swirl: number;\n u_grainMixer: number;\n u_grainOverlay: number;\n}\n\nexport interface MeshGradientParams extends ShaderSizingParams, ShaderMotionParams {\n colors?: string[];\n distortion?: number;\n swirl?: number;\n grainMixer?: number;\n grainOverlay?: number;\n}\n"],
5
- "mappings": ";;;;;AAEA;AAAA,OAGO;AACP,SAAS,WAAW,WAAW,wBAAwB;AAEhD,MAAM,mBAAmB;AAAA,EAC9B,eAAe;AACjB;AAkCO,MAAM,6BAAqC;AAAA;AAAA;AAAA;AAAA;AAAA,wBAK1B,iBAAiB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWpD,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,wBA0DM,iBAAiB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
4
+ "sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport {\n type ShaderSizingParams,\n type ShaderSizingUniforms,\n} from '../shader-sizing.js';\nimport { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js';\n\nexport const meshGradientMeta = {\n maxColorCount: 10,\n} as const;\n\n/**\n * A flowing composition of color spots, moving along distinct trajectories\n * and transformed by organic distortion.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_colors (vec4[]): Up to 10 color spots in RGBA\n * - u_colorsCount (float): Number of active colors\n * - u_distortion (float): Power of organic noise distortion (0 to 1)\n * - u_swirl (float): Power of vortex distortion (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 *\n * Vertex shader outputs (used in fragment shader):\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 *\n */\n\n// language=GLSL\nexport const meshGradientFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform float u_time;\n\nuniform vec4 u_colors[${meshGradientMeta.maxColorCount}];\nuniform float u_colorsCount;\n\nuniform float u_distortion;\nuniform float u_swirl;\nuniform float u_grainMixer;\nuniform float u_grainOverlay;\n\nin vec2 v_objectUV;\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 noise(vec2 n, vec2 seedOffset) {\n return valueNoise(n + seedOffset);\n}\n\nvec2 getPosition(int i, float t) {\n float a = float(i) * .37;\n float b = .6 + fract(float(i) / 3.) * .9;\n float c = .8 + fract(float(i + 1) / 4.);\n\n float x = sin(t * b + a);\n float y = cos(t * c + a * 1.5);\n\n return .5 + .5 * vec2(x, y);\n}\n\nvoid main() {\n vec2 uv = v_objectUV;\n uv += .5;\n vec2 grainUV = uv * 1000.;\n\n float mixerGrain = 0.;\n if (u_grainMixer > 0.) {\n mixerGrain = .4 * u_grainMixer * (noise(grainUV, vec2(0.)) - .5);\n }\n\n const float firstFrameOffset = 41.5;\n float t = .5 * (u_time + firstFrameOffset);\n\n float radius = smoothstep(0., 1., length(uv - .5));\n float center = 1. - radius;\n for (float i = 1.; i <= 2.; i++) {\n uv.x += u_distortion * center / i * sin(t + i * .4 * smoothstep(.0, 1., uv.y)) * cos(.2 * t + i * 2.4 * smoothstep(.0, 1., uv.y));\n uv.y += u_distortion * center / i * cos(t + i * 2. * smoothstep(.0, 1., uv.x));\n }\n\n vec2 uvRotated = uv;\n uvRotated -= vec2(.5);\n float angle = 3. * u_swirl * radius;\n uvRotated = rotate(uvRotated, -angle);\n uvRotated += vec2(.5);\n\n vec3 color = vec3(0.);\n float opacity = 0.;\n float totalWeight = 0.;\n\n for (int i = 0; i < ${meshGradientMeta.maxColorCount}; i++) {\n if (i >= int(u_colorsCount)) break;\n\n vec2 pos = getPosition(i, t) + mixerGrain;\n vec3 colorFraction = u_colors[i].rgb * u_colors[i].a;\n float opacityFraction = u_colors[i].a;\n\n float dist = length(uvRotated - pos);\n\n dist = pow(dist, 3.5);\n float weight = 1. / (dist + 1e-3);\n color += colorFraction * weight;\n opacity += opacityFraction * weight;\n totalWeight += weight;\n }\n\n color /= max(1e-4, totalWeight);\n opacity /= max(1e-4, totalWeight);\n\n if (u_grainOverlay > 0.) {\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, .35 * grainOverlayStrength);\n\n opacity += .5 * grainOverlayStrength;\n }\n opacity = clamp(opacity, 0., 1.);\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport interface MeshGradientUniforms extends ShaderSizingUniforms {\n u_colors: vec4[];\n u_colorsCount: number;\n u_distortion: number;\n u_swirl: number;\n u_grainMixer: number;\n u_grainOverlay: number;\n}\n\nexport interface MeshGradientParams extends ShaderSizingParams, ShaderMotionParams {\n colors?: string[];\n distortion?: number;\n swirl?: number;\n grainMixer?: number;\n grainOverlay?: number;\n}\n"],
5
+ "mappings": ";;;;;AAEA;AAAA,OAGO;AACP,SAAS,WAAW,WAAW,wBAAwB;AAEhD,MAAM,mBAAmB;AAAA,EAC9B,eAAe;AACjB;AAkCO,MAAM,6BAAqC;AAAA;AAAA;AAAA;AAAA;AAAA,wBAK1B,iBAAiB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWpD,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,wBA4DM,iBAAiB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -11,9 +11,9 @@ export declare const metaballsMeta: {
11
11
  * Fragment shader uniforms:
12
12
  * - u_time (float): Animation time
13
13
  * - u_colorBack (vec4): Background color in RGBA
14
- * - u_colors (vec4[]): Up to 8 base colors in RGBA
14
+ * - u_colors (vec4[]): Up to 8 base colors in RGBA, applied to the balls in order (colors beyond u_count are unused)
15
15
  * - u_colorsCount (float): Number of active colors
16
- * - u_count (float): Number of balls (1 to 20)
16
+ * - u_count (float): Number of balls, fractional values shrink the last ball (1 to 20)
17
17
  * - u_size (float): Size of the balls (0 to 1)
18
18
  * - u_noiseTexture (sampler2D): Pre-computed randomizer source texture
19
19
  *
@@ -20,7 +20,6 @@ uniform vec4 u_colorBack;
20
20
  uniform vec4 u_colors[${metaballsMeta.maxColorCount}];
21
21
  uniform float u_colorsCount;
22
22
  uniform float u_size;
23
- uniform float u_sizeRange;
24
23
  uniform float u_count;
25
24
 
26
25
  in vec2 v_objectUV;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/shaders/metaballs.ts"],
4
- "sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, textureRandomizerR, colorBandingFix } from '../shader-utils.js';\n\nexport const metaballsMeta = {\n maxColorCount: 8,\n maxBallsCount: 20,\n} as const;\n\n/**\n * Up to 20 colored gooey balls moving around the center and merging into smooth organic shapes.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colors (vec4[]): Up to 8 base colors in RGBA\n * - u_colorsCount (float): Number of active colors\n * - u_count (float): Number of balls (1 to 20)\n * - u_size (float): Size of the balls (0 to 1)\n * - u_noiseTexture (sampler2D): Pre-computed randomizer source texture\n *\n * Vertex shader outputs (used in fragment shader):\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 *\n */\n\n// language=GLSL\nexport const metaballsFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform float u_time;\n\nuniform sampler2D u_noiseTexture;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colors[${ metaballsMeta.maxColorCount }];\nuniform float u_colorsCount;\nuniform float u_size;\nuniform float u_sizeRange;\nuniform float u_count;\n\nin vec2 v_objectUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ textureRandomizerR }\nfloat noise(float x) {\n float i = floor(x);\n float f = fract(x);\n float u = f * f * (3.0 - 2.0 * f);\n vec2 p0 = vec2(i, 0.0);\n vec2 p1 = vec2(i + 1.0, 0.0);\n return mix(randomR(p0), randomR(p1), u);\n}\n\nfloat getBallShape(vec2 uv, vec2 c, float p) {\n float s = .5 * length(uv - c);\n s = 1. - clamp(s, 0., 1.);\n s = pow(s, p);\n return s;\n}\n\nvoid main() {\n vec2 shape_uv = v_objectUV;\n\n shape_uv += .5;\n\n const float firstFrameOffset = 2503.4;\n float t = .2 * (u_time + firstFrameOffset);\n\n vec3 totalColor = vec3(0.);\n float totalShape = 0.;\n float totalOpacity = 0.;\n\n for (int i = 0; i < ${ metaballsMeta.maxBallsCount }; i++) {\n if (i >= int(ceil(u_count))) break;\n\n float idxFract = float(i) / float(${ metaballsMeta.maxBallsCount });\n float angle = TWO_PI * idxFract;\n\n float speed = 1. - .2 * idxFract;\n float noiseX = noise(angle * 10. + float(i) + t * speed);\n float noiseY = noise(angle * 20. + float(i) - t * speed);\n\n vec2 pos = vec2(.5) + 1e-4 + .9 * (vec2(noiseX, noiseY) - .5);\n\n int safeIndex = i % int(u_colorsCount + 0.5);\n vec4 ballColor = u_colors[safeIndex];\n ballColor.rgb *= ballColor.a;\n\n float sizeFrac = 1.;\n if (float(i) > floor(u_count - 1.)) {\n sizeFrac *= fract(u_count);\n }\n\n float shape = getBallShape(shape_uv, pos, 45. - 30. * u_size * sizeFrac);\n shape *= pow(u_size, .2);\n shape = smoothstep(0., 1., shape);\n\n totalColor += ballColor.rgb * shape;\n totalShape += shape;\n totalOpacity += ballColor.a * shape;\n }\n\n totalColor /= max(totalShape, 1e-4);\n totalOpacity /= max(totalShape, 1e-4);\n\n float edge_width = fwidth(totalShape);\n float finalShape = smoothstep(.4, .4 + edge_width, totalShape);\n\n vec3 color = totalColor * finalShape;\n float 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\n ${ colorBandingFix }\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport interface MetaballsUniforms extends ShaderSizingUniforms {\n u_colorBack: [number, number, number, number];\n u_colors: vec4[];\n u_colorsCount: number;\n u_count: number;\n u_size: number;\n u_noiseTexture?: HTMLImageElement;\n}\n\nexport interface MetaballsParams extends ShaderSizingParams, ShaderMotionParams {\n colorBack?: string;\n colors?: string[];\n count?: number;\n size?: number;\n}\n"],
5
- "mappings": ";;;;;AAEA,eAAmE;AACnE,SAAS,WAAW,oBAAoB,uBAAuB;AAExD,MAAM,gBAAgB;AAAA,EAC3B,eAAe;AAAA,EACf,eAAe;AACjB;AAiCO,MAAM,0BAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAQtB,cAAc,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUlD,SAAU;AAAA,EACV,kBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBA6BG,cAAc,aAAc;AAAA;AAAA;AAAA,wCAGZ,cAAc,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAwChE,eAAgB;AAAA;AAAA;AAAA;AAAA;",
4
+ "sourcesContent": ["import type { vec4 } from '../types.js';\nimport type { ShaderMotionParams } from '../shader-mount.js';\nimport { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js';\nimport { declarePI, textureRandomizerR, colorBandingFix } from '../shader-utils.js';\n\nexport const metaballsMeta = {\n maxColorCount: 8,\n maxBallsCount: 20,\n} as const;\n\n/**\n * Up to 20 colored gooey balls moving around the center and merging into smooth organic shapes.\n *\n * Fragment shader uniforms:\n * - u_time (float): Animation time\n * - u_colorBack (vec4): Background color in RGBA\n * - u_colors (vec4[]): Up to 8 base colors in RGBA, applied to the balls in order (colors beyond u_count are unused)\n * - u_colorsCount (float): Number of active colors\n * - u_count (float): Number of balls, fractional values shrink the last ball (1 to 20)\n * - u_size (float): Size of the balls (0 to 1)\n * - u_noiseTexture (sampler2D): Pre-computed randomizer source texture\n *\n * Vertex shader outputs (used in fragment shader):\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 *\n */\n\n// language=GLSL\nexport const metaballsFragmentShader: string = `#version 300 es\nprecision mediump float;\n\nuniform float u_time;\n\nuniform sampler2D u_noiseTexture;\n\nuniform vec4 u_colorBack;\nuniform vec4 u_colors[${ metaballsMeta.maxColorCount }];\nuniform float u_colorsCount;\nuniform float u_size;\nuniform float u_count;\n\nin vec2 v_objectUV;\n\nout vec4 fragColor;\n\n${ declarePI }\n${ textureRandomizerR }\nfloat noise(float x) {\n float i = floor(x);\n float f = fract(x);\n float u = f * f * (3.0 - 2.0 * f);\n vec2 p0 = vec2(i, 0.0);\n vec2 p1 = vec2(i + 1.0, 0.0);\n return mix(randomR(p0), randomR(p1), u);\n}\n\nfloat getBallShape(vec2 uv, vec2 c, float p) {\n float s = .5 * length(uv - c);\n s = 1. - clamp(s, 0., 1.);\n s = pow(s, p);\n return s;\n}\n\nvoid main() {\n vec2 shape_uv = v_objectUV;\n\n shape_uv += .5;\n\n const float firstFrameOffset = 2503.4;\n float t = .2 * (u_time + firstFrameOffset);\n\n vec3 totalColor = vec3(0.);\n float totalShape = 0.;\n float totalOpacity = 0.;\n\n for (int i = 0; i < ${ metaballsMeta.maxBallsCount }; i++) {\n if (i >= int(ceil(u_count))) break;\n\n float idxFract = float(i) / float(${ metaballsMeta.maxBallsCount });\n float angle = TWO_PI * idxFract;\n\n float speed = 1. - .2 * idxFract;\n float noiseX = noise(angle * 10. + float(i) + t * speed);\n float noiseY = noise(angle * 20. + float(i) - t * speed);\n\n vec2 pos = vec2(.5) + 1e-4 + .9 * (vec2(noiseX, noiseY) - .5);\n\n int safeIndex = i % int(u_colorsCount + 0.5);\n vec4 ballColor = u_colors[safeIndex];\n ballColor.rgb *= ballColor.a;\n\n float sizeFrac = 1.;\n if (float(i) > floor(u_count - 1.)) {\n sizeFrac *= fract(u_count);\n }\n\n float shape = getBallShape(shape_uv, pos, 45. - 30. * u_size * sizeFrac);\n shape *= pow(u_size, .2);\n shape = smoothstep(0., 1., shape);\n\n totalColor += ballColor.rgb * shape;\n totalShape += shape;\n totalOpacity += ballColor.a * shape;\n }\n\n totalColor /= max(totalShape, 1e-4);\n totalOpacity /= max(totalShape, 1e-4);\n\n float edge_width = fwidth(totalShape);\n float finalShape = smoothstep(.4, .4 + edge_width, totalShape);\n\n vec3 color = totalColor * finalShape;\n float 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\n ${ colorBandingFix }\n\n fragColor = vec4(color, opacity);\n}\n`;\n\nexport interface MetaballsUniforms extends ShaderSizingUniforms {\n u_colorBack: [number, number, number, number];\n u_colors: vec4[];\n u_colorsCount: number;\n u_count: number;\n u_size: number;\n u_noiseTexture?: HTMLImageElement;\n}\n\nexport interface MetaballsParams extends ShaderSizingParams, ShaderMotionParams {\n colorBack?: string;\n colors?: string[];\n count?: number;\n size?: number;\n}\n"],
5
+ "mappings": ";;;;;AAEA,eAAmE;AACnE,SAAS,WAAW,oBAAoB,uBAAuB;AAExD,MAAM,gBAAgB;AAAA,EAC3B,eAAe;AAAA,EACf,eAAe;AACjB;AAiCO,MAAM,0BAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAQtB,cAAc,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASlD,SAAU;AAAA,EACV,kBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBA6BG,cAAc,aAAc;AAAA;AAAA;AAAA,wCAGZ,cAAc,aAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAwChE,eAAgB;AAAA;AAAA;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -6,13 +6,11 @@ import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-si
6
6
  *
7
7
  * Fragment shader uniforms:
8
8
  * - u_time (float): Animation time
9
- * - u_resolution (vec2): Canvas resolution in pixels
10
- * - u_pixelRatio (float): Device pixel ratio
11
9
  * - u_colorFront (vec4): Graphics highlight color in RGBA
12
10
  * - u_colorMid (vec4): Graphics main color in RGBA
13
11
  * - u_colorBack (vec4): Background color in RGBA
14
12
  * - u_brightness (float): Luminosity of the crossing points (0 to 1)
15
- * - u_contrast (float): Sharpness of the bright-dark transition (0 to 1)
13
+ * - u_contrast (float): Sharpness of the transition between colorMid and colorBack (0 to 1)
16
14
  *
17
15
  * Vertex shader outputs (used in fragment shader):
18
16
  * - v_patternUV (vec2): UV coordinates for pattern with global sizing (rotation, scale, offset, etc) applied