@pixerate/editor 0.108.0 → 0.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/animation/index.cjs +5 -5
  2. package/dist/animation/index.cjs.map +1 -1
  3. package/dist/animation/index.js +9 -9
  4. package/dist/animation-editor/index.cjs +59 -59
  5. package/dist/animation-editor/index.cjs.map +1 -1
  6. package/dist/animation-editor/index.d.cts +2 -1
  7. package/dist/animation-editor/index.d.ts +2 -1
  8. package/dist/animation-editor/index.js +29 -29
  9. package/dist/blending-DXl06NSx.d.ts +486 -0
  10. package/dist/blending-aOE6Y3UE.d.cts +486 -0
  11. package/dist/canvas/index.cjs +27 -27
  12. package/dist/canvas/index.cjs.map +1 -1
  13. package/dist/canvas/index.js +11 -11
  14. package/dist/{chunk-2FGRTYJN.cjs → chunk-477UVMNR.cjs} +3 -3
  15. package/dist/{chunk-2FGRTYJN.cjs.map → chunk-477UVMNR.cjs.map} +1 -1
  16. package/dist/chunk-7VOHOPLW.cjs +1136 -0
  17. package/dist/chunk-7VOHOPLW.cjs.map +1 -0
  18. package/dist/{chunk-HUQ7EHVI.js → chunk-BLWCAQCW.js} +723 -1975
  19. package/dist/chunk-BLWCAQCW.js.map +1 -0
  20. package/dist/{chunk-SOROZ2BF.js → chunk-DMRSJBJF.js} +1939 -318
  21. package/dist/chunk-DMRSJBJF.js.map +1 -0
  22. package/dist/chunk-FTBZPI77.cjs +1 -0
  23. package/dist/chunk-FTBZPI77.cjs.map +1 -0
  24. package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
  25. package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
  26. package/dist/chunk-JLYX4V57.cjs +2001 -0
  27. package/dist/chunk-JLYX4V57.cjs.map +1 -0
  28. package/dist/chunk-KB2YBCVS.js +2001 -0
  29. package/dist/chunk-KB2YBCVS.js.map +1 -0
  30. package/dist/{chunk-G4BIYE6R.js → chunk-LQEEDHOP.js} +40 -2
  31. package/dist/chunk-LQEEDHOP.js.map +1 -0
  32. package/dist/{chunk-FRD3FUXI.cjs → chunk-MGL4U5WA.cjs} +2144 -523
  33. package/dist/chunk-MGL4U5WA.cjs.map +1 -0
  34. package/dist/{chunk-T3QZ6S47.cjs → chunk-POGBUYWE.cjs} +131 -93
  35. package/dist/chunk-POGBUYWE.cjs.map +1 -0
  36. package/dist/chunk-VKK5VS2T.js +1 -0
  37. package/dist/{chunk-UTEVPQTS.cjs → chunk-W3GXSUSQ.cjs} +746 -1998
  38. package/dist/chunk-W3GXSUSQ.cjs.map +1 -0
  39. package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
  40. package/dist/chunk-XIOMMGPD.cjs.map +1 -0
  41. package/dist/{chunk-JK6M6V7G.js → chunk-Z3YWVUWT.js} +4 -4
  42. package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
  43. package/dist/chunk-Z4BC6M74.js.map +1 -0
  44. package/dist/{export-a9bnF7Cl.d.ts → export-CO2ryu3I.d.ts} +419 -18
  45. package/dist/{export-JGWQEPJn.d.cts → export-DRiLzP_j.d.cts} +419 -18
  46. package/dist/image-editor/index.cjs +1665 -1113
  47. package/dist/image-editor/index.cjs.map +1 -1
  48. package/dist/image-editor/index.d.cts +78 -14
  49. package/dist/image-editor/index.d.ts +78 -14
  50. package/dist/image-editor/index.js +843 -291
  51. package/dist/image-editor/index.js.map +1 -1
  52. package/dist/paint/index.cjs +28 -4
  53. package/dist/paint/index.cjs.map +1 -1
  54. package/dist/paint/index.d.cts +3 -89
  55. package/dist/paint/index.d.ts +3 -89
  56. package/dist/paint/index.js +27 -3
  57. package/dist/paint-editor/index.cjs +1056 -287
  58. package/dist/paint-editor/index.cjs.map +1 -1
  59. package/dist/paint-editor/index.d.cts +304 -22
  60. package/dist/paint-editor/index.d.ts +304 -22
  61. package/dist/paint-editor/index.js +852 -83
  62. package/dist/paint-editor/index.js.map +1 -1
  63. package/dist/raster/index.cjs +7 -3
  64. package/dist/raster/index.cjs.map +1 -1
  65. package/dist/raster/index.d.cts +3 -129
  66. package/dist/raster/index.d.ts +3 -129
  67. package/dist/raster/index.js +6 -2
  68. package/dist/{compositor-D92URMlm.d.cts → render-BEriFuqK.d.cts} +136 -2
  69. package/dist/{compositor-Kt_M4ruo.d.ts → render-tjzKaik-.d.ts} +136 -2
  70. package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
  71. package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -1
  72. package/dist/{svg-SseJTIrA.d.ts → svg-D_cfkhlB.d.cts} +3 -629
  73. package/dist/{svg-ClVIG4T7.d.cts → svg-wIaKLrU0.d.ts} +3 -629
  74. package/dist/types-DtL_qLFz.d.ts +630 -0
  75. package/dist/types-DyQq-WPY.d.cts +630 -0
  76. package/dist/vector/index.cjs +563 -563
  77. package/dist/vector/index.cjs.map +1 -1
  78. package/dist/vector/index.d.cts +4 -2
  79. package/dist/vector/index.d.ts +4 -2
  80. package/dist/vector/index.js +69 -69
  81. package/dist/video-editor/index.cjs +3 -3
  82. package/dist/video-editor/index.js +2 -2
  83. package/package.json +1 -1
  84. package/skills/pixerate-paint-editor/SKILL.md +49 -13
  85. package/dist/chunk-3PHA3UKI.js.map +0 -1
  86. package/dist/chunk-6BU5WU4R.cjs +0 -1
  87. package/dist/chunk-6BU5WU4R.cjs.map +0 -1
  88. package/dist/chunk-DWGZUSES.cjs +0 -694
  89. package/dist/chunk-DWGZUSES.cjs.map +0 -1
  90. package/dist/chunk-FRD3FUXI.cjs.map +0 -1
  91. package/dist/chunk-G4BIYE6R.js.map +0 -1
  92. package/dist/chunk-HUFQD4CB.cjs +0 -734
  93. package/dist/chunk-HUFQD4CB.cjs.map +0 -1
  94. package/dist/chunk-HUQ7EHVI.js.map +0 -1
  95. package/dist/chunk-KKREERP2.js +0 -694
  96. package/dist/chunk-KKREERP2.js.map +0 -1
  97. package/dist/chunk-SOROZ2BF.js.map +0 -1
  98. package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
  99. package/dist/chunk-T3QZ6S47.cjs.map +0 -1
  100. package/dist/chunk-UTEVPQTS.cjs.map +0 -1
  101. package/dist/chunk-XYBJKUAU.js +0 -1
  102. package/dist/mixer-5ziHU8OS.d.cts +0 -282
  103. package/dist/mixer-DNqiYXYQ.d.ts +0 -282
  104. /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
  105. /package/dist/{chunk-JK6M6V7G.js.map → chunk-Z3YWVUWT.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import { B as BlendMode, R as RasterRect, a as TiledSurface, M as MaskSurface, P as PixelSurface, T as TileKey } from './surface-CkKOHGPm.js';
1
+ import { B as BlendMode, R as RasterRect, a as TiledSurface, M as MaskSurface, b as PixelSurface, T as TileKey } from './surface-C8RRg_Bh.js';
2
2
  import { H as Homography, V as Vec2 } from './types-CwMPoRMU.js';
3
3
 
4
4
  /**
@@ -832,4 +832,138 @@ declare class RasterCompositor<L extends CompositorLayer = CompositorLayer, S ex
832
832
  /** Tiles that differ between two surfaces (by tile identity), as tile keys. */
833
833
  declare function changedTiles(a: TiledSurface, b: TiledSurface): TileKey[];
834
834
 
835
- export { TransformTooLargeError as $, type GradientColorStop as A, type BevelContour as B, type CompositorLayer as C, DEFAULT_GLOBAL_LIGHT as D, type EffectPattern as E, type GradientOpacityStop as F, type GroupBlendMode as G, type GradientOverlayEffect as H, type Interpolation as I, type GradientStyle as J, INTERPOLATIONS as K, type LayerLocks as L, type InnerGlowEffect as M, type InnerShadowEffect as N, LAYER_EFFECT_KINDS as O, LayerEffectError as P, MAX_EFFECT_REACH as Q, type RasterLayer as R, MAX_TRANSFORM_PIXELS as S, type OuterGlowEffect as T, type PatternOverlayEffect as U, PatternPlacement as V, PatternTile as W, type ResampleOptions as X, type SatinEffect as Y, type StrokeEffect as Z, type StrokePosition as _, type RasterGroupLayer as a, blendTile as a0, builtinPattern as a1, builtinPatternIds as a2, changedTiles as a3, compositeSurfaceOver as a4, defaultEffectGradient as a5, defaultLayerEffect as a6, effectColorHex as a7, effectReach as a8, effectsFingerprint as a9, type CompositorProfile as aA, type GroupIn as aB, type PixelFilterSpec as aC, type RasterNode as aD, effectsMargin as aa, effectsScale as ab, findLayer as ac, flattenLayers as ad, hasActiveEffects as ae, hashNoise as af, insertLayer as ag, isInterpolation as ah, isLayerEffectKind as ai, isLayerLocked as aj, keysWeight as ak, layerEffectFields as al, layerEffectLabel as am, mapLayer as an, mergeLayerEffect as ao, newEffectId as ap, normalizeLayerEffects as aq, parseEffectColor as ar, reduceSurface as as, scaleLayerEffect as at, transformSurface as au, transformedBounds as av, validateEffectGradient as aw, validateLayerEffect as ax, validateLayerEffects as ay, warpSurface as az, type RasterPixelLayer as b, RasterCompositor as c, type RasterSources as d, type RasterLayerBase as e, type RasterMask as f, type GlobalLight as g, type LayerLocation as h, type LayerEffect as i, type LayerEffects as j, type CompositorOptions as k, type LayerEffectKind as l, type BevelEmbossEffect as m, type BevelStyle as n, type BevelTechnique as o, type BevelTexture as p, type ColorOverlayEffect as q, type CompositeCallOptions as r, type CompositeStats as s, type ContourPoint as t, type DropShadowEffect as u, type EffectContour as v, type EffectFill as w, type EffectGradient as x, type EffectPatternRef as y, type GlowTechnique as z };
835
+ /**
836
+ * CPU renderer for layer effects.
837
+ *
838
+ * Render order, bottom to top (Photoshop's layer-style stack read upwards): drop shadows, outer
839
+ * glows, then the layer itself (content at fill opacity) with pattern, gradient and colour
840
+ * overlays, satin, inner glows, inner shadows, the inside part of strokes and inner bevels on
841
+ * top. The outside part of strokes and outer bevels go beneath the layer, onto the backdrop.
842
+ * Exterior effects blend straight into the backdrop with their own modes; the layer blends with
843
+ * its mode and, in a mode other than Normal, is merged into the backdrop before the interior
844
+ * effects paint over it (Photoshop's "Blend Interior Effects as Group" off). Emboss and pillow
845
+ * emboss then shade the composited result. Layer opacity applies to the whole stack; fill
846
+ * opacity only to the layer's own pixels.
847
+ *
848
+ * Every map depends only on the layer's shape (its alpha after the mask), the effect settings
849
+ * and the global light, never on the backdrop. {@link buildEffectMaps} computes them once over the
850
+ * layer's whole region; {@link compositeLayerEffects} paints any part of that region (a tile) from
851
+ * them, pixel by pixel, so a tiled render equals a whole-image render.
852
+ *
853
+ * Distances: a stroke's outer edge uses the 5 × 5 chamfer below 20 px and an exact Euclidean
854
+ * transform from there (the chamfer's octagonal level sets facet wide strokes); inside strokes and
855
+ * spread / choke keep the chamfer; precise glows and chiselled bevels use the exact transform.
856
+ * Soft falloffs are two box blurs (a tent).
857
+ */
858
+
859
+ interface EffectRect {
860
+ x: number;
861
+ y: number;
862
+ width: number;
863
+ height: number;
864
+ }
865
+ /** Outside stroke width from which the outer edge uses the exact Euclidean transform. */
866
+ declare const STROKE_CHAMFER_MAX = 20;
867
+ /** A contour as a 0..1 LUT (corner points split the spline), or `null` for linear. */
868
+ declare function contourLut(c: EffectContour | undefined): Float32Array | null;
869
+ /** Where a bevel paints: inside the shape, outside it, or both (emboss styles). */
870
+ type BevelPaint = 'inner' | 'outer' | 'both';
871
+ interface EffectContext {
872
+ light: GlobalLight;
873
+ /** A pattern tile by id, or `null` when missing (pattern effects then paint nothing). */
874
+ pattern(id: string): PatternTile | null;
875
+ }
876
+ /** Effect maps of one layer over its region: see {@link buildEffectMaps}. */
877
+ interface EffectMaps {
878
+ /** The region the maps cover (document space). */
879
+ rect: EffectRect;
880
+ /** The enabled effects, Scale Effects applied, in list order. */
881
+ items: LayerEffect[];
882
+ /** Pixel bounds of the shape (alpha above half a step), or `null` when the shape is empty. */
883
+ bounds: EffectRect | null;
884
+ shape: Float32Array;
885
+ /** Per item: the maps it paints through. */
886
+ per: Float32Array[][];
887
+ bevelPaint: (BevelPaint | null)[];
888
+ din: Float32Array | null;
889
+ dout: Float32Array | null;
890
+ doutExact: Float32Array | null;
891
+ /** Frames of gradient strokes by item index. */
892
+ frames: Map<number, EffectRect>;
893
+ /** Approximate size in bytes (cache budgeting). */
894
+ bytes: number;
895
+ }
896
+ /** The enabled effects of `effects` with Scale Effects applied (empty when the master switch is off). */
897
+ declare function activeEffectItems(effects: LayerEffects | undefined | null): LayerEffect[];
898
+ /**
899
+ * Whether every map pixel of `items` depends only on the shape within the effects' margin, so a
900
+ * tile is unchanged while the layer is unchanged within its margin. Not local: anything laid out
901
+ * over the layer's bounds (aligned gradient overlays, gradient strokes, linked patterns) and noise
902
+ * (it speckles any coverage above zero, which picks up the blur's float noise from far away).
903
+ */
904
+ declare function effectsAreLocal(items: readonly LayerEffect[]): boolean;
905
+ /**
906
+ * Builds every map `items` paints through from the layer's alpha `shape` (0..1) over `rect`.
907
+ * `rect` should cover the layer's shape plus the effects' margin.
908
+ */
909
+ declare function buildEffectMaps(items: LayerEffect[], shape: Float32Array, rect: EffectRect, ctx: EffectContext): EffectMaps;
910
+ /**
911
+ * `prev` brought up to date for a shape over `rect` that differs from the one it was built from only
912
+ * inside `changed` (document rects), or `null` when a full {@link buildEffectMaps} is the better
913
+ * deal (the change covers much of the region) or the effects are not local
914
+ * ({@link effectsAreLocal}). `items` must be the ones `prev` was built with, reaching at most
915
+ * `margin` px, and `shapeOver(r)` returns the new shape over `r`.
916
+ *
917
+ * Every map pixel within `margin` of a change, or outside `prev.rect`, is rebuilt from the shape
918
+ * within `margin` of it (a window of the change grown by twice the margin) and pasted in; the rest
919
+ * is kept. A local map pixel depends only on the shape within the margin and the blur sums exactly,
920
+ * so the result paints exactly what a full build over `rect` would. `prev`'s buffers are reused
921
+ * when `rect` is unchanged.
922
+ */
923
+ declare function patchEffectMaps(prev: EffectMaps, items: LayerEffect[], rect: EffectRect, changed: readonly EffectRect[], margin: number, shapeOver: (r: EffectRect) => Float32Array, ctx: EffectContext): EffectMaps | null;
924
+ /** Gradient parameter `t` (0..1) at pixel centre `(x, y)` of a gradient laid out in `frame`. */
925
+ declare function gradientT(style: GradientStyle, angle: number, scale: number, reverse: boolean, offset: {
926
+ x: number;
927
+ y: number;
928
+ }, frame: EffectRect, x: number, y: number): number;
929
+ /**
930
+ * The colours an effect fill paints over `rect`, as straight float RGBA (0..1, `width × height`
931
+ * pixels, row by row): a solid colour, a gradient laid out in `frame` (when aligned with the layer)
932
+ * or `canvas`, or a pattern tiled from `anchor` (when linked). Shape layers paint with it, so their
933
+ * fills and strokes match the overlays of layer effects.
934
+ */
935
+ declare function effectFillColors(fill: EffectFill, rect: EffectRect, frame: EffectRect, canvas: EffectRect, anchor: {
936
+ x: number;
937
+ y: number;
938
+ }, ctx: EffectContext): Float32Array;
939
+ interface EffectCompositeInput {
940
+ /** The accumulator (straight RGBA over `rect`); receives the layer and its effects. */
941
+ backdrop: Uint8ClampedArray;
942
+ /** The layer's pixels over `rect` (straight RGBA, mask applied to alpha, clipped layers applied). */
943
+ content: Uint8ClampedArray;
944
+ rect: EffectRect;
945
+ maps: EffectMaps;
946
+ blend: BlendMode;
947
+ /** Layer opacity: the whole stack. */
948
+ opacity: number;
949
+ /** Fill opacity: the layer's own pixels only. */
950
+ fillOpacity: number;
951
+ /** The canvas (unaligned gradients span it). */
952
+ canvas: EffectRect;
953
+ ctx: EffectContext;
954
+ }
955
+ /** Composites a layer's content and its effects onto `backdrop` in place (see the module comment for the order). */
956
+ declare function compositeLayerEffects(input: EffectCompositeInput): void;
957
+ /**
958
+ * Renders one layer with its effects over a whole region in one pass (no tiles): the reference
959
+ * the per-tile compositor is checked against, and a convenience for one-off renders.
960
+ */
961
+ declare function renderLayerEffects(effects: LayerEffects, layer: {
962
+ content: Uint8ClampedArray;
963
+ rect: EffectRect;
964
+ blend: BlendMode;
965
+ opacity: number;
966
+ fillOpacity?: number;
967
+ }, backdrop: Uint8ClampedArray, canvas: EffectRect, ctx: EffectContext): void;
968
+
969
+ export { type StrokeEffect as $, type EffectRect as A, type BevelContour as B, type CompositorLayer as C, DEFAULT_GLOBAL_LIGHT as D, type EffectFill as E, type GlowTechnique as F, type GroupBlendMode as G, type GradientColorStop as H, type Interpolation as I, type GradientOpacityStop as J, type GradientOverlayEffect as K, type LayerLocks as L, type GradientStyle as M, INTERPOLATIONS as N, type InnerGlowEffect as O, type InnerShadowEffect as P, LAYER_EFFECT_KINDS as Q, type RasterLayer as R, LayerEffectError as S, MAX_EFFECT_REACH as T, MAX_TRANSFORM_PIXELS as U, type OuterGlowEffect as V, type PatternOverlayEffect as W, PatternPlacement as X, PatternTile as Y, type ResampleOptions as Z, type SatinEffect as _, type RasterGroupLayer as a, type StrokePosition as a0, TransformTooLargeError as a1, blendTile as a2, builtinPattern as a3, builtinPatternIds as a4, changedTiles as a5, compositeSurfaceOver as a6, defaultEffectGradient as a7, defaultLayerEffect as a8, effectColorHex as a9, validateLayerEffect as aA, validateLayerEffects as aB, warpSurface as aC, type CompositorProfile as aD, type EffectCompositeInput as aE, type EffectMaps as aF, type GroupIn as aG, type PixelFilterSpec as aH, type RasterNode as aI, STROKE_CHAMFER_MAX as aJ, activeEffectItems as aK, buildEffectMaps as aL, compositeLayerEffects as aM, contourLut as aN, effectFillColors as aO, effectsAreLocal as aP, gradientT as aQ, patchEffectMaps as aR, effectReach as aa, effectsFingerprint as ab, effectsMargin as ac, effectsScale as ad, findLayer as ae, flattenLayers as af, hasActiveEffects as ag, hashNoise as ah, insertLayer as ai, isInterpolation as aj, isLayerEffectKind as ak, isLayerLocked as al, keysWeight as am, layerEffectFields as an, layerEffectLabel as ao, mapLayer as ap, mergeLayerEffect as aq, newEffectId as ar, normalizeLayerEffects as as, parseEffectColor as at, reduceSurface as au, renderLayerEffects as av, scaleLayerEffect as aw, transformSurface as ax, transformedBounds as ay, validateEffectGradient as az, type RasterPixelLayer as b, RasterCompositor as c, type RasterSources as d, type RasterLayerBase as e, type RasterMask as f, type GlobalLight as g, type EffectPattern as h, type LayerLocation as i, type LayerEffect as j, type LayerEffects as k, type CompositorOptions as l, type LayerEffectKind as m, type EffectContext as n, type BevelEmbossEffect as o, type BevelStyle as p, type BevelTechnique as q, type BevelTexture as r, type ColorOverlayEffect as s, type CompositeCallOptions as t, type CompositeStats as u, type ContourPoint as v, type DropShadowEffect as w, type EffectContour as x, type EffectGradient as y, type EffectPatternRef as z };
@@ -1,3 +1,106 @@
1
+ /**
2
+ * Pigment-like colour mixing: blue and yellow make green, red and white make pink, as paint does.
3
+ *
4
+ * Each sRGB colour is lifted to a smooth reflectance spectrum, the spectra are mixed with the
5
+ * Kubelka-Munk two-flux model, and the mixed spectrum is turned back into sRGB. Written from the
6
+ * published literature; no third-party code or data:
7
+ *
8
+ * 1. **Spectral uplift.** A colour's reflectance is a sigmoid of a quadratic in wavelength,
9
+ * `R(λ) = s(c₀λ² + c₁λ + c₂)` with `s(z) = ½ + z / 2√(1 + z²)` (Jakob & Hanika, "A
10
+ * Low-Dimensional Function Space for Efficient Spectral Upsampling", Eurographics 2019). The
11
+ * three coefficients are solved per colour with Newton's method so the spectrum reproduces the
12
+ * colour. Spectra are precomputed lazily on a 17³ lattice over sRGB and interpolated
13
+ * trilinearly in *linear* RGB: colour is linear in reflectance, so an interpolated spectrum
14
+ * still reproduces its colour exactly while staying smooth and inside 0..1.
15
+ * 2. **Mixing.** Each spectrum becomes `K/S = (1 − R)² / 2R` per band. Colours mix with the
16
+ * two-constant form `K/S = Σ cᵢSᵢ(K/S)ᵢ / Σ cᵢSᵢ`, where `cᵢ` is the weight times the colour's
17
+ * tinting strength and `Sᵢ` its scattering, taken from its luminance (light paints such as
18
+ * titanium white scatter strongly, dark ones barely). Back to reflectance with
19
+ * `R = 1 + K/S − √((K/S)² + 2·K/S)`.
20
+ * 3. **Back to sRGB.** The spectrum is weighted by the CIE 1931 2° colour-matching functions
21
+ * (the analytic multi-lobe fit of Wyman, Sloan & Shirley, JCGT 2013) under a 6504 K Planckian
22
+ * illuminant, converted to linear sRGB and normalised so a perfect white reflector is exactly
23
+ * white. Analytic curves keep the CIE's own (CC BY-SA) tables out of the package.
24
+ *
25
+ * Everything is plain TypeScript on typed arrays with no DOM: it runs in Node and Workers and is
26
+ * deterministic. Colours are straight sRGB triples in 0..1, as in `@pixerate/editor/raster`.
27
+ */
28
+ /** An sRGB colour, each channel 0..1 (gamma-encoded, as in CSS). */
29
+ type PigmentRgb = readonly [number, number, number];
30
+ /** Number of spectral bands. */
31
+ declare const PIGMENT_BANDS = 36;
32
+ /** First band's wavelength in nm; bands are 10 nm apart (380..730 nm). */
33
+ declare const PIGMENT_LAMBDA_MIN = 380;
34
+ declare const PIGMENT_LAMBDA_STEP = 10;
35
+ /** Band centre wavelengths in nm. */
36
+ declare const PIGMENT_WAVELENGTHS: Float32Array;
37
+ /**
38
+ * The smooth reflectance spectrum (0..1 per band, `PIGMENT_BANDS` bands from 380 nm in 10 nm
39
+ * steps) of an sRGB colour. `reflectanceToRgb` of the result gives the colour back.
40
+ */
41
+ declare function rgbToReflectance(r: number, g: number, b: number, out?: Float32Array): Float32Array;
42
+ /** The sRGB colour (0..1, clipped to the gamut) of a reflectance spectrum. */
43
+ declare function reflectanceToRgb(reflectance: ArrayLike<number>, out?: number[]): number[];
44
+ /**
45
+ * A colour prepared for mixing: its K/S spectrum and its weight in a mix. Make one per brush or
46
+ * palette colour with `createPigment` and reuse it (`setPigmentColor`) on hot paths.
47
+ */
48
+ interface Pigment {
49
+ /** The sRGB colour it was made from (0..1). */
50
+ readonly rgb: [number, number, number];
51
+ /** Tinting strength (default 1): how strongly it colours a mix, relative to its weight. */
52
+ strength: number;
53
+ /** K/S per band. */
54
+ readonly ks: Float32Array;
55
+ /** Scattering from the colour's luminance (floored), before strength. */
56
+ scatter: number;
57
+ }
58
+ interface PigmentOptions {
59
+ /**
60
+ * Tinting strength, > 0 (default 1). A strength of 4 counts each part as four, so a little of
61
+ * a strong dark pigment tints a lot of white. A colour mixed only with itself is unchanged
62
+ * whatever its strength.
63
+ */
64
+ strength?: number;
65
+ }
66
+ /** Prepares a colour for mixing. */
67
+ declare function createPigment(color: PigmentRgb, options?: PigmentOptions): Pigment;
68
+ /** Re-targets a pigment to another colour without allocating (the per-pixel fast path). */
69
+ declare function setPigmentColor(pigment: Pigment, r: number, g: number, b: number, strength?: number): Pigment;
70
+ /**
71
+ * Mixes colours like paint. `weights` are parts by amount (any non-negative scale; at least one
72
+ * positive). Colours may be sRGB triples (0..1) or prepared pigments (which carry a tinting
73
+ * strength). Returns the mixed sRGB colour (0..1).
74
+ */
75
+ declare function mixPigments(colors: readonly (PigmentRgb | Pigment)[], weights: readonly number[]): [number, number, number];
76
+ /** Mixes `a` towards `b` like paint: `t = 0` is `a`, `t = 1` is `b`. */
77
+ declare function lerpPigment(a: PigmentRgb | Pigment, b: PigmentRgb | Pigment, t: number): [number, number, number];
78
+ /**
79
+ * The per-dab fast path: mixes two prepared pigments (`t` of `b` into `a`) and writes the sRGB
80
+ * result to `out[offset..offset + 2]`, without allocating. Pair it with `setPigmentColor` for the
81
+ * colour under the brush.
82
+ */
83
+ declare function lerpPigmentInto(a: Pigment, b: Pigment, t: number, out: {
84
+ [i: number]: number;
85
+ }, offset?: number): void;
86
+
87
+ /**
88
+ * Photoshop blend modes on straight (non-premultiplied) 0..1 colours.
89
+ *
90
+ * Formulas follow the W3C Compositing / ISO 32000 definitions, with Photoshop's variants where
91
+ * they differ (Soft Light, Vivid / Linear / Pin Light, Hard Mix, Linear Burn / Dodge, Subtract,
92
+ * Divide, Darker / Lighter Color), and the hardened `ClipColor` for the non-separable modes.
93
+ *
94
+ * Plus `pigment`, which is not a `B(Cb, Cs)` mode: it replaces the alpha interpolation
95
+ * `(1 − α)·Cb + α·B` with a Kubelka-Munk paint mix of the two colours (see `lerpPigment`).
96
+ *
97
+ * Plus four painterly modes of our own design, which are ordinary `B(Cb, Cs)` modes, so every
98
+ * caller of `blendRgb` / `blendChannel` (the compositor, adjustment layers, effects, brushes) gets
99
+ * them for free: `watercolor` (transparent washes, multiplied per wavelength), `tint` (the source's
100
+ * colour on the backdrop's lightness, gently at the extremes), `highlight` (light added with a
101
+ * soft shoulder) and `shadow` (light taken away, with ambient fill). See `painterlyRgb`.
102
+ */
103
+
1
104
  type BlendMode = 'normal' | 'darken' | 'multiply' | 'color-burn' | 'linear-burn' | 'darker-color' | 'lighten' | 'screen' | 'color-dodge' | 'linear-dodge' | 'lighter-color' | 'overlay' | 'soft-light' | 'hard-light' | 'vivid-light' | 'linear-light' | 'pin-light' | 'hard-mix' | 'difference' | 'exclusion' | 'subtract' | 'divide' | 'hue' | 'saturation' | 'color' | 'luminosity' | 'pigment' | 'watercolor' | 'tint' | 'highlight' | 'shadow';
2
105
  interface BlendModeInfo {
3
106
  id: BlendMode;
@@ -56,6 +159,11 @@ declare const SHADOW_AMBIENT = 0.2;
56
159
  * `out[0..2]`. `t ≥ 1` (or equal colours) is `cs` exactly. Colours are 0..1.
57
160
  */
58
161
  declare function mixPigmentRgb(cb: readonly number[], cs: readonly number[], t: number, out?: number[]): number[];
162
+ /**
163
+ * The prepared pigment (K/S spectrum) of an 8-bit colour, from the cache every 8-bit pigment
164
+ * composite shares. Read-only: do not retarget it with `setPigmentColor`.
165
+ */
166
+ declare function pigment8(r: number, g: number, b: number): Pigment;
59
167
  /**
60
168
  * {@link mixPigmentRgb} on 8-bit channels, with each colour's spectrum cached: writes the 0..1
61
169
  * result to `out[0..2]`. The compositor's and {@link blendImageData}'s per-pixel path.
@@ -208,4 +316,4 @@ declare function collectTiles(surface: TiledSurface, into: Set<Tile>): void;
208
316
  /** True when two surfaces hold the same pixels (cheap when they share tiles). */
209
317
  declare function surfacesEqual(a: TiledSurface, b: TiledSurface): boolean;
210
318
 
211
- export { mixPigmentRgb as A, type BlendMode as B, normalizeBlendMode as C, packRgba as D, readPixel as E, readRegion as F, setLum as G, solidSurface as H, surfaceBytes as I, surfaceFromPixels as J, surfacesEqual as K, tileCoord as L, type MaskSurface as M, tileKey as N, tileRect as O, type PixelSurface as P, tilesInRect as Q, type RasterRect as R, SHADOW_AMBIENT as S, type TileKey as T, translateSurface as U, uniformTile as V, writeRegion as W, TiledSurface as a, type BlendModeInfo as b, BLEND_MODES as c, PAINTERLY_BLEND_MODES as d, type Tile as e, isPainterlyBlendMode as f, type Rgb as g, type Rgba as h, isBlendMode as i, SurfaceDraft as j, TILE as k, TILE_SHIFT as l, blendChannel as m, blendImageData as n, blendLum as o, blendPixel as p, blendRgb as q, canvasCompositeOperation as r, standardBlendMode as s, collectTiles as t, contentBounds as u, emptySurface as v, fillRect as w, finishTile as x, isSeparableBlendMode as y, mixPigment8 as z };
319
+ export { solidSurface as $, collectTiles as A, type BlendMode as B, contentBounds as C, createPigment as D, emptySurface as E, fillRect as F, finishTile as G, isSeparableBlendMode as H, lerpPigment as I, lerpPigmentInto as J, mixPigment8 as K, mixPigmentRgb as L, type MaskSurface as M, mixPigments as N, normalizeBlendMode as O, type Pigment as P, packRgba as Q, type RasterRect as R, SHADOW_AMBIENT as S, type TileKey as T, pigment8 as U, readPixel as V, readRegion as W, reflectanceToRgb as X, rgbToReflectance as Y, setLum as Z, setPigmentColor as _, TiledSurface as a, surfaceBytes as a0, surfaceFromPixels as a1, surfacesEqual as a2, tileCoord as a3, tileKey as a4, tileRect as a5, tilesInRect as a6, translateSurface as a7, uniformTile as a8, writeRegion as a9, type PixelSurface as b, type BlendModeInfo as c, BLEND_MODES as d, PAINTERLY_BLEND_MODES as e, type Tile as f, isPainterlyBlendMode as g, PIGMENT_BANDS as h, isBlendMode as i, PIGMENT_LAMBDA_MIN as j, PIGMENT_LAMBDA_STEP as k, PIGMENT_WAVELENGTHS as l, type PigmentOptions as m, type PigmentRgb as n, type Rgb as o, type Rgba as p, SurfaceDraft as q, TILE as r, standardBlendMode as s, TILE_SHIFT as t, blendChannel as u, blendImageData as v, blendLum as w, blendPixel as x, blendRgb as y, canvasCompositeOperation as z };
@@ -1,3 +1,106 @@
1
+ /**
2
+ * Pigment-like colour mixing: blue and yellow make green, red and white make pink, as paint does.
3
+ *
4
+ * Each sRGB colour is lifted to a smooth reflectance spectrum, the spectra are mixed with the
5
+ * Kubelka-Munk two-flux model, and the mixed spectrum is turned back into sRGB. Written from the
6
+ * published literature; no third-party code or data:
7
+ *
8
+ * 1. **Spectral uplift.** A colour's reflectance is a sigmoid of a quadratic in wavelength,
9
+ * `R(λ) = s(c₀λ² + c₁λ + c₂)` with `s(z) = ½ + z / 2√(1 + z²)` (Jakob & Hanika, "A
10
+ * Low-Dimensional Function Space for Efficient Spectral Upsampling", Eurographics 2019). The
11
+ * three coefficients are solved per colour with Newton's method so the spectrum reproduces the
12
+ * colour. Spectra are precomputed lazily on a 17³ lattice over sRGB and interpolated
13
+ * trilinearly in *linear* RGB: colour is linear in reflectance, so an interpolated spectrum
14
+ * still reproduces its colour exactly while staying smooth and inside 0..1.
15
+ * 2. **Mixing.** Each spectrum becomes `K/S = (1 − R)² / 2R` per band. Colours mix with the
16
+ * two-constant form `K/S = Σ cᵢSᵢ(K/S)ᵢ / Σ cᵢSᵢ`, where `cᵢ` is the weight times the colour's
17
+ * tinting strength and `Sᵢ` its scattering, taken from its luminance (light paints such as
18
+ * titanium white scatter strongly, dark ones barely). Back to reflectance with
19
+ * `R = 1 + K/S − √((K/S)² + 2·K/S)`.
20
+ * 3. **Back to sRGB.** The spectrum is weighted by the CIE 1931 2° colour-matching functions
21
+ * (the analytic multi-lobe fit of Wyman, Sloan & Shirley, JCGT 2013) under a 6504 K Planckian
22
+ * illuminant, converted to linear sRGB and normalised so a perfect white reflector is exactly
23
+ * white. Analytic curves keep the CIE's own (CC BY-SA) tables out of the package.
24
+ *
25
+ * Everything is plain TypeScript on typed arrays with no DOM: it runs in Node and Workers and is
26
+ * deterministic. Colours are straight sRGB triples in 0..1, as in `@pixerate/editor/raster`.
27
+ */
28
+ /** An sRGB colour, each channel 0..1 (gamma-encoded, as in CSS). */
29
+ type PigmentRgb = readonly [number, number, number];
30
+ /** Number of spectral bands. */
31
+ declare const PIGMENT_BANDS = 36;
32
+ /** First band's wavelength in nm; bands are 10 nm apart (380..730 nm). */
33
+ declare const PIGMENT_LAMBDA_MIN = 380;
34
+ declare const PIGMENT_LAMBDA_STEP = 10;
35
+ /** Band centre wavelengths in nm. */
36
+ declare const PIGMENT_WAVELENGTHS: Float32Array;
37
+ /**
38
+ * The smooth reflectance spectrum (0..1 per band, `PIGMENT_BANDS` bands from 380 nm in 10 nm
39
+ * steps) of an sRGB colour. `reflectanceToRgb` of the result gives the colour back.
40
+ */
41
+ declare function rgbToReflectance(r: number, g: number, b: number, out?: Float32Array): Float32Array;
42
+ /** The sRGB colour (0..1, clipped to the gamut) of a reflectance spectrum. */
43
+ declare function reflectanceToRgb(reflectance: ArrayLike<number>, out?: number[]): number[];
44
+ /**
45
+ * A colour prepared for mixing: its K/S spectrum and its weight in a mix. Make one per brush or
46
+ * palette colour with `createPigment` and reuse it (`setPigmentColor`) on hot paths.
47
+ */
48
+ interface Pigment {
49
+ /** The sRGB colour it was made from (0..1). */
50
+ readonly rgb: [number, number, number];
51
+ /** Tinting strength (default 1): how strongly it colours a mix, relative to its weight. */
52
+ strength: number;
53
+ /** K/S per band. */
54
+ readonly ks: Float32Array;
55
+ /** Scattering from the colour's luminance (floored), before strength. */
56
+ scatter: number;
57
+ }
58
+ interface PigmentOptions {
59
+ /**
60
+ * Tinting strength, > 0 (default 1). A strength of 4 counts each part as four, so a little of
61
+ * a strong dark pigment tints a lot of white. A colour mixed only with itself is unchanged
62
+ * whatever its strength.
63
+ */
64
+ strength?: number;
65
+ }
66
+ /** Prepares a colour for mixing. */
67
+ declare function createPigment(color: PigmentRgb, options?: PigmentOptions): Pigment;
68
+ /** Re-targets a pigment to another colour without allocating (the per-pixel fast path). */
69
+ declare function setPigmentColor(pigment: Pigment, r: number, g: number, b: number, strength?: number): Pigment;
70
+ /**
71
+ * Mixes colours like paint. `weights` are parts by amount (any non-negative scale; at least one
72
+ * positive). Colours may be sRGB triples (0..1) or prepared pigments (which carry a tinting
73
+ * strength). Returns the mixed sRGB colour (0..1).
74
+ */
75
+ declare function mixPigments(colors: readonly (PigmentRgb | Pigment)[], weights: readonly number[]): [number, number, number];
76
+ /** Mixes `a` towards `b` like paint: `t = 0` is `a`, `t = 1` is `b`. */
77
+ declare function lerpPigment(a: PigmentRgb | Pigment, b: PigmentRgb | Pigment, t: number): [number, number, number];
78
+ /**
79
+ * The per-dab fast path: mixes two prepared pigments (`t` of `b` into `a`) and writes the sRGB
80
+ * result to `out[offset..offset + 2]`, without allocating. Pair it with `setPigmentColor` for the
81
+ * colour under the brush.
82
+ */
83
+ declare function lerpPigmentInto(a: Pigment, b: Pigment, t: number, out: {
84
+ [i: number]: number;
85
+ }, offset?: number): void;
86
+
87
+ /**
88
+ * Photoshop blend modes on straight (non-premultiplied) 0..1 colours.
89
+ *
90
+ * Formulas follow the W3C Compositing / ISO 32000 definitions, with Photoshop's variants where
91
+ * they differ (Soft Light, Vivid / Linear / Pin Light, Hard Mix, Linear Burn / Dodge, Subtract,
92
+ * Divide, Darker / Lighter Color), and the hardened `ClipColor` for the non-separable modes.
93
+ *
94
+ * Plus `pigment`, which is not a `B(Cb, Cs)` mode: it replaces the alpha interpolation
95
+ * `(1 − α)·Cb + α·B` with a Kubelka-Munk paint mix of the two colours (see `lerpPigment`).
96
+ *
97
+ * Plus four painterly modes of our own design, which are ordinary `B(Cb, Cs)` modes, so every
98
+ * caller of `blendRgb` / `blendChannel` (the compositor, adjustment layers, effects, brushes) gets
99
+ * them for free: `watercolor` (transparent washes, multiplied per wavelength), `tint` (the source's
100
+ * colour on the backdrop's lightness, gently at the extremes), `highlight` (light added with a
101
+ * soft shoulder) and `shadow` (light taken away, with ambient fill). See `painterlyRgb`.
102
+ */
103
+
1
104
  type BlendMode = 'normal' | 'darken' | 'multiply' | 'color-burn' | 'linear-burn' | 'darker-color' | 'lighten' | 'screen' | 'color-dodge' | 'linear-dodge' | 'lighter-color' | 'overlay' | 'soft-light' | 'hard-light' | 'vivid-light' | 'linear-light' | 'pin-light' | 'hard-mix' | 'difference' | 'exclusion' | 'subtract' | 'divide' | 'hue' | 'saturation' | 'color' | 'luminosity' | 'pigment' | 'watercolor' | 'tint' | 'highlight' | 'shadow';
2
105
  interface BlendModeInfo {
3
106
  id: BlendMode;
@@ -56,6 +159,11 @@ declare const SHADOW_AMBIENT = 0.2;
56
159
  * `out[0..2]`. `t ≥ 1` (or equal colours) is `cs` exactly. Colours are 0..1.
57
160
  */
58
161
  declare function mixPigmentRgb(cb: readonly number[], cs: readonly number[], t: number, out?: number[]): number[];
162
+ /**
163
+ * The prepared pigment (K/S spectrum) of an 8-bit colour, from the cache every 8-bit pigment
164
+ * composite shares. Read-only: do not retarget it with `setPigmentColor`.
165
+ */
166
+ declare function pigment8(r: number, g: number, b: number): Pigment;
59
167
  /**
60
168
  * {@link mixPigmentRgb} on 8-bit channels, with each colour's spectrum cached: writes the 0..1
61
169
  * result to `out[0..2]`. The compositor's and {@link blendImageData}'s per-pixel path.
@@ -208,4 +316,4 @@ declare function collectTiles(surface: TiledSurface, into: Set<Tile>): void;
208
316
  /** True when two surfaces hold the same pixels (cheap when they share tiles). */
209
317
  declare function surfacesEqual(a: TiledSurface, b: TiledSurface): boolean;
210
318
 
211
- export { mixPigmentRgb as A, type BlendMode as B, normalizeBlendMode as C, packRgba as D, readPixel as E, readRegion as F, setLum as G, solidSurface as H, surfaceBytes as I, surfaceFromPixels as J, surfacesEqual as K, tileCoord as L, type MaskSurface as M, tileKey as N, tileRect as O, type PixelSurface as P, tilesInRect as Q, type RasterRect as R, SHADOW_AMBIENT as S, type TileKey as T, translateSurface as U, uniformTile as V, writeRegion as W, TiledSurface as a, type BlendModeInfo as b, BLEND_MODES as c, PAINTERLY_BLEND_MODES as d, type Tile as e, isPainterlyBlendMode as f, type Rgb as g, type Rgba as h, isBlendMode as i, SurfaceDraft as j, TILE as k, TILE_SHIFT as l, blendChannel as m, blendImageData as n, blendLum as o, blendPixel as p, blendRgb as q, canvasCompositeOperation as r, standardBlendMode as s, collectTiles as t, contentBounds as u, emptySurface as v, fillRect as w, finishTile as x, isSeparableBlendMode as y, mixPigment8 as z };
319
+ export { solidSurface as $, collectTiles as A, type BlendMode as B, contentBounds as C, createPigment as D, emptySurface as E, fillRect as F, finishTile as G, isSeparableBlendMode as H, lerpPigment as I, lerpPigmentInto as J, mixPigment8 as K, mixPigmentRgb as L, type MaskSurface as M, mixPigments as N, normalizeBlendMode as O, type Pigment as P, packRgba as Q, type RasterRect as R, SHADOW_AMBIENT as S, type TileKey as T, pigment8 as U, readPixel as V, readRegion as W, reflectanceToRgb as X, rgbToReflectance as Y, setLum as Z, setPigmentColor as _, TiledSurface as a, surfaceBytes as a0, surfaceFromPixels as a1, surfacesEqual as a2, tileCoord as a3, tileKey as a4, tileRect as a5, tilesInRect as a6, translateSurface as a7, uniformTile as a8, writeRegion as a9, type PixelSurface as b, type BlendModeInfo as c, BLEND_MODES as d, PAINTERLY_BLEND_MODES as e, type Tile as f, isPainterlyBlendMode as g, PIGMENT_BANDS as h, isBlendMode as i, PIGMENT_LAMBDA_MIN as j, PIGMENT_LAMBDA_STEP as k, PIGMENT_WAVELENGTHS as l, type PigmentOptions as m, type PigmentRgb as n, type Rgb as o, type Rgba as p, SurfaceDraft as q, TILE as r, standardBlendMode as s, TILE_SHIFT as t, blendChannel as u, blendImageData as v, blendLum as w, blendPixel as x, blendRgb as y, canvasCompositeOperation as z };