@toriistudio/shader-ui 0.0.10 → 0.0.11

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.
package/dist/index.d.mts CHANGED
@@ -316,6 +316,7 @@ type DitherStreamProps = {
316
316
  children?: React.ReactNode;
317
317
  imageTextureSrc?: string;
318
318
  backgroundImageSrc?: string;
319
+ backgroundImageScale?: number;
319
320
  backgroundDithered?: boolean;
320
321
  projectionSpeed?: number;
321
322
  beamSpeed?: number;
@@ -331,14 +332,16 @@ type DitherStreamProps = {
331
332
  pathAngle?: number;
332
333
  godrayIntensity?: number;
333
334
  };
334
- declare function DitherStream({ width, height, className, style, children, imageTextureSrc, backgroundImageSrc, backgroundDithered, projectionSpeed, beamSpeed, beamDirection, beamColor, beamCenter, beamRadius, beamScale, beamPathShape, beamCustomPathPoints, beamEnabled, pathPos, pathAngle, godrayIntensity, }: DitherStreamProps): react_jsx_runtime.JSX.Element;
335
+ declare function DitherStream({ width, height, className, style, children, imageTextureSrc, backgroundImageSrc, backgroundImageScale, backgroundDithered, projectionSpeed, beamSpeed, beamDirection, beamColor, beamCenter, beamRadius, beamScale, beamPathShape, beamCustomPathPoints, beamEnabled, pathPos, pathAngle, godrayIntensity, }: DitherStreamProps): react_jsx_runtime.JSX.Element;
335
336
 
336
337
  type DitherStreamPathDrawerProps = {
337
338
  enabled: boolean;
338
339
  beamColor?: string;
339
340
  backgroundImageSrc?: string;
341
+ backgroundImageScale?: number;
340
342
  onCommit: (points: Array<[number, number]>) => void;
343
+ onCancel?: () => void;
341
344
  };
342
- declare function DitherStreamPathDrawer({ enabled, beamColor, backgroundImageSrc, onCommit, }: DitherStreamPathDrawerProps): react_jsx_runtime.JSX.Element | null;
345
+ declare function DitherStreamPathDrawer({ enabled, beamColor, backgroundImageSrc, backgroundImageScale, onCommit, onCancel, }: DitherStreamPathDrawerProps): react_jsx_runtime.JSX.Element | null;
343
346
 
344
347
  export { AnimatedDrawingSVG, type CombineShaderMode, DitherPulseRing, DitherStream, DitherStreamPathDrawer, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, RippleWave, ShaderArt, type ShaderArtUniforms, Snow, WANDY_HAND_DEFAULTS, WandyHand };
package/dist/index.d.ts CHANGED
@@ -316,6 +316,7 @@ type DitherStreamProps = {
316
316
  children?: React.ReactNode;
317
317
  imageTextureSrc?: string;
318
318
  backgroundImageSrc?: string;
319
+ backgroundImageScale?: number;
319
320
  backgroundDithered?: boolean;
320
321
  projectionSpeed?: number;
321
322
  beamSpeed?: number;
@@ -331,14 +332,16 @@ type DitherStreamProps = {
331
332
  pathAngle?: number;
332
333
  godrayIntensity?: number;
333
334
  };
334
- declare function DitherStream({ width, height, className, style, children, imageTextureSrc, backgroundImageSrc, backgroundDithered, projectionSpeed, beamSpeed, beamDirection, beamColor, beamCenter, beamRadius, beamScale, beamPathShape, beamCustomPathPoints, beamEnabled, pathPos, pathAngle, godrayIntensity, }: DitherStreamProps): react_jsx_runtime.JSX.Element;
335
+ declare function DitherStream({ width, height, className, style, children, imageTextureSrc, backgroundImageSrc, backgroundImageScale, backgroundDithered, projectionSpeed, beamSpeed, beamDirection, beamColor, beamCenter, beamRadius, beamScale, beamPathShape, beamCustomPathPoints, beamEnabled, pathPos, pathAngle, godrayIntensity, }: DitherStreamProps): react_jsx_runtime.JSX.Element;
335
336
 
336
337
  type DitherStreamPathDrawerProps = {
337
338
  enabled: boolean;
338
339
  beamColor?: string;
339
340
  backgroundImageSrc?: string;
341
+ backgroundImageScale?: number;
340
342
  onCommit: (points: Array<[number, number]>) => void;
343
+ onCancel?: () => void;
341
344
  };
342
- declare function DitherStreamPathDrawer({ enabled, beamColor, backgroundImageSrc, onCommit, }: DitherStreamPathDrawerProps): react_jsx_runtime.JSX.Element | null;
345
+ declare function DitherStreamPathDrawer({ enabled, beamColor, backgroundImageSrc, backgroundImageScale, onCommit, onCancel, }: DitherStreamPathDrawerProps): react_jsx_runtime.JSX.Element | null;
343
346
 
344
347
  export { AnimatedDrawingSVG, type CombineShaderMode, DitherPulseRing, DitherStream, DitherStreamPathDrawer, EFECTO_ASCII_COMPONENT_DEFAULTS, EFECTO_ASCII_POST_PROCESSING_DEFAULTS, Efecto, type EfectoAsciiColorPalette, type EfectoAsciiStyle, type PublicPostProcessingSettings as EfectoPublicAsciiPostProcessingSettings, FractalFlower, MenuGlitch, type MenuGlitchUniforms, OranoParticles, type OranoParticlesUniforms, RippleWave, ShaderArt, type ShaderArtUniforms, Snow, WANDY_HAND_DEFAULTS, WandyHand };
package/dist/index.js CHANGED
@@ -5261,7 +5261,7 @@ var import_react24 = require("react");
5261
5261
  var THREE22 = __toESM(require("three"));
5262
5262
 
5263
5263
  // src/shaders/dither-godray-dither/fragment.glsl
5264
- var fragment_default16 = "precision highp float;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform sampler2D uBackgroundTexture;\nuniform float uHasBackground;\nuniform float uDitherBackground;\nuniform float uBackgroundAspect;\nuniform vec2 uResolution;\n\nout vec4 fragColor;\n\n// Object-cover UV: fills the canvas, cropping the image centered at (0.5, 0.5).\nvec2 coverUV(vec2 uv, float imageAspect, float canvasAspect) {\n vec2 offset = uv - 0.5;\n if (imageAspect > canvasAspect) {\n offset.x *= canvasAspect / imageAspect;\n } else {\n offset.y *= imageAspect / canvasAspect;\n }\n return offset + 0.5;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ar = uResolution.x / uResolution.y;\n float ac = mix(ar, 1.0 / ar, 0.5);\n\n float gs = 0.005;\n float bg = 1.0 / gs;\n vec2 cellSize = vec2(1.0 / (bg * ar), 1.0 / bg) * ac;\n\n vec2 pos = vec2(0.5);\n vec2 off = uv - pos;\n vec2 cell = floor(off / cellSize);\n vec2 center = (cell + 0.5) * cellSize;\n vec2 pixelUv = center + pos;\n\n // Foreground dither\n vec4 c = texture(uTexture, pixelUv);\n float lum = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722));\n float gm = pow(mix(0.2, 2.2, 0.3), 2.2);\n\n vec2 local = mod(uv - pos, cellSize) / cellSize;\n vec2 ct = local * 2.0 - 1.0;\n float d = length(ct);\n\n float ns = 16.0;\n float si = clamp(floor(lum * ns * gm), 0.0, ns - 1.0);\n float rad = si / ns;\n float alpha = smoothstep(rad + 0.08, rad - 0.08, d);\n\n vec3 tint = (c.rgb - si * 0.04) * 1.4;\n\n // Background \u2014 sampled with cover UVs\n vec2 bgUV = coverUV(uv, uBackgroundAspect, ar);\n vec2 bgCellUV = coverUV(pixelUv, uBackgroundAspect, ar);\n\n float beamReveal = smoothstep(0.4, 0.75, lum);\n vec3 bgRaw = texture(uBackgroundTexture, bgUV).rgb;\n vec3 bgRawColor = mix(vec3(0.0), bgRaw, uHasBackground * beamReveal);\n\n // Background content at cell-snapped UV (pixelated)\n vec3 bgCell = texture(uBackgroundTexture, bgCellUV).rgb;\n\n // Dithered mode: background image multiplies the beam tint.\n // bgCell * 0.6 + 0.4 maps [0,1] \u2192 [0.4, 1.0], so:\n // bright bg \u2192 tint \xD7 1.0 (same intensity as raw mode)\n // dark bg \u2192 tint \xD7 0.4 (dark areas of image dim the beam)\n // The beam's hue is always preserved; the background shows as luminance texture.\n vec3 bgCell3 = bgCell * 0.6 + 0.4;\n vec3 bgFiltered = tint * bgCell3;\n vec3 bgDotContent = mix(tint, bgFiltered, uHasBackground);\n vec3 dotColor = mix(tint, bgDotContent, uDitherBackground);\n\n // Gap (between dots): raw bg reveal in raw mode, black in dithered mode.\n vec3 gapColor = bgRawColor * (1.0 - uDitherBackground);\n\n fragColor = vec4(mix(gapColor, dotColor, alpha), 1.0);\n}\n";
5264
+ var fragment_default16 = "precision highp float;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform sampler2D uBackgroundTexture;\nuniform float uHasBackground;\nuniform float uDitherBackground;\nuniform float uBackgroundAspect;\nuniform float uBackgroundScale;\nuniform vec2 uResolution;\n\nout vec4 fragColor;\n\n// Object-cover UV: fills the canvas, cropping the image centered at (0.5, 0.5).\n// scale > 1 zooms in, scale < 1 zooms out.\nvec2 coverUV(vec2 uv, float imageAspect, float canvasAspect, float scale) {\n vec2 offset = uv - 0.5;\n if (imageAspect > canvasAspect) {\n offset.x *= canvasAspect / imageAspect;\n } else {\n offset.y *= imageAspect / canvasAspect;\n }\n return (offset / scale) + 0.5;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ar = uResolution.x / uResolution.y;\n float ac = mix(ar, 1.0 / ar, 0.5);\n\n float gs = 0.005;\n float bg = 1.0 / gs;\n vec2 cellSize = vec2(1.0 / (bg * ar), 1.0 / bg) * ac;\n\n vec2 pos = vec2(0.5);\n vec2 off = uv - pos;\n vec2 cell = floor(off / cellSize);\n vec2 center = (cell + 0.5) * cellSize;\n vec2 pixelUv = center + pos;\n\n // Foreground dither\n vec4 c = texture(uTexture, pixelUv);\n float lum = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722));\n float gm = pow(mix(0.2, 2.2, 0.3), 2.2);\n\n vec2 local = mod(uv - pos, cellSize) / cellSize;\n vec2 ct = local * 2.0 - 1.0;\n float d = length(ct);\n\n float ns = 16.0;\n float si = clamp(floor(lum * ns * gm), 0.0, ns - 1.0);\n float rad = si / ns;\n float alpha = smoothstep(rad + 0.08, rad - 0.08, d);\n\n vec3 tint = (c.rgb - si * 0.04) * 1.4;\n\n // Background \u2014 sampled with cover UVs\n vec2 bgUV = coverUV(uv, uBackgroundAspect, ar, uBackgroundScale);\n vec2 bgCellUV = coverUV(pixelUv, uBackgroundAspect, ar, uBackgroundScale);\n\n float beamReveal = smoothstep(0.4, 0.75, lum);\n vec3 bgRaw = texture(uBackgroundTexture, bgUV).rgb;\n vec3 bgRawColor = mix(vec3(0.0), bgRaw, uHasBackground * beamReveal);\n\n // Background content at cell-snapped UV (pixelated)\n vec3 bgCell = texture(uBackgroundTexture, bgCellUV).rgb;\n\n // Dithered mode: background image multiplies the beam tint.\n // bgCell * 0.6 + 0.4 maps [0,1] \u2192 [0.4, 1.0], so:\n // bright bg \u2192 tint \xD7 1.0 (same intensity as raw mode)\n // dark bg \u2192 tint \xD7 0.4 (dark areas of image dim the beam)\n // The beam's hue is always preserved; the background shows as luminance texture.\n vec3 bgCell3 = bgCell * 0.6 + 0.4;\n vec3 bgFiltered = tint * bgCell3;\n vec3 bgDotContent = mix(tint, bgFiltered, uHasBackground);\n vec3 dotColor = mix(tint, bgDotContent, uDitherBackground);\n\n // Gap (between dots): raw bg reveal in raw mode, black in dithered mode.\n vec3 gapColor = bgRawColor * (1.0 - uDitherBackground);\n\n fragColor = vec4(mix(gapColor, dotColor, alpha), 1.0);\n}\n";
5265
5265
 
5266
5266
  // src/shaders/dither-godray-dither/vertex.glsl
5267
5267
  var vertex_default15 = "out vec2 vTextureCoord;\n\nvoid main() {\n vTextureCoord = uv;\n gl_Position = vec4(position, 1.0);\n}\n";
@@ -5271,6 +5271,7 @@ var import_jsx_runtime25 = require("react/jsx-runtime");
5271
5271
  function DitherStreamDitherPass({
5272
5272
  inputTexture = null,
5273
5273
  backgroundImageSrc,
5274
+ backgroundImageScale = 1,
5274
5275
  ditherBackground = true,
5275
5276
  target = null,
5276
5277
  clear = true,
@@ -5326,6 +5327,7 @@ function DitherStreamDitherPass({
5326
5327
  uHasBackground: { value: 0 },
5327
5328
  uDitherBackground: { value: 1 },
5328
5329
  uBackgroundAspect: { value: 1 },
5330
+ uBackgroundScale: { value: 1 },
5329
5331
  uResolution: { value: new THREE22.Vector2(1, 1) }
5330
5332
  }),
5331
5333
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -5336,6 +5338,7 @@ function DitherStreamDitherPass({
5336
5338
  uniforms.uHasBackground.value = backgroundTexture ? 1 : 0;
5337
5339
  uniforms.uDitherBackground.value = ditherBackground ? 1 : 0;
5338
5340
  uniforms.uBackgroundAspect.value = backgroundAspect;
5341
+ uniforms.uBackgroundScale.value = backgroundImageScale;
5339
5342
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
5340
5343
  ShaderPass,
5341
5344
  {
@@ -5597,6 +5600,7 @@ function DitherStream({
5597
5600
  children,
5598
5601
  imageTextureSrc,
5599
5602
  backgroundImageSrc,
5603
+ backgroundImageScale = 1,
5600
5604
  backgroundDithered = true,
5601
5605
  projectionSpeed = 0.05,
5602
5606
  beamSpeed = 0.1,
@@ -5660,7 +5664,11 @@ function DitherStream({
5660
5664
  },
5661
5665
  {
5662
5666
  component: DitherStreamDitherPass,
5663
- props: { backgroundImageSrc, ditherBackground: backgroundDithered }
5667
+ props: {
5668
+ backgroundImageSrc,
5669
+ backgroundImageScale,
5670
+ ditherBackground: backgroundDithered
5671
+ }
5664
5672
  },
5665
5673
  {
5666
5674
  component: DitherStreamGodRaysPass,
@@ -5729,7 +5737,9 @@ function DitherStreamPathDrawer({
5729
5737
  enabled,
5730
5738
  beamColor = "#aab0f0",
5731
5739
  backgroundImageSrc,
5732
- onCommit
5740
+ backgroundImageScale = 1,
5741
+ onCommit,
5742
+ onCancel
5733
5743
  }) {
5734
5744
  const overlayRef = (0, import_react28.useRef)(null);
5735
5745
  const canvasRef = (0, import_react28.useRef)(null);
@@ -5875,6 +5885,25 @@ function DitherStreamPathDrawer({
5875
5885
  },
5876
5886
  [updatePoints]
5877
5887
  );
5888
+ (0, import_react28.useEffect)(() => {
5889
+ if (!enabled) return;
5890
+ const handleKeyDown = (event) => {
5891
+ if (event.key !== "Escape") return;
5892
+ drawingRef.current = false;
5893
+ pointerIdRef.current = null;
5894
+ lastPixelPointRef.current = null;
5895
+ if (commitTimeoutRef.current) {
5896
+ clearTimeout(commitTimeoutRef.current);
5897
+ commitTimeoutRef.current = null;
5898
+ }
5899
+ updatePoints([]);
5900
+ clearCanvas();
5901
+ setCursor(null);
5902
+ onCancel?.();
5903
+ };
5904
+ document.addEventListener("keydown", handleKeyDown);
5905
+ return () => document.removeEventListener("keydown", handleKeyDown);
5906
+ }, [enabled, clearCanvas, updatePoints, onCancel]);
5878
5907
  (0, import_react28.useEffect)(() => {
5879
5908
  if (enabled) return;
5880
5909
  drawingRef.current = false;
@@ -5916,7 +5945,7 @@ function DitherStreamPathDrawer({
5916
5945
  "div",
5917
5946
  {
5918
5947
  ref: overlayRef,
5919
- className: "absolute inset-0 z-20 cursor-crosshair touch-none",
5948
+ className: "absolute inset-0 z-20 cursor-crosshair touch-none overflow-hidden",
5920
5949
  style: {
5921
5950
  background: backgroundImageSrc ? void 0 : "radial-gradient(circle at center, rgba(102,110,164,0.10), rgba(7,9,17,0.72))",
5922
5951
  border: "1px dashed rgba(188,196,246,0.35)"
@@ -5984,6 +6013,7 @@ function DitherStreamPathDrawer({
5984
6013
  {
5985
6014
  src: backgroundImageSrc,
5986
6015
  className: "pointer-events-none absolute inset-0 h-full w-full object-cover",
6016
+ style: { transform: `scale(${backgroundImageScale})` },
5987
6017
  alt: "",
5988
6018
  "aria-hidden": true
5989
6019
  }
package/dist/index.mjs CHANGED
@@ -5246,7 +5246,7 @@ import { useEffect as useEffect17, useMemo as useMemo17, useState as useState4 }
5246
5246
  import * as THREE22 from "three";
5247
5247
 
5248
5248
  // src/shaders/dither-godray-dither/fragment.glsl
5249
- var fragment_default16 = "precision highp float;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform sampler2D uBackgroundTexture;\nuniform float uHasBackground;\nuniform float uDitherBackground;\nuniform float uBackgroundAspect;\nuniform vec2 uResolution;\n\nout vec4 fragColor;\n\n// Object-cover UV: fills the canvas, cropping the image centered at (0.5, 0.5).\nvec2 coverUV(vec2 uv, float imageAspect, float canvasAspect) {\n vec2 offset = uv - 0.5;\n if (imageAspect > canvasAspect) {\n offset.x *= canvasAspect / imageAspect;\n } else {\n offset.y *= imageAspect / canvasAspect;\n }\n return offset + 0.5;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ar = uResolution.x / uResolution.y;\n float ac = mix(ar, 1.0 / ar, 0.5);\n\n float gs = 0.005;\n float bg = 1.0 / gs;\n vec2 cellSize = vec2(1.0 / (bg * ar), 1.0 / bg) * ac;\n\n vec2 pos = vec2(0.5);\n vec2 off = uv - pos;\n vec2 cell = floor(off / cellSize);\n vec2 center = (cell + 0.5) * cellSize;\n vec2 pixelUv = center + pos;\n\n // Foreground dither\n vec4 c = texture(uTexture, pixelUv);\n float lum = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722));\n float gm = pow(mix(0.2, 2.2, 0.3), 2.2);\n\n vec2 local = mod(uv - pos, cellSize) / cellSize;\n vec2 ct = local * 2.0 - 1.0;\n float d = length(ct);\n\n float ns = 16.0;\n float si = clamp(floor(lum * ns * gm), 0.0, ns - 1.0);\n float rad = si / ns;\n float alpha = smoothstep(rad + 0.08, rad - 0.08, d);\n\n vec3 tint = (c.rgb - si * 0.04) * 1.4;\n\n // Background \u2014 sampled with cover UVs\n vec2 bgUV = coverUV(uv, uBackgroundAspect, ar);\n vec2 bgCellUV = coverUV(pixelUv, uBackgroundAspect, ar);\n\n float beamReveal = smoothstep(0.4, 0.75, lum);\n vec3 bgRaw = texture(uBackgroundTexture, bgUV).rgb;\n vec3 bgRawColor = mix(vec3(0.0), bgRaw, uHasBackground * beamReveal);\n\n // Background content at cell-snapped UV (pixelated)\n vec3 bgCell = texture(uBackgroundTexture, bgCellUV).rgb;\n\n // Dithered mode: background image multiplies the beam tint.\n // bgCell * 0.6 + 0.4 maps [0,1] \u2192 [0.4, 1.0], so:\n // bright bg \u2192 tint \xD7 1.0 (same intensity as raw mode)\n // dark bg \u2192 tint \xD7 0.4 (dark areas of image dim the beam)\n // The beam's hue is always preserved; the background shows as luminance texture.\n vec3 bgCell3 = bgCell * 0.6 + 0.4;\n vec3 bgFiltered = tint * bgCell3;\n vec3 bgDotContent = mix(tint, bgFiltered, uHasBackground);\n vec3 dotColor = mix(tint, bgDotContent, uDitherBackground);\n\n // Gap (between dots): raw bg reveal in raw mode, black in dithered mode.\n vec3 gapColor = bgRawColor * (1.0 - uDitherBackground);\n\n fragColor = vec4(mix(gapColor, dotColor, alpha), 1.0);\n}\n";
5249
+ var fragment_default16 = "precision highp float;\n\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform sampler2D uBackgroundTexture;\nuniform float uHasBackground;\nuniform float uDitherBackground;\nuniform float uBackgroundAspect;\nuniform float uBackgroundScale;\nuniform vec2 uResolution;\n\nout vec4 fragColor;\n\n// Object-cover UV: fills the canvas, cropping the image centered at (0.5, 0.5).\n// scale > 1 zooms in, scale < 1 zooms out.\nvec2 coverUV(vec2 uv, float imageAspect, float canvasAspect, float scale) {\n vec2 offset = uv - 0.5;\n if (imageAspect > canvasAspect) {\n offset.x *= canvasAspect / imageAspect;\n } else {\n offset.y *= imageAspect / canvasAspect;\n }\n return (offset / scale) + 0.5;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n\n float ar = uResolution.x / uResolution.y;\n float ac = mix(ar, 1.0 / ar, 0.5);\n\n float gs = 0.005;\n float bg = 1.0 / gs;\n vec2 cellSize = vec2(1.0 / (bg * ar), 1.0 / bg) * ac;\n\n vec2 pos = vec2(0.5);\n vec2 off = uv - pos;\n vec2 cell = floor(off / cellSize);\n vec2 center = (cell + 0.5) * cellSize;\n vec2 pixelUv = center + pos;\n\n // Foreground dither\n vec4 c = texture(uTexture, pixelUv);\n float lum = dot(c.rgb, vec3(0.2126, 0.7152, 0.0722));\n float gm = pow(mix(0.2, 2.2, 0.3), 2.2);\n\n vec2 local = mod(uv - pos, cellSize) / cellSize;\n vec2 ct = local * 2.0 - 1.0;\n float d = length(ct);\n\n float ns = 16.0;\n float si = clamp(floor(lum * ns * gm), 0.0, ns - 1.0);\n float rad = si / ns;\n float alpha = smoothstep(rad + 0.08, rad - 0.08, d);\n\n vec3 tint = (c.rgb - si * 0.04) * 1.4;\n\n // Background \u2014 sampled with cover UVs\n vec2 bgUV = coverUV(uv, uBackgroundAspect, ar, uBackgroundScale);\n vec2 bgCellUV = coverUV(pixelUv, uBackgroundAspect, ar, uBackgroundScale);\n\n float beamReveal = smoothstep(0.4, 0.75, lum);\n vec3 bgRaw = texture(uBackgroundTexture, bgUV).rgb;\n vec3 bgRawColor = mix(vec3(0.0), bgRaw, uHasBackground * beamReveal);\n\n // Background content at cell-snapped UV (pixelated)\n vec3 bgCell = texture(uBackgroundTexture, bgCellUV).rgb;\n\n // Dithered mode: background image multiplies the beam tint.\n // bgCell * 0.6 + 0.4 maps [0,1] \u2192 [0.4, 1.0], so:\n // bright bg \u2192 tint \xD7 1.0 (same intensity as raw mode)\n // dark bg \u2192 tint \xD7 0.4 (dark areas of image dim the beam)\n // The beam's hue is always preserved; the background shows as luminance texture.\n vec3 bgCell3 = bgCell * 0.6 + 0.4;\n vec3 bgFiltered = tint * bgCell3;\n vec3 bgDotContent = mix(tint, bgFiltered, uHasBackground);\n vec3 dotColor = mix(tint, bgDotContent, uDitherBackground);\n\n // Gap (between dots): raw bg reveal in raw mode, black in dithered mode.\n vec3 gapColor = bgRawColor * (1.0 - uDitherBackground);\n\n fragColor = vec4(mix(gapColor, dotColor, alpha), 1.0);\n}\n";
5250
5250
 
5251
5251
  // src/shaders/dither-godray-dither/vertex.glsl
5252
5252
  var vertex_default15 = "out vec2 vTextureCoord;\n\nvoid main() {\n vTextureCoord = uv;\n gl_Position = vec4(position, 1.0);\n}\n";
@@ -5256,6 +5256,7 @@ import { jsx as jsx25 } from "react/jsx-runtime";
5256
5256
  function DitherStreamDitherPass({
5257
5257
  inputTexture = null,
5258
5258
  backgroundImageSrc,
5259
+ backgroundImageScale = 1,
5259
5260
  ditherBackground = true,
5260
5261
  target = null,
5261
5262
  clear = true,
@@ -5311,6 +5312,7 @@ function DitherStreamDitherPass({
5311
5312
  uHasBackground: { value: 0 },
5312
5313
  uDitherBackground: { value: 1 },
5313
5314
  uBackgroundAspect: { value: 1 },
5315
+ uBackgroundScale: { value: 1 },
5314
5316
  uResolution: { value: new THREE22.Vector2(1, 1) }
5315
5317
  }),
5316
5318
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -5321,6 +5323,7 @@ function DitherStreamDitherPass({
5321
5323
  uniforms.uHasBackground.value = backgroundTexture ? 1 : 0;
5322
5324
  uniforms.uDitherBackground.value = ditherBackground ? 1 : 0;
5323
5325
  uniforms.uBackgroundAspect.value = backgroundAspect;
5326
+ uniforms.uBackgroundScale.value = backgroundImageScale;
5324
5327
  return /* @__PURE__ */ jsx25(
5325
5328
  ShaderPass,
5326
5329
  {
@@ -5582,6 +5585,7 @@ function DitherStream({
5582
5585
  children,
5583
5586
  imageTextureSrc,
5584
5587
  backgroundImageSrc,
5588
+ backgroundImageScale = 1,
5585
5589
  backgroundDithered = true,
5586
5590
  projectionSpeed = 0.05,
5587
5591
  beamSpeed = 0.1,
@@ -5645,7 +5649,11 @@ function DitherStream({
5645
5649
  },
5646
5650
  {
5647
5651
  component: DitherStreamDitherPass,
5648
- props: { backgroundImageSrc, ditherBackground: backgroundDithered }
5652
+ props: {
5653
+ backgroundImageSrc,
5654
+ backgroundImageScale,
5655
+ ditherBackground: backgroundDithered
5656
+ }
5649
5657
  },
5650
5658
  {
5651
5659
  component: DitherStreamGodRaysPass,
@@ -5719,7 +5727,9 @@ function DitherStreamPathDrawer({
5719
5727
  enabled,
5720
5728
  beamColor = "#aab0f0",
5721
5729
  backgroundImageSrc,
5722
- onCommit
5730
+ backgroundImageScale = 1,
5731
+ onCommit,
5732
+ onCancel
5723
5733
  }) {
5724
5734
  const overlayRef = useRef13(null);
5725
5735
  const canvasRef = useRef13(null);
@@ -5865,6 +5875,25 @@ function DitherStreamPathDrawer({
5865
5875
  },
5866
5876
  [updatePoints]
5867
5877
  );
5878
+ useEffect21(() => {
5879
+ if (!enabled) return;
5880
+ const handleKeyDown = (event) => {
5881
+ if (event.key !== "Escape") return;
5882
+ drawingRef.current = false;
5883
+ pointerIdRef.current = null;
5884
+ lastPixelPointRef.current = null;
5885
+ if (commitTimeoutRef.current) {
5886
+ clearTimeout(commitTimeoutRef.current);
5887
+ commitTimeoutRef.current = null;
5888
+ }
5889
+ updatePoints([]);
5890
+ clearCanvas();
5891
+ setCursor(null);
5892
+ onCancel?.();
5893
+ };
5894
+ document.addEventListener("keydown", handleKeyDown);
5895
+ return () => document.removeEventListener("keydown", handleKeyDown);
5896
+ }, [enabled, clearCanvas, updatePoints, onCancel]);
5868
5897
  useEffect21(() => {
5869
5898
  if (enabled) return;
5870
5899
  drawingRef.current = false;
@@ -5906,7 +5935,7 @@ function DitherStreamPathDrawer({
5906
5935
  "div",
5907
5936
  {
5908
5937
  ref: overlayRef,
5909
- className: "absolute inset-0 z-20 cursor-crosshair touch-none",
5938
+ className: "absolute inset-0 z-20 cursor-crosshair touch-none overflow-hidden",
5910
5939
  style: {
5911
5940
  background: backgroundImageSrc ? void 0 : "radial-gradient(circle at center, rgba(102,110,164,0.10), rgba(7,9,17,0.72))",
5912
5941
  border: "1px dashed rgba(188,196,246,0.35)"
@@ -5974,6 +6003,7 @@ function DitherStreamPathDrawer({
5974
6003
  {
5975
6004
  src: backgroundImageSrc,
5976
6005
  className: "pointer-events-none absolute inset-0 h-full w-full object-cover",
6006
+ style: { transform: `scale(${backgroundImageScale})` },
5977
6007
  alt: "",
5978
6008
  "aria-hidden": true
5979
6009
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toriistudio/shader-ui",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Shader components",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",