@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.
- package/dist/animation/index.cjs +5 -5
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.js +9 -9
- package/dist/animation-editor/index.cjs +59 -59
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +2 -1
- package/dist/animation-editor/index.d.ts +2 -1
- package/dist/animation-editor/index.js +29 -29
- package/dist/blending-DXl06NSx.d.ts +486 -0
- package/dist/blending-aOE6Y3UE.d.cts +486 -0
- package/dist/canvas/index.cjs +27 -27
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.js +11 -11
- package/dist/{chunk-2FGRTYJN.cjs → chunk-477UVMNR.cjs} +3 -3
- package/dist/{chunk-2FGRTYJN.cjs.map → chunk-477UVMNR.cjs.map} +1 -1
- package/dist/chunk-7VOHOPLW.cjs +1136 -0
- package/dist/chunk-7VOHOPLW.cjs.map +1 -0
- package/dist/{chunk-HUQ7EHVI.js → chunk-BLWCAQCW.js} +723 -1975
- package/dist/chunk-BLWCAQCW.js.map +1 -0
- package/dist/{chunk-SOROZ2BF.js → chunk-DMRSJBJF.js} +1939 -318
- package/dist/chunk-DMRSJBJF.js.map +1 -0
- package/dist/chunk-FTBZPI77.cjs +1 -0
- package/dist/chunk-FTBZPI77.cjs.map +1 -0
- package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
- package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
- package/dist/chunk-JLYX4V57.cjs +2001 -0
- package/dist/chunk-JLYX4V57.cjs.map +1 -0
- package/dist/chunk-KB2YBCVS.js +2001 -0
- package/dist/chunk-KB2YBCVS.js.map +1 -0
- package/dist/{chunk-G4BIYE6R.js → chunk-LQEEDHOP.js} +40 -2
- package/dist/chunk-LQEEDHOP.js.map +1 -0
- package/dist/{chunk-FRD3FUXI.cjs → chunk-MGL4U5WA.cjs} +2144 -523
- package/dist/chunk-MGL4U5WA.cjs.map +1 -0
- package/dist/{chunk-T3QZ6S47.cjs → chunk-POGBUYWE.cjs} +131 -93
- package/dist/chunk-POGBUYWE.cjs.map +1 -0
- package/dist/chunk-VKK5VS2T.js +1 -0
- package/dist/{chunk-UTEVPQTS.cjs → chunk-W3GXSUSQ.cjs} +746 -1998
- package/dist/chunk-W3GXSUSQ.cjs.map +1 -0
- package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
- package/dist/chunk-XIOMMGPD.cjs.map +1 -0
- package/dist/{chunk-JK6M6V7G.js → chunk-Z3YWVUWT.js} +4 -4
- package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
- package/dist/chunk-Z4BC6M74.js.map +1 -0
- package/dist/{export-a9bnF7Cl.d.ts → export-CO2ryu3I.d.ts} +419 -18
- package/dist/{export-JGWQEPJn.d.cts → export-DRiLzP_j.d.cts} +419 -18
- package/dist/image-editor/index.cjs +1665 -1113
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +78 -14
- package/dist/image-editor/index.d.ts +78 -14
- package/dist/image-editor/index.js +843 -291
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.cjs +28 -4
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +3 -89
- package/dist/paint/index.d.ts +3 -89
- package/dist/paint/index.js +27 -3
- package/dist/paint-editor/index.cjs +1056 -287
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +304 -22
- package/dist/paint-editor/index.d.ts +304 -22
- package/dist/paint-editor/index.js +852 -83
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +7 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +3 -129
- package/dist/raster/index.d.ts +3 -129
- package/dist/raster/index.js +6 -2
- package/dist/{compositor-D92URMlm.d.cts → render-BEriFuqK.d.cts} +136 -2
- package/dist/{compositor-Kt_M4ruo.d.ts → render-tjzKaik-.d.ts} +136 -2
- package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
- package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -1
- package/dist/{svg-SseJTIrA.d.ts → svg-D_cfkhlB.d.cts} +3 -629
- package/dist/{svg-ClVIG4T7.d.cts → svg-wIaKLrU0.d.ts} +3 -629
- package/dist/types-DtL_qLFz.d.ts +630 -0
- package/dist/types-DyQq-WPY.d.cts +630 -0
- package/dist/vector/index.cjs +563 -563
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +4 -2
- package/dist/vector/index.d.ts +4 -2
- package/dist/vector/index.js +69 -69
- package/dist/video-editor/index.cjs +3 -3
- package/dist/video-editor/index.js +2 -2
- package/package.json +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +49 -13
- package/dist/chunk-3PHA3UKI.js.map +0 -1
- package/dist/chunk-6BU5WU4R.cjs +0 -1
- package/dist/chunk-6BU5WU4R.cjs.map +0 -1
- package/dist/chunk-DWGZUSES.cjs +0 -694
- package/dist/chunk-DWGZUSES.cjs.map +0 -1
- package/dist/chunk-FRD3FUXI.cjs.map +0 -1
- package/dist/chunk-G4BIYE6R.js.map +0 -1
- package/dist/chunk-HUFQD4CB.cjs +0 -734
- package/dist/chunk-HUFQD4CB.cjs.map +0 -1
- package/dist/chunk-HUQ7EHVI.js.map +0 -1
- package/dist/chunk-KKREERP2.js +0 -694
- package/dist/chunk-KKREERP2.js.map +0 -1
- package/dist/chunk-SOROZ2BF.js.map +0 -1
- package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
- package/dist/chunk-T3QZ6S47.cjs.map +0 -1
- package/dist/chunk-UTEVPQTS.cjs.map +0 -1
- package/dist/chunk-XYBJKUAU.js +0 -1
- package/dist/mixer-5ziHU8OS.d.cts +0 -282
- package/dist/mixer-DNqiYXYQ.d.ts +0 -282
- /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
- /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,
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 };
|