@pixerate/editor 0.95.0 → 0.97.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/THIRD_PARTY_NOTICES.md +1 -0
- package/dist/{chunk-T6H3JDGA.cjs → chunk-4AB5BHYK.cjs} +254 -63
- package/dist/chunk-4AB5BHYK.cjs.map +1 -0
- package/dist/{chunk-E2ILRYTJ.js → chunk-OEVSVA6V.js} +231 -40
- package/dist/chunk-OEVSVA6V.js.map +1 -0
- package/dist/image-editor/index.cjs +1559 -385
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +172 -5
- package/dist/image-editor/index.d.ts +172 -5
- package/dist/image-editor/index.js +1329 -155
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.d.cts +1 -1
- package/dist/paint/index.d.ts +1 -1
- package/dist/raster/index.cjs +4 -2
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +839 -5
- package/dist/raster/index.d.ts +839 -5
- package/dist/raster/index.js +3 -1
- package/dist/{surface-CgAiXVI3.d.cts → surface-DIA-XatS.d.cts} +1 -1
- package/dist/{surface-CgAiXVI3.d.ts → surface-DIA-XatS.d.ts} +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +13 -4
- package/dist/chunk-E2ILRYTJ.js.map +0 -1
- package/dist/chunk-T6H3JDGA.cjs.map +0 -1
- package/dist/zlib-B3LIoxF-.d.cts +0 -796
- package/dist/zlib-eiTEEj1z.d.ts +0 -796
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-T6H3JDGA.cjs","../src/raster/blur.ts","../src/raster/layers.ts","../src/raster/effects/model.ts","../src/raster/effects/patterns.ts","../src/raster/curve.ts","../src/raster/effects/render.ts","../src/raster/compositor.ts","../src/raster/zlib.ts","../src/raster/png.ts"],"names":["a","d","paint","clamp01","blend","fill","i"],"mappings":"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A;AACwB,EAAA;AACK,EAAA;AACL,EAAA;AAC1B,EAAA;AACC,IAAA;AACF,MAAA;AACZ,MAAA;AACO,QAAA;AACF,QAAA;AACd,QAAA;AACgC,UAAA;AACO,UAAA;AAC5C,QAAA;AACqB,MAAA;AACI,QAAA;AACI,QAAA;AACA,QAAA;AACpB,QAAA;AACI,QAAA;AACA,QAAA;AAC8C,QAAA;AACtC,MAAA;AACA,QAAA;AACwC,QAAA;AACvC,QAAA;AACQ,QAAA;AACA,QAAA;AACmB,QAAA;AAC5B,MAAA;AACI,QAAA;AACU,QAAA;AACH,QAAA;AAC3B,MAAA;AACmB,QAAA;AACQ,QAAA;AACA,QAAA;AACA,QAAA;AAClC,MAAA;AACF,IAAA;AACF,EAAA;AAC4C,EAAA;AAC9C;AAGsE;AACvB,EAAA;AACjB,EAAA;AAC2C,EAAA;AACzE;AAMY;AACqB;AAEwB;AAC9B,EAAA;AACP,EAAA;AACwC,EAAA;AACR,IAAA;AACxC,IAAA;AACA,IAAA;AACoB,IAAA;AACoC,MAAA;AACK,MAAA;AACrE,IAAA;AACa,IAAA;AACqD,MAAA;AACF,MAAA;AAChE,IAAA;AACY,IAAA;AACd,EAAA;AACoB,EAAA;AACtB;AAEwD;AACxC,EAAA;AACyB,IAAA;AACqB,IAAA;AAC7B,IAAA;AACA,IAAA;AAC/B,EAAA;AACY,EAAA;AAC2B,EAAA;AACsB,EAAA;AAClD,EAAA;AACD,EAAA;AACF,EAAA;AAC+B,EAAA;AACT,IAAA;AACC,IAAA;AACoB,IAAA;AAC9B,IAAA;AACX,IAAA;AACO,IAAA;AACL,MAAA;AACmB,MAAA;AAC7B,IAAA;AACF,EAAA;AACwB,EAAA;AAC1B;AAGsF;AAChC,EAAA;AACrC,EAAA;AACmB,EAAA;AACG,EAAA;AACqB,EAAA;AACtB,EAAA;AACI,EAAA;AAC0B,EAAA;AACvB,EAAA;AAC7C;AAEgH;AAC/C,EAAA;AACjE;ATmuGqE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-T6H3JDGA.cjs","sourcesContent":[null,"// Portions ported from photocraft/crates/algo/src/selection_blur.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/** Separable Gaussian blur of single-channel float planes (mask feather, selection feather). */\n\n/** Normalised Gaussian kernel of σ, truncated at ⌈3σ⌉. */\nfunction gaussianKernel(sigma: number): Float32Array {\n const r = Math.ceil(sigma * 3);\n const k = new Float32Array(2 * r + 1);\n let s = 0;\n for (let i = -r; i <= r; i++) {\n const v = Math.exp(-(i * i) / (2 * sigma * sigma));\n k[i + r] = v;\n s += v;\n }\n for (let i = 0; i < k.length; i++) k[i] /= s;\n return k;\n}\n\n/** Above this σ a Gaussian is approximated by three box passes (cost independent of σ). */\nexport const DIRECT_GAUSSIAN_MAX_SIGMA = 8;\n\n/** Box sizes of three passes whose combined variance matches σ² (Kovesi). */\nfunction boxRadii(sigma: number): [number, number, number] {\n const n = 3;\n const wIdeal = Math.sqrt((12 * sigma * sigma) / n + 1);\n let wl = Math.floor(wIdeal);\n if (wl % 2 === 0) wl--;\n const wu = wl + 2;\n const mIdeal = (12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4);\n const m = Math.round(mIdeal);\n const sizes = [0, 1, 2].map((i) => (i < m ? wl : wu));\n return sizes.map((s) => (s - 1) / 2) as [number, number, number];\n}\n\n/** How a blur treats pixels beyond the image: `zero`, or `clamp` (replicate the border). */\nexport type BlurEdge = 'zero' | 'clamp';\ntype Edge = BlurEdge;\n\nfunction convolveLine(src: Float32Array, off: number, stride: number, len: number, k: Float32Array, edge: Edge, dst: Float32Array) {\n const r = (k.length - 1) / 2;\n for (let i = 0; i < len; i++) {\n let a = 0;\n const lo = i - r;\n for (let j = 0; j < k.length; j++) {\n let x = lo + j;\n if (x < 0 || x >= len) {\n if (edge === 'zero') continue;\n x = x < 0 ? 0 : len - 1;\n }\n a += src[off + x * stride] * k[j];\n }\n dst[i] = a;\n }\n}\n\n/**\n * Three box passes along one line, run on the line padded by the passes' total reach (zeros or\n * replicated ends), so the result equals one convolution with the combined kernel over the\n * same outside values — clipping between passes would lose the mass that spilled out.\n */\nfunction boxesLine(\n src: Float32Array,\n off: number,\n stride: number,\n len: number,\n radii: readonly number[],\n edge: Edge,\n a: Float32Array,\n b: Float32Array,\n dst: Float32Array\n) {\n const pad = radii.reduce((s, r) => s + r, 0);\n const plen = len + 2 * pad;\n for (let i = 0; i < plen; i++) {\n const j = i - pad;\n a[i] = j < 0 ? (edge === 'zero' ? 0 : src[off]) : j >= len ? (edge === 'zero' ? 0 : src[off + (len - 1) * stride]) : src[off + j * stride];\n }\n let from = a;\n let to = b;\n for (const r of radii) {\n // Window clipped to the padded line; the padding is wide enough that the clip never\n // reaches the part that is kept.\n let sum = 0;\n for (let i = 0; i <= Math.min(r, plen - 1); i++) sum += from[i];\n const k = 2 * r + 1;\n for (let i = 0; i < plen; i++) {\n to[i] = sum / k;\n const add = i + r + 1;\n const drop = i - r;\n if (add < plen) sum += from[add];\n if (drop >= 0) sum -= from[drop];\n }\n const t = from;\n from = to;\n to = t;\n }\n for (let i = 0; i < len; i++) dst[i] = from[i + pad];\n}\n\n/**\n * Separable Gaussian blur of a single-channel float image. `edge` `zero` treats pixels beyond\n * the image as 0 (selection feather), `clamp` replicates the border (mask refinement). σ up to\n * {@link DIRECT_GAUSSIAN_MAX_SIGMA} is convolved directly; wider blurs use three box passes, so\n * the cost per pixel never grows with σ.\n */\nexport function gaussianBlur(src: ArrayLike<number>, w: number, h: number, sigma: number, edge: Edge = 'zero'): Float32Array {\n const out = Float32Array.from(src);\n if (!(sigma >= 0.1) || w <= 0 || h <= 0) return out;\n // Far beyond the image a wider blur changes nothing but cost.\n const s = Math.min(sigma, 4 * Math.max(w, h));\n const line = new Float32Array(Math.max(w, h));\n const passes: Array<(buf: Float32Array, off: number, stride: number, len: number) => void> = [];\n if (s <= DIRECT_GAUSSIAN_MAX_SIGMA) {\n const k = gaussianKernel(s);\n passes.push((buf, off, stride, len) => convolveLine(buf, off, stride, len, k, edge, line));\n } else {\n const radii = boxRadii(s);\n const plen = Math.max(w, h) + 2 * radii.reduce((a, r) => a + r, 0);\n const a = new Float32Array(plen);\n const b = new Float32Array(plen);\n passes.push((buf, off, stride, len) => boxesLine(buf, off, stride, len, radii, edge, a, b, line));\n }\n for (const pass of passes) {\n for (let y = 0; y < h; y++) {\n pass(out, y * w, 1, w);\n out.set(line.subarray(0, w), y * w);\n }\n }\n for (const pass of passes) {\n for (let x = 0; x < w; x++) {\n pass(out, x, w, h);\n for (let y = 0; y < h; y++) out[y * w + x] = line[y];\n }\n }\n return out;\n}\n","/**\n * A layer stack that is not specific to any editor: pixel layers on tiled surfaces, groups (with\n * pass-through), raster masks with density and feather, clipping, locks, opacity, fill and blend\n * modes, plus pure tree helpers. Editors extend these types with their own layer kinds (the image\n * editor adds adjustment and annotation layers, filters and parametric masks).\n *\n * Layer objects are immutable: every change builds new objects along the path from the root to\n * the changed layer and shares everything else.\n */\n\nimport type { BlendMode } from './blend';\nimport type { LayerEffects } from './effects/model';\nimport type { MaskSurface, PixelSurface } from './surface';\n\n/** Blend modes of a layer; groups also accept `pass-through`. */\nexport type GroupBlendMode = BlendMode | 'pass-through';\n\nexport interface LayerLocks {\n /** Painting and filling are refused. */\n pixels?: boolean;\n /** Painting keeps each pixel's alpha. */\n transparency?: boolean;\n /** Moving (offset) is refused. */\n position?: boolean;\n /** Everything above, plus property changes and deletion. */\n all?: boolean;\n}\n\n/**\n * A layer mask: 8-bit coverage (255 reveals, 0 hides) on a tiled surface, plus non-destructive\n * density and feather. `source` is an editor-defined parametric mask (generated instead of\n * painted; the compositor asks its profile for the coverage, and the surface is unused).\n */\nexport interface RasterMask<Source = unknown> {\n surface: MaskSurface;\n /** Value outside the painted tiles (PSD's default colour); equals `surface.defaultValue`. */\n defaultValue: 0 | 255;\n /** 0..1 (missing = 1): at 0.5 hidden areas still show half. */\n density?: number;\n /** Gaussian σ in pixels (missing = 0). */\n feather?: number;\n /** `false` keeps the mask but ignores it. */\n enabled?: boolean;\n source?: Source;\n}\n\nexport interface RasterLayerBase {\n id: string;\n name: string;\n visible: boolean;\n /** 0..1 */\n opacity: number;\n blend: BlendMode;\n /** Clip to the nearest unclipped layer below (Photoshop's clipping mask). */\n clipped?: boolean;\n mask?: RasterMask;\n locks?: LayerLocks;\n /** Live layer effects (drop shadow, stroke, …), rendered between the content and its blend. */\n effects?: LayerEffects;\n}\n\nexport interface RasterPixelLayer extends RasterLayerBase {\n type: 'pixel';\n /** Pixels in layer space; document position = layer position + `offset`. A mask moves with it. */\n surface: PixelSurface;\n /** Integer move without re-tiling. */\n offset: { x: number; y: number };\n /** 0..1, PSD \"Fill\" (missing = 1). */\n fillOpacity?: number;\n}\n\nexport interface RasterGroupLayer<L = RasterLayer> extends Omit<RasterLayerBase, 'blend'> {\n type: 'group';\n blend: GroupBlendMode;\n /** Bottom to top. */\n children: L[];\n /** UI only. */\n collapsed?: boolean;\n}\n\n/** The plain stack: pixel layers and groups. */\nexport type RasterLayer = RasterPixelLayer | RasterGroupLayer<RasterLayer>;\n\n/** What the tree helpers need of a layer: an id, a type, and `children` on groups. */\nexport interface RasterNode {\n id: string;\n type: string;\n}\n\n/** The group members of a layer union. */\nexport type GroupIn<T> = Extract<T, { type: 'group' }>;\n\nconst childrenOf = <T>(l: T): readonly T[] | null =>\n (l as { type: string }).type === 'group' ? ((l as unknown as { children: T[] }).children ?? null) : null;\n\nexport interface LayerLocation<T extends RasterNode = RasterLayer> {\n layer: T;\n /** The containing group, or `null` at the top level. */\n parent: GroupIn<T> | null;\n /** Index within the parent's children (or the top-level list). */\n index: number;\n /** Ancestors from the top level down to the parent. */\n path: GroupIn<T>[];\n}\n\n/** Every layer, depth first, bottom to top (a group comes before its children). */\nexport function flattenLayers<T extends RasterNode>(layers: readonly T[]): T[] {\n const out: T[] = [];\n const walk = (list: readonly T[]) => {\n for (const l of list) {\n out.push(l);\n const c = childrenOf(l);\n if (c) walk(c);\n }\n };\n walk(layers);\n return out;\n}\n\nexport function findLayer<T extends RasterNode>(layers: readonly T[], id: string): LayerLocation<T> | null {\n const walk = (list: readonly T[], parent: GroupIn<T> | null, path: GroupIn<T>[]): LayerLocation<T> | null => {\n for (let i = 0; i < list.length; i++) {\n const l = list[i];\n if (l.id === id) return { layer: l, parent, index: i, path };\n const c = childrenOf(l);\n if (c) {\n const g = l as GroupIn<T>;\n const hit = walk(c, g, [...path, g]);\n if (hit) return hit;\n }\n }\n return null;\n };\n return walk(layers, null, []);\n}\n\n/** Replaces the layer with `id` by `fn(layer)` (or removes it when `fn` returns `null`). */\nexport function mapLayer<T extends RasterNode>(layers: readonly T[], id: string, fn: (layer: T) => T | null): T[] {\n let changed = false;\n const walk = (list: readonly T[]): T[] => {\n let out: T[] | null = null;\n for (let i = 0; i < list.length; i++) {\n const l = list[i];\n let next: T | null = l;\n if (l.id === id) {\n next = fn(l);\n changed = true;\n } else {\n const c = childrenOf(l);\n if (c) {\n const children = walk(c);\n if (children !== c) next = { ...l, children } as T;\n }\n }\n if (next !== l) {\n if (!out) out = list.slice(0, i);\n if (next) out.push(next);\n } else if (out) out.push(l);\n }\n return out ?? (list as T[]);\n };\n const result = walk(layers);\n return changed ? result : (layers as T[]);\n}\n\n/** Inserts `layer` into `parentId`'s children (or the top level) at `index` (default: top). */\nexport function insertLayer<T extends RasterNode>(layers: readonly T[], layer: T, parentId: string | null, index?: number): T[] {\n const put = (list: readonly T[]) => {\n const at = index === undefined ? list.length : Math.max(0, Math.min(list.length, index));\n const next = list.slice();\n next.splice(at, 0, layer);\n return next;\n };\n if (!parentId) return put(layers);\n return mapLayer(layers, parentId, (g) => {\n const c = childrenOf(g);\n return c ? ({ ...g, children: put(c) } as T) : g;\n });\n}\n\nexport function isLayerLocked(layer: { locks?: LayerLocks }, what: keyof LayerLocks): boolean {\n return Boolean(layer.locks?.all || layer.locks?.[what]);\n}\n","// Portions ported from photocraft-main/crates/doc/src/effects.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Layer effects (Photoshop's layer styles) as data: drop and inner shadows, outer and inner\n * glows, bevel & emboss, satin, colour / gradient / pattern overlays and strokes. Rendering lives\n * in `./render`, PSD mapping in the image editor's PSD module.\n *\n * Angles are in degrees, counter-clockwise from 3 o'clock, and give the direction the light\n * comes *from* (Photoshop's convention). Sizes and distances are in pixels, opacities and\n * percentages in 0..1, colours are `#rrggbb`.\n */\n\nimport { isBlendMode, type BlendMode } from '../blend';\n\n/** One point of a contour curve (input → output, both 0..1). */\nexport interface ContourPoint {\n input: number;\n output: number;\n /** The curve bends sharply here instead of passing smoothly through (Photoshop's corner point). */\n corner?: boolean;\n}\n\n/** A transfer curve applied to an effect's coverage. Missing on an effect = linear. */\nexport interface EffectContour {\n name: string;\n points: ContourPoint[];\n}\n\nexport type GradientStyle = 'linear' | 'radial' | 'angle' | 'reflected' | 'diamond';\n\nexport interface GradientColorStop {\n /** 0..1 along the gradient. */\n position: number;\n color: string;\n}\n\nexport interface GradientOpacityStop {\n position: number;\n /** 0..1 */\n opacity: number;\n}\n\n/** A gradient as overlays, gradient strokes and gradient glows paint it. */\nexport interface EffectGradient {\n stops: GradientColorStop[];\n /** Missing or empty: opaque. */\n opacityStops?: GradientOpacityStop[];\n style: GradientStyle;\n /** Degrees (90 = bottom to top). */\n angle: number;\n /** 1 = 100 %. */\n scale: number;\n reverse: boolean;\n /** Align with the layer's bounds (else the canvas). */\n align: boolean;\n /** Centre offset as a fraction of the bounds. */\n offset: { x: number; y: number };\n}\n\n/** A pattern reference: the id of a document or built-in pattern (see `builtinPatternIds`). */\nexport interface EffectPatternRef {\n patternId: string;\n /** Display name (PSD keeps it beside the id). */\n name?: string;\n /** 1 = 100 %. */\n scale: number;\n /** Rotation in degrees (counter-clockwise). */\n angle?: number;\n /** Tile from the layer's top-left (\"Link with Layer\", default) instead of the canvas origin. */\n link?: boolean;\n /** Origin offset in pixels. */\n phase?: { x: number; y: number };\n}\n\n/** What a stroke or glow paints with. */\nexport type EffectFill =\n | { type: 'color'; color: string }\n | { type: 'gradient'; gradient: EffectGradient }\n | ({ type: 'pattern' } & EffectPatternRef);\n\ninterface EffectBase {\n /** Unique within the layer. */\n id: string;\n enabled: boolean;\n}\n\ninterface Blended {\n blend: BlendMode;\n /** 0..1 */\n opacity: number;\n}\n\nexport interface DropShadowEffect extends EffectBase, Blended {\n kind: 'drop-shadow';\n color: string;\n angle: number;\n useGlobalLight: boolean;\n distance: number;\n /** 0..1 of the size grown solid before the blur. */\n spread: number;\n size: number;\n contour?: EffectContour;\n antiAlias: boolean;\n /** 0..1 */\n noise: number;\n /** The layer hides the shadow beneath its see-through fill (\"Layer Knocks Out Drop Shadow\"). */\n knocksOut: boolean;\n}\n\nexport interface InnerShadowEffect extends EffectBase, Blended {\n kind: 'inner-shadow';\n color: string;\n angle: number;\n useGlobalLight: boolean;\n distance: number;\n /** 0..1 of the size shrunk solid before the blur. */\n choke: number;\n size: number;\n contour?: EffectContour;\n antiAlias: boolean;\n noise: number;\n}\n\nexport type GlowTechnique = 'softer' | 'precise';\n\nexport interface OuterGlowEffect extends EffectBase, Blended {\n kind: 'outer-glow';\n /** Colour or gradient. */\n fill: Extract<EffectFill, { type: 'color' | 'gradient' }>;\n technique: GlowTechnique;\n spread: number;\n size: number;\n contour?: EffectContour;\n antiAlias: boolean;\n /** 0..1 (default 0.5): the part of the glow the contour spans. */\n range: number;\n /** 0..1, varies a gradient glow's colours. Stored only (not rendered). */\n jitter: number;\n noise: number;\n}\n\nexport interface InnerGlowEffect extends EffectBase, Blended {\n kind: 'inner-glow';\n fill: Extract<EffectFill, { type: 'color' | 'gradient' }>;\n technique: GlowTechnique;\n choke: number;\n size: number;\n contour?: EffectContour;\n antiAlias: boolean;\n range: number;\n jitter: number;\n noise: number;\n /** Glow from the edge inwards (default) or from the centre outwards. */\n source: 'edge' | 'center';\n}\n\nexport type BevelStyle = 'inner-bevel' | 'outer-bevel' | 'emboss' | 'pillow-emboss' | 'stroke-emboss';\nexport type BevelTechnique = 'smooth' | 'chisel-hard' | 'chisel-soft';\n\n/** The Contour element of Bevel & Emboss: shapes the bevel's height profile. */\nexport interface BevelContour {\n contour: EffectContour;\n /** 0..1 (default 0.5). Stored only. */\n range: number;\n antiAlias: boolean;\n}\n\n/** The Texture element of Bevel & Emboss: a pattern's luminance added to the height. */\nexport interface BevelTexture extends EffectPatternRef {\n /** -10..10 (1 = +100 %); negative carves the pattern in. */\n depth: number;\n invert: boolean;\n}\n\nexport interface BevelEmbossEffect extends EffectBase {\n kind: 'bevel-emboss';\n style: BevelStyle;\n technique: BevelTechnique;\n /** 0..10 (1 = 100 %). */\n depth: number;\n direction: 'up' | 'down';\n size: number;\n soften: number;\n angle: number;\n /** Light altitude in degrees (0..90). */\n altitude: number;\n useGlobalLight: boolean;\n glossContour?: EffectContour;\n highlightColor: string;\n highlightBlend: BlendMode;\n highlightOpacity: number;\n shadowColor: string;\n shadowBlend: BlendMode;\n shadowOpacity: number;\n /** Contour element (shapes the bevel's height profile), or missing when off. */\n contourElement?: BevelContour;\n /** Texture element, or missing when off. */\n texture?: BevelTexture;\n}\n\nexport interface SatinEffect extends EffectBase, Blended {\n kind: 'satin';\n color: string;\n angle: number;\n distance: number;\n size: number;\n contour?: EffectContour;\n antiAlias: boolean;\n invert: boolean;\n}\n\nexport interface ColorOverlayEffect extends EffectBase, Blended {\n kind: 'color-overlay';\n color: string;\n}\n\nexport interface GradientOverlayEffect extends EffectBase, Blended {\n kind: 'gradient-overlay';\n gradient: EffectGradient;\n dither: boolean;\n}\n\nexport interface PatternOverlayEffect extends EffectBase, Blended, EffectPatternRef {\n kind: 'pattern-overlay';\n}\n\nexport type StrokePosition = 'outside' | 'inside' | 'center';\n\nexport interface StrokeEffect extends EffectBase, Blended {\n kind: 'stroke';\n size: number;\n position: StrokePosition;\n fill: EffectFill;\n}\n\n/** One layer effect. Several of the same kind are allowed; the first listed of a kind is on top. */\nexport type LayerEffect =\n | DropShadowEffect\n | InnerShadowEffect\n | OuterGlowEffect\n | InnerGlowEffect\n | BevelEmbossEffect\n | SatinEffect\n | ColorOverlayEffect\n | GradientOverlayEffect\n | PatternOverlayEffect\n | StrokeEffect;\n\nexport type LayerEffectKind = LayerEffect['kind'];\n\n/** A layer's effects: the master switch, Layer Style › Scale Effects and the effect list. */\nexport interface LayerEffects {\n /** The master switch (\"effects on\"); `false` keeps the effects but renders none. */\n enabled: boolean;\n /** Scale Effects: multiplies every size, distance and pattern scale (1 = 100 %; missing = 1). */\n scale?: number;\n items: LayerEffect[];\n /**\n * The PSD block the effects were read from (`lfx2`, `lfxs` or `lmfx`), kept so export writes it\n * back verbatim while the effects still map to the same values. Export regenerates it otherwise.\n */\n psd?: { key: string; data: Uint8Array };\n}\n\n/** The document-wide light that effects with `useGlobalLight` use. */\nexport interface GlobalLight {\n /** Degrees. */\n angle: number;\n /** Degrees. */\n altitude: number;\n}\n\nexport const DEFAULT_GLOBAL_LIGHT: GlobalLight = { angle: 120, altitude: 30 };\n\n/** Every effect kind, in the order the Layer Style dialog lists them. */\nexport const LAYER_EFFECT_KINDS: readonly LayerEffectKind[] = [\n 'bevel-emboss',\n 'stroke',\n 'inner-shadow',\n 'inner-glow',\n 'satin',\n 'color-overlay',\n 'gradient-overlay',\n 'pattern-overlay',\n 'outer-glow',\n 'drop-shadow',\n];\n\nconst LABELS: Record<LayerEffectKind, string> = {\n 'drop-shadow': 'Drop Shadow',\n 'inner-shadow': 'Inner Shadow',\n 'outer-glow': 'Outer Glow',\n 'inner-glow': 'Inner Glow',\n 'bevel-emboss': 'Bevel & Emboss',\n satin: 'Satin',\n 'color-overlay': 'Color Overlay',\n 'gradient-overlay': 'Gradient Overlay',\n 'pattern-overlay': 'Pattern Overlay',\n stroke: 'Stroke',\n};\n\nexport function layerEffectLabel(kind: LayerEffectKind): string {\n return LABELS[kind];\n}\n\nexport function isLayerEffectKind(value: unknown): value is LayerEffectKind {\n return typeof value === 'string' && value in LABELS;\n}\n\n/** Photoshop's default black-to-white linear gradient. */\nexport function defaultEffectGradient(): EffectGradient {\n return {\n stops: [\n { position: 0, color: '#000000' },\n { position: 1, color: '#ffffff' },\n ],\n style: 'linear',\n angle: 90,\n scale: 1,\n reverse: false,\n align: true,\n offset: { x: 0, y: 0 },\n };\n}\n\nlet fxCounter = 0;\n\n/** A fresh effect id. */\nexport function newEffectId(): string {\n return `fx_${Date.now().toString(36)}_${(++fxCounter).toString(36)}`;\n}\n\n/** An effect of `kind` with Photoshop's default settings. */\nexport function defaultLayerEffect(kind: LayerEffectKind, id = newEffectId()): LayerEffect {\n const base = { id, enabled: true };\n switch (kind) {\n case 'drop-shadow':\n return { ...base, kind, blend: 'multiply', opacity: 0.75, color: '#000000', angle: 120, useGlobalLight: true, distance: 5, spread: 0, size: 5, antiAlias: false, noise: 0, knocksOut: true };\n case 'inner-shadow':\n return { ...base, kind, blend: 'multiply', opacity: 0.75, color: '#000000', angle: 120, useGlobalLight: true, distance: 5, choke: 0, size: 5, antiAlias: false, noise: 0 };\n case 'outer-glow':\n return { ...base, kind, blend: 'screen', opacity: 0.75, fill: { type: 'color', color: '#ffffbe' }, technique: 'softer', spread: 0, size: 5, antiAlias: false, range: 0.5, jitter: 0, noise: 0 };\n case 'inner-glow':\n return { ...base, kind, blend: 'screen', opacity: 0.75, fill: { type: 'color', color: '#ffffbe' }, technique: 'softer', choke: 0, size: 5, antiAlias: false, range: 0.5, jitter: 0, noise: 0, source: 'edge' };\n case 'bevel-emboss':\n return {\n ...base,\n kind,\n style: 'inner-bevel',\n technique: 'smooth',\n depth: 1,\n direction: 'up',\n size: 5,\n soften: 0,\n angle: 120,\n altitude: 30,\n useGlobalLight: true,\n highlightColor: '#ffffff',\n highlightBlend: 'screen',\n highlightOpacity: 0.75,\n shadowColor: '#000000',\n shadowBlend: 'multiply',\n shadowOpacity: 0.75,\n };\n case 'satin':\n return { ...base, kind, blend: 'multiply', opacity: 0.5, color: '#000000', angle: 19, distance: 11, size: 14, antiAlias: true, invert: true };\n case 'color-overlay':\n return { ...base, kind, blend: 'normal', opacity: 1, color: '#ff0000' };\n case 'gradient-overlay':\n return { ...base, kind, blend: 'normal', opacity: 1, gradient: defaultEffectGradient(), dither: false };\n case 'pattern-overlay':\n return { ...base, kind, blend: 'normal', opacity: 1, patternId: 'checker', name: 'Checker', scale: 1, angle: 0, link: true, phase: { x: 0, y: 0 } };\n case 'stroke':\n return { ...base, kind, blend: 'normal', opacity: 1, size: 3, position: 'outside', fill: { type: 'color', color: '#000000' } };\n }\n}\n\n/** True when the master switch is on and at least one effect is enabled. */\nexport function hasActiveEffects(effects: LayerEffects | undefined | null): effects is LayerEffects {\n return Boolean(effects && effects.enabled && effects.items.some((e) => e.enabled));\n}\n\n/** Maximum distance (px) an effect is rendered from the layer's shape. */\nexport const MAX_EFFECT_REACH = 512;\n\n/** How far (px, before Scale Effects) one effect paints beyond or into the layer's shape. */\nexport function effectReach(e: LayerEffect): number {\n switch (e.kind) {\n case 'drop-shadow':\n case 'inner-shadow':\n return e.distance + e.size;\n case 'outer-glow':\n case 'inner-glow':\n return e.size + 1;\n case 'stroke':\n return e.size;\n case 'satin':\n return Math.max(1, e.distance) + e.size;\n case 'bevel-emboss':\n return e.size + e.soften + 1;\n default:\n return 0;\n }\n}\n\n/**\n * Pixels an effect stack can reach beyond the layer's shape (its enabled effects, scaled), plus\n * a little slack. 0 when no effect is active. Dirty areas and tile signatures grow by this.\n */\nexport function effectsMargin(effects: LayerEffects | undefined | null): number {\n if (!hasActiveEffects(effects)) return 0;\n const scale = effectsScale(effects);\n let m = 0;\n for (const e of effects.items) {\n if (!e.enabled) continue;\n const r = effectReach(e) * scale;\n if (Number.isFinite(r)) m = Math.max(m, r);\n }\n return Math.ceil(Math.min(Math.max(m, 0), MAX_EFFECT_REACH)) + 2;\n}\n\n/** Scale Effects as a usable factor (missing or invalid = 1). */\nexport function effectsScale(effects: Pick<LayerEffects, 'scale'>): number {\n const s = effects.scale;\n return typeof s === 'number' && Number.isFinite(s) && s > 0 ? s : 1;\n}\n\n/** `effect` with its sizes, distances and pattern scales multiplied by `s` (Scale Effects). */\nexport function scaleLayerEffect<E extends LayerEffect>(e: E, s: number): E {\n if (s === 1) return e;\n const pattern = <P extends EffectPatternRef>(p: P): P => ({ ...p, scale: p.scale * s });\n const fill = (f: EffectFill): EffectFill => (f.type === 'pattern' ? pattern(f) : f);\n switch (e.kind) {\n case 'drop-shadow':\n case 'inner-shadow':\n case 'satin':\n return { ...e, distance: e.distance * s, size: e.size * s };\n case 'outer-glow':\n case 'inner-glow':\n return { ...e, size: e.size * s };\n case 'stroke':\n return { ...e, size: e.size * s, fill: fill(e.fill) };\n case 'pattern-overlay':\n return pattern(e) as E;\n case 'bevel-emboss':\n return { ...e, size: e.size * s, soften: e.soften * s, texture: e.texture ? pattern(e.texture) : undefined };\n default:\n return e;\n }\n}\n\n/** `#rrggbb` (or `#rgb`, `rrggbb`) as 0..1 RGB, or `null`. */\nexport function parseEffectColor(value: unknown): [number, number, number] | null {\n if (typeof value !== 'string') return null;\n let h = value.trim().replace(/^#/, '');\n if (/^[0-9a-f]{3}$/i.test(h)) h = h.replace(/./g, (c) => c + c);\n if (!/^[0-9a-f]{6}$/i.test(h)) return null;\n return [parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255];\n}\n\n/** 0..1 RGB as `#rrggbb`. */\nexport function effectColorHex(rgb: readonly number[]): string {\n const c = (v: number) =>\n Math.round(Math.max(0, Math.min(1, v)) * 255)\n .toString(16)\n .padStart(2, '0');\n return `#${c(rgb[0])}${c(rgb[1])}${c(rgb[2])}`;\n}\n\n// ---------------------------------------------------------------------------\n// Validation (agents, JSON and PSD all go through here)\n// ---------------------------------------------------------------------------\n\nexport class LayerEffectError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LayerEffectError';\n }\n}\n\ntype Rec = Record<string, unknown>;\n\nfunction fail(path: string, msg: string): never {\n throw new LayerEffectError(`${path}: ${msg}`);\n}\n\nfunction num(r: Rec, key: string, path: string, min: number, max: number): void {\n const v = r[key];\n if (typeof v !== 'number' || !Number.isFinite(v)) fail(`${path}.${key}`, `must be a number from ${min} to ${max}.`);\n if (v < min || v > max) fail(`${path}.${key}`, `${v} is out of range (${min} to ${max}).`);\n}\n\nfunction bool(r: Rec, key: string, path: string): void {\n if (typeof r[key] !== 'boolean') fail(`${path}.${key}`, 'must be true or false.');\n}\n\nfunction color(r: Rec, key: string, path: string): void {\n if (!parseEffectColor(r[key])) fail(`${path}.${key}`, `must be a colour like \"#ff8800\" (got ${JSON.stringify(r[key])}).`);\n}\n\nfunction oneOf(r: Rec, key: string, path: string, values: readonly string[]): void {\n if (typeof r[key] !== 'string' || !values.includes(r[key] as string)) fail(`${path}.${key}`, `must be one of ${values.join(', ')}.`);\n}\n\nfunction blend(r: Rec, key: string, path: string): void {\n if (!isBlendMode(r[key])) fail(`${path}.${key}`, `must be a blend mode such as normal, multiply or screen (got ${JSON.stringify(r[key])}).`);\n}\n\nfunction contour(v: unknown, path: string): void {\n if (v === undefined) return;\n if (!v || typeof v !== 'object') fail(path, 'must be an object { name, points }.');\n const c = v as Rec;\n if (typeof c.name !== 'string') fail(`${path}.name`, 'must be a string.');\n if (!Array.isArray(c.points) || c.points.length < 2) fail(`${path}.points`, 'needs at least 2 points { input, output } from 0 to 1.');\n c.points.forEach((p, i) => {\n if (!p || typeof p !== 'object') fail(`${path}.points[${i}]`, 'must be { input, output }.');\n num(p as Rec, 'input', `${path}.points[${i}]`, 0, 1);\n num(p as Rec, 'output', `${path}.points[${i}]`, 0, 1);\n if ((p as Rec).corner !== undefined) bool(p as Rec, 'corner', `${path}.points[${i}]`);\n });\n}\n\nexport function validateEffectGradient(v: unknown, path: string): void {\n if (!v || typeof v !== 'object') fail(path, 'must be a gradient object.');\n const g = v as Rec;\n if (!Array.isArray(g.stops) || g.stops.length < 1) fail(`${path}.stops`, 'needs at least one stop { position, color }.');\n g.stops.forEach((s, i) => {\n num(s as Rec, 'position', `${path}.stops[${i}]`, 0, 1);\n color(s as Rec, 'color', `${path}.stops[${i}]`);\n });\n if (g.opacityStops !== undefined) {\n if (!Array.isArray(g.opacityStops)) fail(`${path}.opacityStops`, 'must be a list of { position, opacity }.');\n g.opacityStops.forEach((s, i) => {\n num(s as Rec, 'position', `${path}.opacityStops[${i}]`, 0, 1);\n num(s as Rec, 'opacity', `${path}.opacityStops[${i}]`, 0, 1);\n });\n }\n oneOf(g, 'style', path, ['linear', 'radial', 'angle', 'reflected', 'diamond']);\n num(g, 'angle', path, -360, 360);\n num(g, 'scale', path, 0.1, 1.5);\n bool(g, 'reverse', path);\n bool(g, 'align', path);\n const o = g.offset as Rec | undefined;\n if (!o || typeof o !== 'object') fail(`${path}.offset`, 'must be { x, y } (fractions of the bounds).');\n num(o, 'x', `${path}.offset`, -1, 1);\n num(o, 'y', `${path}.offset`, -1, 1);\n}\n\nfunction patternRef(r: Rec, path: string): void {\n if (typeof r.patternId !== 'string' || !r.patternId) fail(`${path}.patternId`, 'must be a pattern id (a built-in pattern such as \"checker\", or one from list_layer_effects.patterns).');\n num(r, 'scale', path, 0.01, 10);\n if (r.angle !== undefined) num(r, 'angle', path, -360, 360);\n if (r.link !== undefined) bool(r, 'link', path);\n if (r.phase !== undefined) {\n const p = r.phase as Rec;\n if (!p || typeof p !== 'object') fail(`${path}.phase`, 'must be { x, y } in pixels.');\n num(p, 'x', `${path}.phase`, -1e6, 1e6);\n num(p, 'y', `${path}.phase`, -1e6, 1e6);\n }\n}\n\nfunction fill(v: unknown, path: string, allowPattern: boolean): void {\n if (!v || typeof v !== 'object') fail(path, 'must be { type: \"color\", color } or { type: \"gradient\", gradient }' + (allowPattern ? ' or { type: \"pattern\", patternId, scale }.' : '.'));\n const f = v as Rec;\n if (f.type === 'color') color(f, 'color', path);\n else if (f.type === 'gradient') validateEffectGradient(f.gradient, `${path}.gradient`);\n else if (f.type === 'pattern' && allowPattern) patternRef(f, path);\n else fail(`${path}.type`, allowPattern ? 'must be color, gradient or pattern.' : 'must be color or gradient (glows cannot use a pattern).');\n}\n\nconst OPTIONAL_FIELDS: Partial<Record<LayerEffectKind, string[]>> = {\n 'drop-shadow': ['contour'],\n 'inner-shadow': ['contour'],\n 'outer-glow': ['contour'],\n 'inner-glow': ['contour'],\n satin: ['contour'],\n 'bevel-emboss': ['glossContour', 'contourElement', 'texture'],\n};\n\n/** Every field an effect of `kind` has (required and optional), including `id` and `kind`. */\nexport function layerEffectFields(kind: LayerEffectKind): string[] {\n return [...Object.keys(defaultLayerEffect(kind, 'x')), ...(OPTIONAL_FIELDS[kind] ?? [])];\n}\n\n/**\n * Checks a complete effect (every field of its kind present and in range). Throws a\n * {@link LayerEffectError} naming the first wrong field and how to fix it.\n */\nexport function validateLayerEffect(v: unknown, path = 'effect'): asserts v is LayerEffect {\n if (!v || typeof v !== 'object') fail(path, 'must be an object.');\n const e = v as Rec;\n if (!isLayerEffectKind(e.kind)) fail(`${path}.kind`, `must be one of ${LAYER_EFFECT_KINDS.join(', ')}.`);\n if (typeof e.id !== 'string' || !e.id) fail(`${path}.id`, 'must be a non-empty string.');\n bool(e, 'enabled', path);\n const kind = e.kind as LayerEffectKind;\n const allowed = layerEffectFields(kind);\n for (const k of Object.keys(e)) {\n if (e[k] !== undefined && !allowed.includes(k)) fail(`${path}.${k}`, `not a setting of ${kind}. Its settings are ${allowed.filter((f) => f !== 'id' && f !== 'kind').join(', ')}.`);\n }\n if (kind !== 'bevel-emboss') {\n blend(e, 'blend', path);\n num(e, 'opacity', path, 0, 1);\n }\n switch (kind) {\n case 'drop-shadow':\n case 'inner-shadow':\n color(e, 'color', path);\n num(e, 'angle', path, -360, 360);\n bool(e, 'useGlobalLight', path);\n num(e, 'distance', path, 0, 30000);\n num(e, kind === 'drop-shadow' ? 'spread' : 'choke', path, 0, 1);\n num(e, 'size', path, 0, 250);\n contour(e.contour, `${path}.contour`);\n bool(e, 'antiAlias', path);\n num(e, 'noise', path, 0, 1);\n if (kind === 'drop-shadow') bool(e, 'knocksOut', path);\n break;\n case 'outer-glow':\n case 'inner-glow':\n fill(e.fill, `${path}.fill`, false);\n oneOf(e, 'technique', path, ['softer', 'precise']);\n num(e, kind === 'outer-glow' ? 'spread' : 'choke', path, 0, 1);\n num(e, 'size', path, 0, 250);\n contour(e.contour, `${path}.contour`);\n bool(e, 'antiAlias', path);\n num(e, 'range', path, 0.01, 1);\n num(e, 'jitter', path, 0, 1);\n num(e, 'noise', path, 0, 1);\n if (kind === 'inner-glow') oneOf(e, 'source', path, ['edge', 'center']);\n break;\n case 'bevel-emboss':\n oneOf(e, 'style', path, ['inner-bevel', 'outer-bevel', 'emboss', 'pillow-emboss', 'stroke-emboss']);\n oneOf(e, 'technique', path, ['smooth', 'chisel-hard', 'chisel-soft']);\n num(e, 'depth', path, 0, 10);\n oneOf(e, 'direction', path, ['up', 'down']);\n num(e, 'size', path, 0, 250);\n num(e, 'soften', path, 0, 16);\n num(e, 'angle', path, -360, 360);\n num(e, 'altitude', path, 0, 90);\n bool(e, 'useGlobalLight', path);\n contour(e.glossContour, `${path}.glossContour`);\n color(e, 'highlightColor', path);\n blend(e, 'highlightBlend', path);\n num(e, 'highlightOpacity', path, 0, 1);\n color(e, 'shadowColor', path);\n blend(e, 'shadowBlend', path);\n num(e, 'shadowOpacity', path, 0, 1);\n if (e.contourElement !== undefined) {\n const c = e.contourElement as Rec;\n if (!c || typeof c !== 'object') fail(`${path}.contourElement`, 'must be { contour, range, antiAlias } or left out.');\n contour(c.contour, `${path}.contourElement.contour`);\n num(c, 'range', `${path}.contourElement`, 0, 1);\n bool(c, 'antiAlias', `${path}.contourElement`);\n }\n if (e.texture !== undefined) {\n const t = e.texture as Rec;\n if (!t || typeof t !== 'object') fail(`${path}.texture`, 'must be { patternId, scale, depth, invert } or left out.');\n patternRef(t, `${path}.texture`);\n num(t, 'depth', `${path}.texture`, -10, 10);\n bool(t, 'invert', `${path}.texture`);\n }\n break;\n case 'satin':\n color(e, 'color', path);\n num(e, 'angle', path, -360, 360);\n num(e, 'distance', path, 0, 250);\n num(e, 'size', path, 0, 250);\n contour(e.contour, `${path}.contour`);\n bool(e, 'antiAlias', path);\n bool(e, 'invert', path);\n break;\n case 'color-overlay':\n color(e, 'color', path);\n break;\n case 'gradient-overlay':\n validateEffectGradient(e.gradient, `${path}.gradient`);\n bool(e, 'dither', path);\n break;\n case 'pattern-overlay':\n patternRef(e, path);\n break;\n case 'stroke':\n num(e, 'size', path, 0, 250);\n oneOf(e, 'position', path, ['outside', 'inside', 'center']);\n fill(e.fill, `${path}.fill`, true);\n break;\n }\n}\n\n/**\n * Applies `changes` to `effect` and validates the result. `kind` and `id` cannot change. Nested\n * objects (`fill`, `gradient`, `contour`, `texture`, `phase`, `offset`) are replaced whole; `null`\n * removes an optional one (`contour`, `glossContour`, bevel `contour` / `texture`).\n */\nexport function mergeLayerEffect(effect: LayerEffect, changes: Record<string, unknown>): LayerEffect {\n if ('kind' in changes && changes.kind !== effect.kind) throw new LayerEffectError(`kind cannot change (${effect.kind}); remove the effect and add a new one.`);\n if ('id' in changes && changes.id !== effect.id) throw new LayerEffectError('id cannot change.');\n const next: Rec = { ...effect };\n for (const [k, v] of Object.entries(changes)) {\n if (v === null) delete next[k];\n else next[k] = v;\n }\n validateLayerEffect(next);\n return next as unknown as LayerEffect;\n}\n\n/** A layer's effects with `items` validated; throws on the first bad effect. */\nexport function validateLayerEffects(v: unknown): asserts v is LayerEffects {\n if (!v || typeof v !== 'object') fail('effects', 'must be { enabled, items }.');\n const r = v as Rec;\n bool(r, 'enabled', 'effects');\n if (r.scale !== undefined) num(r, 'scale', 'effects', 0.01, 10);\n if (!Array.isArray(r.items)) fail('effects.items', 'must be a list.');\n const ids = new Set<string>();\n r.items.forEach((e, i) => {\n validateLayerEffect(e, `effects.items[${i}]`);\n if (ids.has(e.id)) fail(`effects.items[${i}].id`, `duplicate id ${e.id}.`);\n ids.add(e.id);\n });\n}\n\n/**\n * Reads effects from JSON leniently: invalid effects are dropped (so one bad entry does not lose\n * a document), and missing optional fields take their defaults. `null` when nothing usable.\n */\nexport function normalizeLayerEffects(raw: unknown): LayerEffects | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const r = raw as Rec;\n const items: LayerEffect[] = [];\n const ids = new Set<string>();\n if (Array.isArray(r.items)) {\n for (const it of r.items) {\n if (!it || typeof it !== 'object' || !isLayerEffectKind((it as Rec).kind)) continue;\n const kind = (it as Rec).kind as LayerEffectKind;\n const merged = { ...defaultLayerEffect(kind), ...(it as Rec) } as Rec;\n if (typeof merged.id !== 'string' || !merged.id || ids.has(merged.id as string)) merged.id = newEffectId();\n try {\n validateLayerEffect(merged);\n } catch {\n continue;\n }\n ids.add(merged.id as string);\n items.push(merged as unknown as LayerEffect);\n }\n }\n const out: LayerEffects = { enabled: r.enabled !== false, items };\n if (typeof r.scale === 'number' && Number.isFinite(r.scale) && r.scale > 0 && r.scale !== 1) out.scale = r.scale;\n if (r.psd && typeof r.psd === 'object') {\n const p = r.psd as Rec;\n if (typeof p.key === 'string' && p.data instanceof Uint8Array) out.psd = { key: p.key, data: p.data };\n }\n return items.length || out.psd ? out : undefined;\n}\n\n/** Effects without ids or the PSD block, for comparing what two effect stacks render. */\nexport function effectsFingerprint(effects: LayerEffects | undefined | null): string {\n if (!effects) return '';\n const items = effects.items.map((e) => {\n const { id: _id, ...rest } = e;\n return rest;\n });\n return stableStringify([effects.enabled, effectsScale(effects), items]);\n}\n\n/** JSON with object keys sorted (and `undefined` fields left out), so key order never matters. */\nfunction stableStringify(v: unknown): string {\n if (Array.isArray(v)) return `[${v.map(stableStringify).join(',')}]`;\n if (v && typeof v === 'object') {\n const keys = Object.keys(v as Rec)\n .filter((k) => (v as Rec)[k] !== undefined)\n .sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify((v as Rec)[k])}`).join(',')}}`;\n }\n return JSON.stringify(v);\n}\n","// Portions ported from photocraft-main/crates/compose/src/pattern.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Pattern paint for pattern overlays, pattern strokes and bevel textures: a tile repeated over\n * the plane from an origin (the layer's top-left when linked, else the canvas origin, plus the\n * phase), scaled and rotated about it. Samples are bilinear and wrap around, on premultiplied\n * colour, so whole-pixel placements at 100 % reproduce the tile exactly.\n *\n * Patterns come from the document (PSD pattern resources) or from the built-in set below, so a\n * pattern effect always has something to paint even in a document without patterns.\n */\n\n/** A pattern tile: straight-alpha RGBA. */\nexport interface EffectPattern {\n id: string;\n name: string;\n width: number;\n height: number;\n data: Uint8ClampedArray;\n}\n\n/** A pattern converted once for sampling (premultiplied 0..1). */\nexport class PatternTile {\n readonly w: number;\n readonly h: number;\n private readonly px: Float32Array;\n\n constructor(p: Pick<EffectPattern, 'width' | 'height' | 'data'>) {\n this.w = Math.max(1, p.width | 0);\n this.h = Math.max(1, p.height | 0);\n const n = this.w * this.h;\n this.px = new Float32Array(n * 4);\n for (let i = 0; i < n; i++) {\n const a = (p.data[i * 4 + 3] ?? 0) / 255;\n this.px[i * 4] = ((p.data[i * 4] ?? 0) / 255) * a;\n this.px[i * 4 + 1] = ((p.data[i * 4 + 1] ?? 0) / 255) * a;\n this.px[i * 4 + 2] = ((p.data[i * 4 + 2] ?? 0) / 255) * a;\n this.px[i * 4 + 3] = a;\n }\n }\n\n /** Bilinear wrapping sample at tile coordinates (pixel centres at `i + 0.5`), straight alpha into `out`. */\n sample(u: number, v: number, out: Float32Array | number[]): void {\n u -= 0.5;\n v -= 0.5;\n const x0 = Math.floor(u);\n const y0 = Math.floor(v);\n const fx = u - x0;\n const fy = v - y0;\n let r = 0;\n let g = 0;\n let b = 0;\n let a = 0;\n for (let dy = 0; dy < 2; dy++) {\n const wy = dy ? fy : 1 - fy;\n if (wy === 0) continue;\n const yi = (((y0 + dy) % this.h) + this.h) % this.h;\n for (let dx = 0; dx < 2; dx++) {\n const wx = dx ? fx : 1 - fx;\n if (wx === 0) continue;\n const xi = (((x0 + dx) % this.w) + this.w) % this.w;\n const k = wx * wy;\n const i = (yi * this.w + xi) * 4;\n r += this.px[i] * k;\n g += this.px[i + 1] * k;\n b += this.px[i + 2] * k;\n a += this.px[i + 3] * k;\n }\n }\n if (a <= 0) {\n out[0] = out[1] = out[2] = out[3] = 0;\n return;\n }\n out[0] = r / a;\n out[1] = g / a;\n out[2] = b / a;\n out[3] = Math.min(1, a);\n }\n}\n\n/** Where a pattern sits: maps a document point to tile coordinates. */\nexport class PatternPlacement {\n private readonly ox: number;\n private readonly oy: number;\n private readonly c: number;\n private readonly s: number;\n private readonly inv: number;\n\n /** `anchor`: the layer's top-left (used when `link`); `phase` in px; `scale` a fraction; `angle` degrees counter-clockwise. */\n constructor(anchor: { x: number; y: number }, link: boolean, phase: { x: number; y: number }, scale: number, angle: number) {\n const bx = link ? anchor.x : 0;\n const by = link ? anchor.y : 0;\n this.ox = bx + phase.x;\n this.oy = by + phase.y;\n this.inv = Number.isFinite(scale) && scale > 1e-3 ? 1 / scale : 1;\n const a = ((Number.isFinite(angle) ? angle : 0) * Math.PI) / 180;\n this.c = Math.cos(a);\n this.s = Math.sin(a);\n }\n\n /** Tile coordinates of the document point `(x, y)` into `out[0..1]`. */\n map(x: number, y: number, out: number[]): void {\n const dx = x - this.ox;\n const dy = y - this.oy;\n // Undo a counter-clockwise rotation (y points down).\n out[0] = (dx * this.c - dy * this.s) * this.inv;\n out[1] = (dx * this.s + dy * this.c) * this.inv;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in patterns\n// ---------------------------------------------------------------------------\n\ntype Painter = (x: number, y: number) => [number, number, number, number];\n\nconst BUILTINS: Record<string, { name: string; size: number; paint: Painter }> = {\n checker: { name: 'Checker', size: 16, paint: (x, y) => ((x < 8) !== (y < 8) ? [204, 204, 204, 255] : [255, 255, 255, 255]) },\n stripes: { name: 'Diagonal Stripes', size: 16, paint: (x, y) => ((x + y) % 16 < 8 ? [40, 40, 40, 255] : [235, 235, 235, 255]) },\n dots: {\n name: 'Dots',\n size: 12,\n paint: (x, y) => {\n const d = Math.hypot(x + 0.5 - 6, y + 0.5 - 6);\n const a = Math.max(0, Math.min(1, 3.5 - d));\n return [Math.round(255 - a * 215), Math.round(255 - a * 215), Math.round(255 - a * 215), 255];\n },\n },\n grid: { name: 'Grid', size: 16, paint: (x, y) => (x === 0 || y === 0 ? [90, 90, 90, 255] : [250, 250, 250, 255]) },\n noise: {\n name: 'Noise',\n size: 64,\n paint: (x, y) => {\n const v = Math.round(hashNoise(x, y) * 255);\n return [v, v, v, 255];\n },\n },\n};\n\n/** Ids of the built-in patterns (`checker`, `stripes`, `dots`, `grid`, `noise`). */\nexport function builtinPatternIds(): string[] {\n return Object.keys(BUILTINS);\n}\n\nconst builtinCache = new Map<string, EffectPattern>();\n\n/** A built-in pattern by id, or `null`. */\nexport function builtinPattern(id: string): EffectPattern | null {\n const def = BUILTINS[id];\n if (!def) return null;\n let p = builtinCache.get(id);\n if (!p) {\n const data = new Uint8ClampedArray(def.size * def.size * 4);\n for (let y = 0; y < def.size; y++) {\n for (let x = 0; x < def.size; x++) data.set(def.paint(x, y), (y * def.size + x) * 4);\n }\n p = { id, name: def.name, width: def.size, height: def.size, data };\n builtinCache.set(id, p);\n }\n return p;\n}\n\n/**\n * Deterministic per-pixel value in 0..1 hashed from document coordinates, so effect noise is\n * stable across renders and tiles (photocraft's `hash_noise`).\n */\nexport function hashNoise(x: number, y: number): number {\n let h = Math.imul(x | 0, 0x8da6b343) ^ Math.imul(y | 0, 0xd8163841) ^ 0x9e3779b9;\n h ^= h >>> 15;\n h = Math.imul(h, 0x2c1b3c6d);\n h ^= h >>> 12;\n return (h & 0xffff) / 65536;\n}\n","/**\n * Curve lookup tables shared by Curves adjustments and layer-effect contours.\n */\n\n/** Entries of a curve LUT (input 0..1 in `size - 1` steps). */\nexport const CURVE_LUT_SIZE = 4096;\n\nconst clamp01 = (v: number) => (v <= 0 ? 0 : v >= 1 ? 1 : v);\n\n/**\n * Photoshop curve: a natural cubic spline through the points (0..1 in and out; it may overshoot\n * between points, as Photoshop's does), constant outside the first / last point, clamped to\n * 0..1, sampled into a LUT. Fewer than two distinct points: the identity.\n */\nexport function curveLut(points: ReadonlyArray<{ input: number; output: number }>, size = CURVE_LUT_SIZE): Float32Array {\n const pts = points\n .filter((p) => Number.isFinite(p.input) && Number.isFinite(p.output))\n .map((p) => [p.input, p.output] as [number, number])\n .sort((a, b) => a[0] - b[0]);\n const dedup: [number, number][] = [];\n for (const p of pts) {\n if (dedup.length && Math.abs(dedup[dedup.length - 1][0] - p[0]) < 1e-6) continue;\n dedup.push(p);\n }\n const out = new Float32Array(size);\n const n = dedup.length;\n if (n < 2) {\n for (let k = 0; k < size; k++) out[k] = k / (size - 1);\n return out;\n }\n const x = dedup.map((p) => p[0]);\n const y = dedup.map((p) => p[1]);\n // Second derivatives of the natural spline (tridiagonal solve).\n const m2 = new Float64Array(n);\n if (n > 2) {\n const c = new Float64Array(n);\n const d = new Float64Array(n);\n for (let i = 1; i < n - 1; i++) {\n const h0 = x[i] - x[i - 1];\n const h1 = x[i + 1] - x[i];\n const a = h0 / 6;\n const b = (h0 + h1) / 3;\n const cc = h1 / 6;\n const r = (y[i + 1] - y[i]) / h1 - (y[i] - y[i - 1]) / h0;\n const denom = b - a * c[i - 1];\n c[i] = cc / denom;\n d[i] = (r - a * d[i - 1]) / denom;\n }\n for (let i = n - 2; i >= 1; i--) m2[i] = d[i] - c[i] * m2[i + 1];\n }\n let seg = 0;\n for (let k = 0; k < size; k++) {\n const xv = k / (size - 1);\n if (xv <= x[0]) {\n out[k] = clamp01(y[0]);\n continue;\n }\n if (xv >= x[n - 1]) {\n out[k] = clamp01(y[n - 1]);\n continue;\n }\n while (seg < n - 2 && xv > x[seg + 1]) seg++;\n const x0 = x[seg], x1 = x[seg + 1];\n const h = x1 - x0;\n const a = (x1 - xv) / h;\n const b = (xv - x0) / h;\n const v = a * y[seg] + b * y[seg + 1] + ((a * a * a - a) * m2[seg] + (b * b * b - b) * m2[seg + 1]) * (h * h) / 6;\n out[k] = clamp01(v);\n }\n return out;\n}\n","// Portions ported from photocraft-main/crates/compose/src/effects.rs and fill_layout.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * CPU renderer for layer effects.\n *\n * Render order, bottom to top (Photoshop's layer-style stack read upwards): drop shadows, outer\n * glows, then the layer itself (content at fill opacity) with pattern, gradient and colour\n * overlays, satin, inner glows, inner shadows, the inside part of strokes and inner bevels on\n * top. The outside part of strokes and outer bevels go beneath the layer, onto the backdrop.\n * Exterior effects blend straight into the backdrop with their own modes; the layer blends with\n * its mode and, in a mode other than Normal, is merged into the backdrop before the interior\n * effects paint over it (Photoshop's \"Blend Interior Effects as Group\" off). Emboss and pillow\n * emboss then shade the composited result. Layer opacity applies to the whole stack; fill\n * opacity only to the layer's own pixels.\n *\n * Every map depends only on the layer's shape (its alpha after the mask), the effect settings\n * and the global light, never on the backdrop. {@link buildEffectMaps} computes them once over the\n * layer's whole region; {@link compositeLayerEffects} paints any part of that region (a tile) from\n * them, pixel by pixel, so a tiled render equals a whole-image render.\n *\n * Distances: a stroke's outer edge uses the 5 × 5 chamfer below 20 px and an exact Euclidean\n * transform from there (the chamfer's octagonal level sets facet wide strokes); inside strokes and\n * spread / choke keep the chamfer; precise glows and chiselled bevels use the exact transform.\n * Soft falloffs are two box blurs (a tent).\n */\n\nimport { blendChannel, blendRgb, isSeparableBlendMode, type BlendMode } from '../blend';\nimport { curveLut } from '../curve';\nimport {\n effectsScale,\n hasActiveEffects,\n parseEffectColor,\n scaleLayerEffect,\n MAX_EFFECT_REACH,\n type BevelEmbossEffect,\n type EffectContour,\n type EffectFill,\n type EffectGradient,\n type EffectPatternRef,\n type GlobalLight,\n type GradientStyle,\n type InnerGlowEffect,\n type LayerEffect,\n type LayerEffects,\n type OuterGlowEffect,\n type StrokeEffect,\n} from './model';\nimport { hashNoise, PatternPlacement, PatternTile } from './patterns';\n\nexport interface EffectRect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** Alpha above which a pixel belongs to the layer's shape (half an 8-bit step). */\nconst INSIDE_EPS = 0.5 / 255;\nconst INF = 1e20;\nconst FAR = 1e9;\n/** Outside stroke width from which the outer edge uses the exact Euclidean transform. */\nexport const STROKE_CHAMFER_MAX = 20;\n/** Height below which an outer bevel pixel is off the bevel. */\nconst BEVEL_H_EPS = 1e-5;\n\ninterface FMap {\n w: number;\n h: number;\n v: Float32Array;\n}\n\nconst fmap = (w: number, h: number, fill = 0): FMap => {\n const v = new Float32Array(w * h);\n if (fill) v.fill(fill);\n return { w, h, v };\n};\n\nconst clamp = (v: number, lo: number, hi: number) => (v < lo ? lo : v > hi ? hi : v);\nconst clamp01 = (v: number) => (v <= 0 ? 0 : v >= 1 ? 1 : v);\n\nfunction mapGet(m: FMap, x: number, y: number): number {\n return x < 0 || y < 0 || x >= m.w || y >= m.h ? 0 : m.v[y * m.w + x];\n}\n\n/** Bilinear shifted copy; reads outside the map give `outside`. */\nfunction shifted(m: FMap, dx: number, dy: number, outside: number): FMap {\n const reach = (d: number) => (Number.isFinite(d) ? clamp(d, -1e9, 1e9) : 0);\n dx = reach(dx);\n dy = reach(dy);\n const fx = Math.floor(dx);\n const fy = Math.floor(dy);\n const ax = dx - fx;\n const ay = dy - fy;\n const { w, h, v } = m;\n const s = (x: number, y: number) => (x < 0 || y < 0 || x >= w || y >= h ? outside : v[y * w + x]);\n const out = fmap(w, h);\n for (let y = 0; y < h; y++) {\n const sy = y - fy;\n for (let x = 0; x < w; x++) {\n const sx = x - fx;\n const top = s(sx, sy) * (1 - ax) + s(sx - 1, sy) * ax;\n const bot = s(sx, sy - 1) * (1 - ax) + s(sx - 1, sy - 1) * ax;\n out.v[y * w + x] = top * (1 - ay) + bot * ay;\n }\n }\n return out;\n}\n\nfunction mapped(m: FMap, f: (v: number) => number): FMap {\n const out = fmap(m.w, m.h);\n for (let i = 0; i < m.v.length; i++) out.v[i] = f(m.v[i]);\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Distance transforms\n// ---------------------------------------------------------------------------\n\n/** 1D squared distance transform (Felzenszwalb & Huttenlocher) with the nearest site. */\nfunction dt1(f: Float64Array, n: number, out: Float64Array, near: Int32Array, v: Int32Array, z: Float64Array): void {\n let k = 0;\n v[0] = 0;\n z[0] = -Infinity;\n z[1] = Infinity;\n for (let q = 1; q < n; q++) {\n for (;;) {\n const p = v[k];\n const s = (f[q] + q * q - (f[p] + p * p)) / (2 * q - 2 * p);\n if (s <= z[k] && k > 0) {\n k--;\n continue;\n }\n if (s <= z[k]) {\n v[0] = q;\n z[0] = -Infinity;\n z[1] = Infinity;\n break;\n }\n k++;\n v[k] = q;\n z[k] = s;\n z[k + 1] = Infinity;\n break;\n }\n }\n k = 0;\n for (let q = 0; q < n; q++) {\n while (z[k + 1] < q) k++;\n const p = v[k];\n const d = q - p;\n out[q] = d * d + f[p];\n near[q] = p;\n }\n}\n\n/** Exact Euclidean distance from every pixel to the nearest `inside` pixel, plus that pixel's index. */\nfunction edtNearest(inside: Uint8Array, w: number, h: number): { d: Float32Array; near: Int32Array } {\n const total = w * h;\n const n = Math.max(w, h);\n const f = new Float64Array(n);\n const o = new Float64Array(n);\n const nr = new Int32Array(n);\n const v = new Int32Array(n);\n const z = new Float64Array(n + 1);\n // Columns first: gc[x * h + y], nc = nearest row.\n const gc = new Float64Array(total);\n const nc = new Int32Array(total);\n for (let x = 0; x < w; x++) {\n for (let y = 0; y < h; y++) f[y] = inside[y * w + x] ? 0 : INF;\n dt1(f, h, o, nr, v, z);\n for (let y = 0; y < h; y++) {\n gc[x * h + y] = o[y];\n nc[x * h + y] = nr[y];\n }\n }\n const d = new Float32Array(total);\n const near = new Int32Array(total);\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) f[x] = gc[x * h + y];\n dt1(f, w, o, nr, v, z);\n for (let x = 0; x < w; x++) {\n d[y * w + x] = Math.sqrt(o[x]);\n const px = nr[x];\n near[y * w + x] = nc[px * h + y] * w + px;\n }\n }\n return { d, near };\n}\n\nconst S2 = Math.SQRT2;\nconst S5 = 2.236068;\nconst FWD: ReadonlyArray<readonly [number, number, number]> = [\n [-1, 0, 1],\n [-1, -1, S2],\n [0, -1, 1],\n [1, -1, S2],\n [-1, -2, S5],\n [1, -2, S5],\n [-2, -1, S5],\n [2, -1, S5],\n];\n\n/**\n * Chamfer distance with a 5 × 5 mask of exact step lengths (1, √2, √5) from per-pixel start\n * values (`INF` = not a seed): the minimum over seeds of start + path length. Photoshop's effect\n * distances follow this metric.\n */\nfunction chamferFrom(d: Float32Array, w: number, h: number): Float32Array {\n const ox = FWD.map((f) => f[0]);\n const oy = FWD.map((f) => f[1]);\n const wt = FWD.map((f) => f[2]);\n const k = FWD.length;\n for (let pass = 0; pass < 2; pass++) {\n const sign = pass === 0 ? 1 : -1;\n for (let yy = 0; yy < h; yy++) {\n const y = pass === 0 ? yy : h - 1 - yy;\n for (let xx = 0; xx < w; xx++) {\n const x = pass === 0 ? xx : w - 1 - xx;\n const i = y * w + x;\n let best = d[i];\n for (let j = 0; j < k; j++) {\n const nx = x + ox[j] * sign;\n const ny = y + oy[j] * sign;\n if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;\n const c = d[ny * w + nx] + wt[j];\n if (c < best) best = c;\n }\n d[i] = best;\n }\n }\n }\n return d;\n}\n\n/**\n * Distance from each pixel centre outside the shape to its edge; the edge inside the nearest\n * covered pixel is placed by its coverage. Inside pixels get -0.5.\n */\nfunction distOutside(s: FMap, chamfer: boolean): Float32Array {\n const { w, h, v } = s;\n const n = v.length;\n const out = new Float32Array(n);\n if (chamfer) {\n const start = new Float32Array(n);\n for (let i = 0; i < n; i++) start[i] = v[i] > INSIDE_EPS ? 1 - Math.min(v[i], 1) : INF;\n const d = chamferFrom(start, w, h);\n for (let i = 0; i < n; i++) out[i] = v[i] > INSIDE_EPS ? -0.5 : d[i] - 0.5;\n return out;\n }\n const inside = new Uint8Array(n);\n for (let i = 0; i < n; i++) inside[i] = v[i] > INSIDE_EPS ? 1 : 0;\n const { d, near } = edtNearest(inside, w, h);\n for (let i = 0; i < n; i++) out[i] = inside[i] ? -0.5 : d[i] - 0.5 + (1 - Math.min(v[near[i]], 1));\n return out;\n}\n\n/** Alpha relative to its 3 × 3 neighbourhood maximum (edge coverage apart from the fill's own opacity). */\nfunction localCoverage(s: FMap): FMap {\n const out: FMap = { w: s.w, h: s.h, v: new Float32Array(s.v) };\n for (let y = 0; y < s.h; y++) {\n for (let x = 0; x < s.w; x++) {\n const a = s.v[y * s.w + x];\n if (a <= INSIDE_EPS) continue;\n let mx = a;\n for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) mx = Math.max(mx, mapGet(s, x + dx, y + dy));\n out.v[y * s.w + x] = Math.min(a / mx, 1);\n }\n }\n return out;\n}\n\n/** Distance from each pixel centre inside the shape to its edge (-0.5 outside). */\nfunction distInside(s: FMap, chamfer: boolean): Float32Array {\n const { w, h, v } = s;\n const n = v.length;\n const out = new Float32Array(n);\n if (chamfer) {\n // Partly covered edge pixels are seeds starting at their coverage.\n const cov = localCoverage(s);\n const start = new Float32Array(n);\n for (let i = 0; i < n; i++) start[i] = v[i] <= INSIDE_EPS ? 0 : cov.v[i] < 1 - INSIDE_EPS ? cov.v[i] : INF;\n const d = chamferFrom(start, w, h);\n for (let i = 0; i < n; i++) out[i] = v[i] <= INSIDE_EPS ? -0.5 : d[i] - 0.5;\n return out;\n }\n const outside = new Uint8Array(n);\n for (let i = 0; i < n; i++) outside[i] = v[i] <= INSIDE_EPS ? 1 : 0;\n const { d } = edtNearest(outside, w, h);\n for (let i = 0; i < n; i++) out[i] = outside[i] ? -0.5 : d[i] - 0.5;\n return out;\n}\n\n/**\n * Signed distances to the shape's edge for chiselled bevels, measured to the straight edge through\n * the nearest edge pixel: the edge's normal is the coverage gradient there and its position the\n * pixel's coverage. Distances to pixel centres (the plain transforms) band along their Voronoi\n * cells, which a chisel's lighting shows as streaks; the edge line keeps a facet planar along an\n * anti-aliased edge. Beyond `reach` the plain distances are used. `din` is ≤ -0.5 outside the\n * shape, `dout` ≤ -0.5 inside it (with partly covered pixels between).\n */\nfunction edgeDistances(s: FMap, reach: number): { din: Float32Array; dout: Float32Array } {\n const { w, h, v } = s;\n const n = v.length;\n const edge = new Uint8Array(n);\n const inside = new Uint8Array(n);\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = y * w + x;\n const a = v[i];\n if (a <= INSIDE_EPS) continue;\n inside[i] = 1;\n if (a < 1 - INSIDE_EPS || mapGet(s, x - 1, y) <= INSIDE_EPS || mapGet(s, x + 1, y) <= INSIDE_EPS || mapGet(s, x, y - 1) <= INSIDE_EPS || mapGet(s, x, y + 1) <= INSIDE_EPS) edge[i] = 1;\n }\n }\n const { d, near } = edtNearest(edge, w, h);\n const din = new Float32Array(n);\n const dout = new Float32Array(n);\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = y * w + x;\n const q = near[i];\n let sd: number;\n if (d[i] > reach || d[i] >= INF / 2) {\n // Far from the edge: plain distance to the nearest edge pixel's centre.\n sd = inside[i] ? -(d[i] + 0.5) : d[i] - 0.5;\n } else {\n const qx = q % w;\n const qy = (q - qx) / w;\n // Outward normal: against the coverage gradient (Sobel) at the edge pixel.\n const g = (dx: number, dy: number) => mapGet(s, qx + dx, qy + dy);\n let nx = -(g(1, -1) + 2 * g(1, 0) + g(1, 1) - g(-1, -1) - 2 * g(-1, 0) - g(-1, 1));\n let ny = -(g(-1, 1) + 2 * g(0, 1) + g(1, 1) - g(-1, -1) - 2 * g(0, -1) - g(1, -1));\n const len = Math.hypot(nx, ny);\n if (len > 1e-6) {\n nx /= len;\n ny /= len;\n } else if (d[i] > 0) {\n nx = (x - qx) / d[i];\n ny = (y - qy) / d[i];\n } else {\n nx = 1;\n ny = 0;\n }\n // The edge crosses q at (coverage - ½) along the normal (an axis-aligned edge's offset).\n sd = (x - qx) * nx + (y - qy) * ny - (Math.min(v[q], 1) - 0.5);\n }\n din[i] = inside[i] ? Math.max(-sd, inside[i] && !edge[i] ? 0.5 : -0.5) : -0.5;\n dout[i] = inside[i] && !edge[i] ? -0.5 : Math.max(sd, -0.5);\n }\n }\n return { din, dout };\n}\n\n/** Grows the shape by `r` px (anti-aliased, chamfer metric), keeping its soft edge. */\nfunction dilate(s: FMap, r: number): FMap {\n if (!(r > 0)) return s;\n const d = distOutside(s, true);\n const out: FMap = { w: s.w, h: s.h, v: new Float32Array(s.v) };\n for (let i = 0; i < d.length; i++) {\n const o = out.v[i];\n out.v[i] = d[i] < 0 ? Math.min(o + r, 1) : Math.max(o, clamp01(r + 0.5 - d[i]));\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Tent blur (two box passes of a fractional width)\n// ---------------------------------------------------------------------------\n\nfunction boxWidth(w: number): number {\n return Number.isNaN(w) ? 1 : clamp(w, 1, MAX_EFFECT_REACH);\n}\n\n/** (r, end-tap weight, 1 / width) of a box of fractional width. */\nfunction boxGeom(bw: number): [number, number, number] {\n bw = boxWidth(bw);\n const half = bw / 2;\n const r = Math.max(0, Math.ceil(half - 0.5));\n const f = clamp01(half - (r - 0.5));\n return [r, f, 1 / bw];\n}\n\n/** One box pass over `src` (zero outside it) evaluated at `x0 .. x0 + dst.length`, as a running sum. */\nfunction boxLine(src: Float64Array, x0: number, dst: Float64Array, bw: number): void {\n const n = src.length;\n const [r, f, norm] = boxGeom(bw);\n const at = (i: number) => (i >= 0 && i < n ? src[i] : 0);\n if (r === 0) {\n for (let k = 0; k < dst.length; k++) dst[k] = at(x0 + k);\n return;\n }\n let inner = 0;\n for (let i = x0 - (r - 1); i <= x0 + (r - 1); i++) inner += at(i);\n for (let k = 0; k < dst.length; k++) {\n const x = x0 + k;\n dst[k] = (inner + f * (at(x - r) + at(x + r))) * norm;\n inner += at(x + r) - at(x - r + 1);\n }\n}\n\n/** Values a running-sum blur leaves where it should be zero (float residue) are flushed below this. */\nconst BLUR_EPS = 1e-7;\n\nfunction tentLine(src: Float64Array, dst: Float64Array, bw: number, mid: Float64Array): void {\n const [r] = boxGeom(bw);\n boxLine(src, -r, mid.subarray(0, src.length + 2 * r), bw);\n boxLine(mid.subarray(0, src.length + 2 * r), r, dst, bw);\n // The running sum is not exactly zero after it passes a shape: flush the residue so it never\n // reads as coverage (noise would speckle it far from the shape).\n for (let i = 0; i < dst.length; i++) if (Math.abs(dst[i]) < BLUR_EPS) dst[i] = 0;\n}\n\n/** Two box passes of width `w` along each axis, zero outside the map. */\nfunction tent(m: FMap, w: number): void {\n if (!Number.isFinite(w) || m.v.length === 0) return;\n // A box of radius 0 is the identity.\n if (boxGeom(w)[0] === 0) return;\n const { w: mw, h: mh } = m;\n const r = boxGeom(w)[0];\n const mid = new Float64Array(Math.max(mw, mh) + 2 * r);\n const row = new Float64Array(mw);\n const rowOut = new Float64Array(mw);\n for (let y = 0; y < mh; y++) {\n for (let x = 0; x < mw; x++) row[x] = m.v[y * mw + x];\n tentLine(row, rowOut, w, mid);\n for (let x = 0; x < mw; x++) m.v[y * mw + x] = rowOut[x];\n }\n const col = new Float64Array(mh);\n const colOut = new Float64Array(mh);\n for (let x = 0; x < mw; x++) {\n for (let y = 0; y < mh; y++) col[y] = m.v[y * mw + x];\n tentLine(col, colOut, w, mid);\n for (let y = 0; y < mh; y++) m.v[y * mw + x] = colOut[y];\n }\n}\n\nfunction blur(m: FMap, size: number): void {\n if (Number.isFinite(size)) tent(m, size);\n}\n\n// ---------------------------------------------------------------------------\n// Contours\n// ---------------------------------------------------------------------------\n\nconst lutCache = new WeakMap<EffectContour, Float32Array | null>();\n\n/** A contour as a 0..1 LUT (corner points split the spline), or `null` for linear. */\nexport function contourLut(c: EffectContour | undefined): Float32Array | null {\n if (!c) return null;\n const cached = lutCache.get(c);\n if (cached !== undefined) return cached;\n const pts = c.points;\n const linear = pts.length === 2 && pts[0].input === 0 && pts[0].output === 0 && pts[1].input === 1 && pts[1].output === 1;\n let lut: Float32Array | null = null;\n if (pts.length >= 2 && !linear) {\n if (!pts.some((p) => p.corner)) lut = curveLut(pts);\n else {\n const sorted = [...pts].sort((a, b) => a.input - b.input);\n let start = 0;\n for (let i = 1; i < sorted.length; i++) {\n if (!(sorted[i].corner || i === sorted.length - 1)) continue;\n const seg = sorted.slice(start, i + 1);\n const from = seg[0].input;\n const part = curveLut(seg);\n if (!lut) lut = part;\n else for (let k = 0; k < part.length; k++) if (k / (part.length - 1) >= from) lut[k] = part[k];\n start = i;\n }\n lut ??= curveLut(pts);\n }\n }\n lutCache.set(c, lut);\n return lut;\n}\n\nfunction lutAt(l: Float32Array, v: number): number {\n const x = clamp01(v) * (l.length - 1);\n const i = Math.floor(x);\n const j = Math.min(i + 1, l.length - 1);\n return l[i] + (l[j] - l[i]) * (x - i);\n}\n\nfunction applyLut(m: FMap, l: Float32Array | null): FMap {\n return l ? mapped(m, (v) => lutAt(l, v)) : m;\n}\n\n/** A contour applied over a Range (`contour(min(v / range, 1))`), `null` for the identity. */\nfunction rangedLut(c: EffectContour | undefined, range: number): Float32Array | null {\n const base = contourLut(c);\n const r = Number.isFinite(range) ? clamp(range, 0.01, 1) : 1;\n if (r >= 0.999) return base;\n const n = 4096;\n const out = new Float32Array(n);\n for (let k = 0; k < n; k++) {\n const v = Math.min(k / (n - 1) / r, 1);\n out[k] = base ? lutAt(base, v) : v;\n }\n return out;\n}\n\n/** Satin's transfer: the contour read from its far end, `|c(1 − v) − c(1)|`. */\nfunction satinLut(c: EffectContour | undefined): Float32Array | null {\n const lut = contourLut(c);\n if (!lut) return null;\n const end = lut[lut.length - 1];\n const out = new Float32Array(lut.length);\n for (let k = 0; k < lut.length; k++) out[k] = Math.abs(lut[lut.length - 1 - k] - end);\n return out;\n}\n\n/** Monochrome speckle in the coverage (Noise), hashed from document coordinates. */\nfunction noise(m: FMap, amount: number, x0: number, y0: number): void {\n if (!Number.isFinite(amount) || amount <= 0) return;\n for (let y = 0; y < m.h; y++) {\n for (let x = 0; x < m.w; x++) {\n const i = y * m.w + x;\n if (m.v[i] <= BLUR_EPS) continue;\n m.v[i] = clamp01(m.v[i] + (hashNoise(x0 + x, y0 + y) - 0.5) * 2 * amount);\n }\n }\n}\n\n/** Offset of an effect lit from `angle` at `distance` (falls away from the light), in whole pixels. */\nfunction lightOffset(angle: number, distance: number): [number, number] {\n const a = (angle * Math.PI) / 180;\n return [Math.round(-Math.cos(a) * distance), Math.round(Math.sin(a) * distance)];\n}\n\n/** (dilation radius, blur width) of a spread / choke. */\nfunction spreadSplit(size: number, spread: number): [number, number] {\n return [Math.round(size * spread), size * (1 - spread)];\n}\n\n// ---------------------------------------------------------------------------\n// Effect maps\n// ---------------------------------------------------------------------------\n\ntype ShadowLike = { color: string; angle: number; useGlobalLight: boolean; distance: number; size: number; contour?: EffectContour; noise: number };\n\nfunction shadowMap(shape: FMap, s: ShadowLike, spread: number, light: GlobalLight, inner: boolean, ox: number, oy: number): FMap {\n const angle = s.useGlobalLight ? light.angle : s.angle;\n const [dx, dy] = lightOffset(angle, s.distance);\n const src = inner ? mapped(shape, (a) => 1 - a) : shape;\n let m = shifted(src, dx, dy, inner ? 1 : 0);\n const [r, bw] = spreadSplit(s.size, spread);\n m = dilate(m, r);\n blur(m, bw);\n m = applyLut(m, contourLut(s.contour));\n noise(m, s.noise, ox, oy);\n if (inner) for (let i = 0; i < m.v.length; i++) m.v[i] *= shape.v[i];\n return m;\n}\n\nfunction glowMap(shape: FMap, g: OuterGlowEffect | InnerGlowEffect, ox: number, oy: number): FMap {\n const inner = g.kind === 'inner-glow';\n const spread = inner ? g.choke : g.spread;\n const src = inner ? mapped(shape, (a) => 1 - a) : shape;\n let m: FMap;\n if (g.technique === 'precise') {\n // Coverage falls linearly from 0.5 at the edge to 0 at `size + 1` px, solid within the spread.\n const d = inner ? distInside(shape, false) : distOutside(src, false);\n const solid = g.size * spread;\n const soft = Math.max(g.size + 1 - solid, 1e-3);\n m = fmap(shape.w, shape.h);\n for (let i = 0; i < d.length; i++) {\n const di = d[i] + 0.5;\n m.v[i] = di <= solid ? 1 : 0.5 * clamp01(1 - (di - solid) / soft);\n }\n } else {\n const [r, bw] = spreadSplit(g.size, spread);\n m = dilate(src, r);\n if (m === src || m === shape) m = { w: m.w, h: m.h, v: new Float32Array(m.v) };\n blur(m, bw);\n }\n m = applyLut(m, rangedLut(g.contour, g.range));\n if (inner && g.source === 'center') m = mapped(m, (v) => 1 - v);\n noise(m, g.noise, ox, oy);\n if (inner) for (let i = 0; i < m.v.length; i++) m.v[i] *= shape.v[i];\n return m;\n}\n\nfunction satinMap(shape: FMap, s: { angle: number; distance: number; size: number; invert: boolean; contour?: EffectContour }): FMap {\n const [dx, dy] = lightOffset(s.angle, Math.max(1, s.distance));\n const a = shifted(shape, dx, dy, 0);\n const b = shifted(shape, -dx, -dy, 0);\n tent(a, s.size);\n tent(b, s.size);\n let m = fmap(shape.w, shape.h);\n for (let i = 0; i < m.v.length; i++) m.v[i] = Math.abs(a.v[i] - b.v[i]);\n if (s.invert) m = mapped(m, (v) => 1 - v);\n m = applyLut(m, satinLut(s.contour));\n for (let i = 0; i < m.v.length; i++) m.v[i] *= shape.v[i];\n return m;\n}\n\n/** Where a bevel paints: inside the shape, outside it, or both (emboss styles). */\ntype BevelPaint = 'inner' | 'outer' | 'both';\n\ninterface BevelGeom {\n paint: BevelPaint;\n width: number;\n depth: number;\n pillow: boolean;\n chiselSoft: number;\n}\n\nfunction bevelGeom(b: BevelEmbossEffect): BevelGeom {\n const size = Math.max(b.size, 1);\n const emboss = b.style === 'emboss' || b.style === 'pillow-emboss';\n const paint: BevelPaint = b.style === 'outer-bevel' ? 'outer' : emboss ? 'both' : 'inner';\n // Emboss styles straddle the edge with half the size, rounded up to whole pixels.\n const width = emboss ? Math.ceil(size / 2) : size;\n const smooth = b.technique === 'smooth';\n const sign = b.direction === 'up' ? 1 : -1;\n return {\n paint,\n width,\n depth: b.depth * (smooth ? width : size / 2) * sign,\n pillow: b.style === 'pillow-emboss',\n chiselSoft: b.technique === 'chisel-soft' ? Math.max(size / 4, 1) : 0,\n };\n}\n\n/** Chiselled height from the inside / outside distances. */\nfunction bevelChiselH(paint: BevelPaint, size: number, din: number, dout: number, soft: boolean): number {\n const ramp = size + 1;\n if (paint === 'outer' && !soft) {\n const top = (size + 2) / ramp;\n return dout > 0 ? clamp((size + 1.5 - dout) / ramp, 0, top) : top;\n }\n if (paint === 'outer') return dout > 0 ? 1 - clamp01((dout + 0.5) / ramp) : (size + 2) / ramp;\n if (paint === 'both') {\n const half = size / 2 + 0.5;\n return din > 0 ? 0.5 + 0.5 * clamp01(din / half) : 0.5 - 0.5 * clamp01(dout / half);\n }\n if (soft) return din > 0 ? clamp((din + 1.5) / ramp, 0, (size + 2) / ramp) : 0;\n return din > 0 ? clamp01((din + 0.5) / ramp) : 0;\n}\n\nexport interface EffectContext {\n light: GlobalLight;\n /** A pattern tile by id, or `null` when missing (pattern effects then paint nothing). */\n pattern(id: string): PatternTile | null;\n}\n\nfunction bevelHeight(shape: FMap, b: BevelEmbossEffect, g: BevelGeom, rect: EffectRect, anchor: { x: number; y: number }, ctx: EffectContext): FMap {\n const size = Math.max(b.size, 1);\n let h: FMap;\n if (b.technique === 'smooth') {\n h = { w: shape.w, h: shape.h, v: new Float32Array(shape.v) };\n tent(h, g.width);\n } else {\n const { din, dout } = edgeDistances(shape, size + 2);\n h = fmap(shape.w, shape.h);\n const soft = g.chiselSoft > 0;\n for (let i = 0; i < h.v.length; i++) h.v[i] = bevelChiselH(g.paint, size, din[i], dout[i], soft);\n if (soft && g.paint === 'both') tent(h, g.chiselSoft);\n }\n if (b.contourElement) h = applyLut(h, contourLut(b.contourElement.contour) ?? identityLut());\n const t = b.texture;\n const tile = t ? ctx.pattern(t.patternId) : null;\n if (t && tile) {\n const unit = Math.abs(b.depth) > 1e-6 ? Math.max(Math.abs(g.depth / b.depth), 1e-3) : Math.max(g.width, 1);\n const place = new PatternPlacement(anchor, t.link ?? true, t.phase ?? { x: 0, y: 0 }, t.scale, 0);\n const k = t.depth / unit;\n const uv = [0, 0];\n const p = [0, 0, 0, 0];\n for (let y = 0; y < h.h; y++) {\n for (let x = 0; x < h.w; x++) {\n place.map(rect.x + x + 0.5, rect.y + y + 0.5, uv);\n tile.sample(uv[0], uv[1], p);\n const l = (0.299 * p[0] + 0.587 * p[1] + 0.114 * p[2]) * p[3];\n h.v[y * h.w + x] += k * (t.invert ? 1 - l : l);\n }\n }\n }\n if (b.soften >= 1) tent(h, b.soften);\n return h;\n}\n\nlet identity: Float32Array | null = null;\nfunction identityLut(): Float32Array {\n if (!identity) {\n identity = new Float32Array(4096);\n for (let k = 0; k < 4096; k++) identity[k] = k / 4095;\n }\n return identity;\n}\n\n/** Bevel highlight and shadow maps (`[hi, sh]`) and where they paint. */\nfunction bevelMaps(shape: FMap, b: BevelEmbossEffect, rect: EffectRect, anchor: { x: number; y: number }, ctx: EffectContext): { maps: FMap[]; paint: BevelPaint } {\n const g = bevelGeom(b);\n const hmap = bevelHeight(shape, b, g, rect, anchor, ctx);\n const angle = b.useGlobalLight ? ctx.light.angle : b.angle;\n const altitude = b.useGlobalLight ? ctx.light.altitude : b.altitude;\n const sa = Math.sin((angle * Math.PI) / 180);\n const ca = Math.cos((angle * Math.PI) / 180);\n const se = Math.sin((altitude * Math.PI) / 180);\n const ce = Math.cos((altitude * Math.PI) / 180);\n const lx = ca * ce;\n const ly = -sa * ce;\n const lz = se;\n const reachPast = g.paint === 'both' || (g.paint === 'outer' && b.technique !== 'smooth');\n const onBevel = (x: number, y: number) =>\n mapGet(hmap, x, y) > BEVEL_H_EPS ||\n (reachPast && (mapGet(hmap, x + 1, y) > BEVEL_H_EPS || mapGet(hmap, x - 1, y) > BEVEL_H_EPS || mapGet(hmap, x, y + 1) > BEVEL_H_EPS || mapGet(hmap, x, y - 1) > BEVEL_H_EPS));\n const gloss = contourLut(b.glossContour);\n const shadeInto = (depth: number, outside: boolean): [FMap, FMap] => {\n const hi = fmap(shape.w, shape.h);\n const sh = fmap(shape.w, shape.h);\n for (let y = 0; y < shape.h; y++) {\n for (let x = 0; x < shape.w; x++) {\n const i = y * shape.w + x;\n const gx = (mapGet(hmap, x + 1, y) - mapGet(hmap, x - 1, y)) * 0.5 * depth;\n const gy = (mapGet(hmap, x, y + 1) - mapGet(hmap, x, y - 1)) * 0.5 * depth;\n const len = Math.sqrt(gx * gx + gy * gy + 1);\n const shade = (-gx * lx - gy * ly + lz) / len;\n const region = outside ? (shape.v[i] < 1 - INSIDE_EPS && onBevel(x, y) ? 1 : 0) : shape.v[i];\n const k = shade - se;\n if (k > 0) hi.v[i] = clamp01(k / Math.max(1 - se, 1e-3)) * region;\n else sh.v[i] = clamp01(-k / Math.max(se, 1e-3)) * region;\n }\n }\n return [applyLut(hi, gloss), applyLut(sh, gloss)];\n };\n if (g.paint !== 'both') {\n const [hi, sh] = shadeInto(g.depth, g.paint === 'outer');\n return { maps: [hi, sh], paint: g.paint };\n }\n // One map per colour over the whole bevel: the inside half where the shape is, the outside half\n // (pillow: facing the other way) for the rest of each pixel.\n const [hi, sh] = shadeInto(g.depth, false);\n const [ho, so] = shadeInto(g.pillow ? -g.depth : g.depth, true);\n const mix = (a: FMap, o: FMap): FMap => {\n const out = fmap(a.w, a.h);\n for (let i = 0; i < out.v.length; i++) {\n const s = shape.v[i];\n out.v[i] = (s > INSIDE_EPS ? a.v[i] : 0) + o.v[i] * (1 - Math.min(s, 1));\n }\n return out;\n };\n return { maps: [mix(hi, ho), mix(sh, so)], paint: 'both' };\n}\n\n/** (inside width, outside width) of a stroke. */\nfunction strokeWidths(s: StrokeEffect): [number, number] {\n return s.position === 'outside' ? [0, s.size] : s.position === 'inside' ? [s.size, 0] : [s.size / 2, s.size / 2];\n}\n\n/** Effect maps of one layer over its region: see {@link buildEffectMaps}. */\nexport interface EffectMaps {\n /** The region the maps cover (document space). */\n rect: EffectRect;\n /** The enabled effects, Scale Effects applied, in list order. */\n items: LayerEffect[];\n /** Pixel bounds of the shape (alpha above half a step), or `null` when the shape is empty. */\n bounds: EffectRect | null;\n shape: Float32Array;\n /** Per item: the maps it paints through. */\n per: Float32Array[][];\n bevelPaint: (BevelPaint | null)[];\n din: Float32Array | null;\n dout: Float32Array | null;\n doutExact: Float32Array | null;\n /** Frames of gradient strokes by item index. */\n frames: Map<number, EffectRect>;\n /** Approximate size in bytes (cache budgeting). */\n bytes: number;\n}\n\n/** The enabled effects of `effects` with Scale Effects applied (empty when the master switch is off). */\nexport function activeEffectItems(effects: LayerEffects | undefined | null): LayerEffect[] {\n if (!hasActiveEffects(effects)) return [];\n const s = effectsScale(effects);\n return effects.items.filter((e) => e.enabled).map((e) => scaleLayerEffect(e, s));\n}\n\n/**\n * Whether every map pixel of `items` depends only on the shape within the effects' margin, so a\n * tile is unchanged while the layer is unchanged within its margin. Not local: anything laid out\n * over the layer's bounds (aligned gradient overlays, gradient strokes, linked patterns) and noise\n * (it speckles any coverage above zero, which picks up the blur's float noise from far away).\n */\nexport function effectsAreLocal(items: readonly LayerEffect[]): boolean {\n const linked = (p: EffectPatternRef) => p.link !== false;\n return items.every((e) => {\n switch (e.kind) {\n case 'gradient-overlay':\n return !e.gradient.align;\n case 'pattern-overlay':\n return !linked(e);\n case 'stroke':\n return e.fill.type === 'color' || (e.fill.type === 'pattern' && !linked(e.fill));\n case 'drop-shadow':\n case 'inner-shadow':\n case 'outer-glow':\n case 'inner-glow':\n return e.noise <= 0;\n case 'bevel-emboss':\n return !e.texture || !linked(e.texture);\n default:\n return true;\n }\n });\n}\n\n/**\n * Builds every map `items` paints through from the layer's alpha `shape` (0..1) over `rect`.\n * `rect` should cover the layer's shape plus the effects' margin.\n */\nexport function buildEffectMaps(items: LayerEffect[], shape: Float32Array, rect: EffectRect, ctx: EffectContext): EffectMaps {\n const w = rect.width;\n const h = rect.height;\n const s: FMap = { w, h, v: shape };\n // Shape bounds.\n let x0 = Infinity;\n let y0 = Infinity;\n let x1 = -Infinity;\n let y1 = -Infinity;\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n if (shape[y * w + x] > INSIDE_EPS) {\n if (x < x0) x0 = x;\n if (x > x1) x1 = x;\n if (y < y0) y0 = y;\n if (y > y1) y1 = y;\n }\n }\n }\n const bounds = x0 === Infinity ? null : { x: rect.x + x0, y: rect.y + y0, width: x1 - x0 + 1, height: y1 - y0 + 1 };\n const anchor = bounds ? { x: bounds.x, y: bounds.y } : { x: rect.x, y: rect.y };\n const per: Float32Array[][] = [];\n const bevelPaint: (BevelPaint | null)[] = [];\n const frames = new Map<number, EffectRect>();\n let bytes = shape.byteLength;\n items.forEach((e, i) => {\n let maps: FMap[] = [];\n let paint: BevelPaint | null = null;\n switch (e.kind) {\n case 'drop-shadow':\n maps = [shadowMap(s, e, e.spread, ctx.light, false, rect.x, rect.y)];\n break;\n case 'inner-shadow':\n maps = [shadowMap(s, e, e.choke, ctx.light, true, rect.x, rect.y)];\n break;\n case 'outer-glow':\n case 'inner-glow':\n maps = [glowMap(s, e, rect.x, rect.y)];\n break;\n case 'satin':\n maps = [satinMap(s, e)];\n break;\n case 'bevel-emboss': {\n const r = bevelMaps(s, e, rect, anchor, ctx);\n maps = r.maps;\n paint = r.paint;\n break;\n }\n case 'stroke':\n if (e.fill.type === 'gradient' && bounds) {\n const grow = Math.max(0, Math.ceil(Math.min(strokeWidths(e)[1], MAX_EFFECT_REACH)) - 1);\n frames.set(i, { x: bounds.x - grow, y: bounds.y - grow, width: bounds.width + 2 * grow, height: bounds.height + 2 * grow });\n }\n break;\n }\n per.push(maps.map((m) => m.v));\n bevelPaint.push(paint);\n for (const m of maps) bytes += m.v.byteLength;\n });\n const strokes = items.filter((e): e is StrokeEffect => e.kind === 'stroke');\n const outW = strokes.map((st) => strokeWidths(st)[1]).filter((x) => x > 0);\n const din = strokes.length ? distInside(s, true) : null;\n const dout = outW.some((x) => x < STROKE_CHAMFER_MAX) ? distOutside(s, true) : null;\n const doutExact = outW.some((x) => x >= STROKE_CHAMFER_MAX) ? distOutside(s, false) : null;\n for (const d of [din, dout, doutExact]) if (d) bytes += d.byteLength;\n return { rect, items, bounds, shape, per, bevelPaint, din, dout, doutExact, frames, bytes };\n}\n\n// ---------------------------------------------------------------------------\n// Painting\n// ---------------------------------------------------------------------------\n\n/** Straight-alpha float RGBA over a rectangle. */\ntype FBuf = Float32Array;\n\nconst rgbScratchB = [0, 0, 0];\nconst rgbScratchS = [0, 0, 0];\n\n/** Composites straight `src` (alpha `sa`) onto `dst[i..i+3]` with `mode` (the formula `blendTile` uses). */\nfunction compositeAt(dst: FBuf, i: number, mode: BlendMode, separable: boolean, sr: number, sg: number, sb: number, sa: number): void {\n if (!(sa > 0)) return;\n const ab = dst[i + 3];\n const ao = sa + ab * (1 - sa);\n if (ao <= 0) {\n dst[i] = dst[i + 1] = dst[i + 2] = dst[i + 3] = 0;\n return;\n }\n const b0 = dst[i];\n const b1 = dst[i + 1];\n const b2 = dst[i + 2];\n let m0: number;\n let m1: number;\n let m2: number;\n if (mode === 'normal') {\n m0 = sr;\n m1 = sg;\n m2 = sb;\n } else if (separable) {\n m0 = blendChannel(mode, b0, sr);\n m1 = blendChannel(mode, b1, sg);\n m2 = blendChannel(mode, b2, sb);\n } else {\n rgbScratchB[0] = b0;\n rgbScratchB[1] = b1;\n rgbScratchB[2] = b2;\n rgbScratchS[0] = sr;\n rgbScratchS[1] = sg;\n rgbScratchS[2] = sb;\n const m = blendRgb(mode, rgbScratchB, rgbScratchS);\n m0 = m[0];\n m1 = m[1];\n m2 = m[2];\n }\n const wb = (1 - sa) * ab;\n const ws = (1 - ab) * sa;\n const wm = sa * ab;\n dst[i] = (wb * b0 + ws * sr + wm * m0) / ao;\n dst[i + 1] = (wb * b1 + ws * sg + wm * m1) / ao;\n dst[i + 2] = (wb * b2 + ws * sb + wm * m2) / ao;\n dst[i + 3] = ao;\n}\n\n/** Composite `color(i) × coverage` into `dst`. `color` fills `c` with straight RGBA. */\nfunction paint(dst: FBuf, m: ArrayLike<number>, color: (i: number, c: number[]) => void, mode: BlendMode, opacity: number): void {\n const sep = mode !== 'normal' && isSeparableBlendMode(mode);\n const c = [0, 0, 0, 1];\n const n = m.length;\n for (let i = 0; i < n; i++) {\n const a = m[i] * opacity;\n if (!(a > 0)) continue;\n color(i, c);\n compositeAt(dst, i * 4, mode, sep, c[0], c[1], c[2], c[3] * Math.min(a, 1));\n }\n}\n\nfunction rgbOf(hex: string): [number, number, number] {\n return parseEffectColor(hex) ?? [0, 0, 0];\n}\n\nfunction paintColor(dst: FBuf, m: ArrayLike<number>, hex: string, mode: BlendMode, opacity: number): void {\n const [r, g, b] = rgbOf(hex);\n paint(\n dst,\n m,\n (_i, c) => {\n c[0] = r;\n c[1] = g;\n c[2] = b;\n c[3] = 1;\n },\n mode,\n opacity\n );\n}\n\n/** Gradient colour + opacity stops prepared for sampling. */\nclass PreparedGradient {\n private readonly color: Array<[number, number, number, number]>;\n private readonly alpha: Array<[number, number]>;\n constructor(g: EffectGradient) {\n this.color = [...g.stops].sort((a, b) => a.position - b.position).map((s) => [s.position, ...rgbOf(s.color)]);\n this.alpha = [...(g.opacityStops ?? [])].sort((a, b) => a.position - b.position).map((s) => [s.position, s.opacity]);\n }\n sample(t: number, out: number[]): void {\n const c = this.color;\n if (!c.length) {\n out[0] = out[1] = out[2] = t;\n } else if (c.length === 1 || t <= c[0][0]) {\n out[0] = c[0][1];\n out[1] = c[0][2];\n out[2] = c[0][3];\n } else {\n let done = false;\n for (let k = 0; k < c.length - 1; k++) {\n const a = c[k];\n const b = c[k + 1];\n if (t <= b[0]) {\n const f = b[0] > a[0] ? (t - a[0]) / (b[0] - a[0]) : 0;\n out[0] = a[1] + (b[1] - a[1]) * f;\n out[1] = a[2] + (b[2] - a[2]) * f;\n out[2] = a[3] + (b[3] - a[3]) * f;\n done = true;\n break;\n }\n }\n if (!done) {\n const l = c[c.length - 1];\n out[0] = l[1];\n out[1] = l[2];\n out[2] = l[3];\n }\n }\n const a = this.alpha;\n if (!a.length) out[3] = 1;\n else if (a.length === 1 || t <= a[0][0]) out[3] = a[0][1];\n else {\n out[3] = a[a.length - 1][1];\n for (let k = 0; k < a.length - 1; k++) {\n if (t <= a[k + 1][0]) {\n const f = a[k + 1][0] > a[k][0] ? (t - a[k][0]) / (a[k + 1][0] - a[k][0]) : 0;\n out[3] = a[k][1] + (a[k + 1][1] - a[k][1]) * f;\n break;\n }\n }\n }\n }\n}\n\n/** Lengths a 100 % gradient at `angle` spans in a `w × h` frame: the chord and the ellipse-norm length. */\nfunction gradientUnits(angle: number, w: number, h: number): [number, number] {\n const a = (angle * Math.PI) / 180;\n const s = Math.sin(a);\n const c = Math.cos(a);\n const len = Math.max(Math.sqrt((c * w) ** 2 + (s * h) ** 2), 1);\n const chord = Math.max(Math.min(w / Math.max(Math.abs(c), 1e-6), h / Math.max(Math.abs(s), 1e-6)), 1);\n return [chord, len];\n}\n\n/** Gradient parameter `t` (0..1) at pixel centre `(x, y)` of a gradient laid out in `frame`. */\nexport function gradientT(style: GradientStyle, angle: number, scale: number, reverse: boolean, offset: { x: number; y: number }, frame: EffectRect, x: number, y: number): number {\n const w = Math.max(frame.width, 1);\n const h = Math.max(frame.height, 1);\n const cx = frame.x + w / 2 + offset.x * w;\n const cy = frame.y + h / 2 + offset.y * h;\n const a = (angle * Math.PI) / 180;\n const s = Math.sin(a);\n const c = Math.cos(a);\n const dx = x - cx;\n const dy = y - cy;\n const along = dx * c - dy * s;\n const across = dx * s + dy * c;\n let [chord, len] = gradientUnits(angle, w, h);\n chord *= Math.max(scale, 1e-3);\n len *= Math.max(scale, 1e-3);\n const corner = 0.5 * (c - s);\n let t: number;\n switch (style) {\n case 'linear':\n t = (along - corner) / chord + 0.5;\n break;\n case 'reflected':\n t = Math.abs((along - corner) / (chord / 2));\n break;\n case 'radial':\n t = Math.sqrt(dx * dx + dy * dy) / (len / 2);\n break;\n case 'diamond':\n t = (Math.abs(along) + Math.abs(across)) / (len / 2);\n break;\n case 'angle': {\n const turn = (a - Math.atan2(-dy, dx)) / (2 * Math.PI);\n t = turn - Math.floor(turn);\n break;\n }\n }\n t = clamp01(t);\n return reverse ? 1 - t : t;\n}\n\n/** Snaps a linear / reflected gradient's end points to the pixel grid, as Photoshop lays effect gradients out. */\nfunction gradientLayout(style: GradientStyle, angle: number, scale: number, offset: { x: number; y: number }, frame: EffectRect): [number, number, { x: number; y: number }] {\n const unchanged: [number, number, { x: number; y: number }] = [angle, scale, offset];\n if ((style !== 'linear' && style !== 'reflected') || ![angle, scale, offset.x, offset.y].every(Number.isFinite)) return unchanged;\n const w = Math.max(frame.width, 1);\n const h = Math.max(frame.height, 1);\n const cx = frame.x + w / 2 + offset.x * w;\n const cy = frame.y + h / 2 + offset.y * h;\n const chordOf = (r: number) => Math.max(Math.min(w / Math.max(Math.abs(Math.cos(r)), 1e-6), h / Math.max(Math.abs(Math.sin(r)), 1e-6)), 1);\n const a = (angle * Math.PI) / 180;\n const half = (chordOf(a) * Math.max(scale, 1e-3)) / 2;\n const dx = Math.cos(a) * half;\n const dy = -Math.sin(a) * half;\n const snap = (v: number) => Math.floor(v + 1e-4);\n const end = [snap(cx + dx), snap(cy + dy)];\n let start: number[];\n let mid: number[];\n if (style === 'reflected') {\n start = [cx, cy];\n mid = [cx, cy];\n } else {\n start = [snap(cx - dx), snap(cy - dy)];\n mid = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2];\n }\n const v = [end[0] - start[0], end[1] - start[1]];\n const len = Math.hypot(v[0], v[1]);\n if (len < 0.5) return unchanged;\n const a2 = Math.atan2(-v[1], v[0]);\n const span = style === 'reflected' ? 2 * len : len;\n return [(a2 * 180) / Math.PI, span / chordOf(a2), { x: offset.x + (mid[0] - cx) / w, y: offset.y + (mid[1] - cy) / h }];\n}\n\ninterface PaintEnv {\n rect: EffectRect;\n /** Frame gradients align with (the layer's bounds) and the canvas (unaligned gradients). */\n bounds: EffectRect;\n canvas: EffectRect;\n anchor: { x: number; y: number };\n ctx: EffectContext;\n}\n\nfunction paintGradient(dst: FBuf, m: ArrayLike<number>, g: EffectGradient, frame: EffectRect, env: PaintEnv, mode: BlendMode, opacity: number): void {\n const prepared = new PreparedGradient(g);\n const [angle, scale, offset] = gradientLayout(g.style, g.angle, g.scale, g.offset, frame);\n const { x: rx, y: ry, width: w } = env.rect;\n paint(dst, m, (i, c) => prepared.sample(gradientT(g.style, angle, scale, g.reverse, offset, frame, rx + (i % w) + 0.5, ry + Math.floor(i / w) + 0.5), c), mode, opacity);\n}\n\nfunction paintPattern(dst: FBuf, m: ArrayLike<number>, p: EffectPatternRef, env: PaintEnv, mode: BlendMode, opacity: number): void {\n const tile = env.ctx.pattern(p.patternId);\n if (!tile) return;\n const place = new PatternPlacement(env.anchor, p.link ?? true, p.phase ?? { x: 0, y: 0 }, p.scale, p.angle ?? 0);\n const { x: rx, y: ry, width: w } = env.rect;\n const uv = [0, 0];\n paint(\n dst,\n m,\n (i, c) => {\n place.map(rx + (i % w) + 0.5, ry + Math.floor(i / w) + 0.5, uv);\n tile.sample(uv[0], uv[1], c);\n },\n mode,\n opacity\n );\n}\n\nfunction paintFill(dst: FBuf, m: ArrayLike<number>, f: EffectFill, frame: EffectRect, env: PaintEnv, mode: BlendMode, opacity: number): void {\n if (f.type === 'color') paintColor(dst, m, f.color, mode, opacity);\n else if (f.type === 'gradient') paintGradient(dst, m, f.gradient, f.gradient.align ? frame : env.canvas, env, mode, opacity);\n else paintPattern(dst, m, f, env, mode, opacity);\n}\n\n/** A glow's paint: a gradient runs along the glow (colour at `1 − v`), opaque from strength `range²`. */\nfunction paintGlow(dst: FBuf, m: ArrayLike<number>, g: OuterGlowEffect | InnerGlowEffect, env: PaintEnv): void {\n if (g.fill.type === 'color') return paintColor(dst, m, g.fill.color, g.blend, g.opacity);\n const prepared = new PreparedGradient(g.fill.gradient);\n const r = Number.isFinite(g.range) ? clamp(g.range, 0.01, 1) : 1;\n const gain = 1 / (r * r);\n const strength = new Float32Array(m.length);\n for (let i = 0; i < m.length; i++) strength[i] = Math.min(m[i] * gain, 1);\n paint(dst, strength, (i, c) => prepared.sample(1 - clamp01(m[i]), c), g.blend, g.opacity);\n}\n\n/** Crops `v` (over `from`) to `to`, filling outside with `fill`. */\nfunction crop(v: Float32Array, from: EffectRect, to: EffectRect, fill: number): Float32Array {\n const out = new Float32Array(to.width * to.height);\n if (fill) out.fill(fill);\n const ix0 = Math.max(from.x, to.x);\n const iy0 = Math.max(from.y, to.y);\n const ix1 = Math.min(from.x + from.width, to.x + to.width);\n const iy1 = Math.min(from.y + from.height, to.y + to.height);\n if (ix1 <= ix0 || iy1 <= iy0) return out;\n const n = ix1 - ix0;\n for (let y = iy0; y < iy1; y++) {\n const s = (y - from.y) * from.width + (ix0 - from.x);\n out.set(v.subarray(s, s + n), (y - to.y) * to.width + (ix0 - to.x));\n }\n return out;\n}\n\nexport interface EffectCompositeInput {\n /** The accumulator (straight RGBA over `rect`); receives the layer and its effects. */\n backdrop: Uint8ClampedArray;\n /** The layer's pixels over `rect` (straight RGBA, mask applied to alpha, clipped layers applied). */\n content: Uint8ClampedArray;\n rect: EffectRect;\n maps: EffectMaps;\n blend: BlendMode;\n /** Layer opacity: the whole stack. */\n opacity: number;\n /** Fill opacity: the layer's own pixels only. */\n fillOpacity: number;\n /** The canvas (unaligned gradients span it). */\n canvas: EffectRect;\n ctx: EffectContext;\n}\n\n/** Composites a layer's content and its effects onto `backdrop` in place (see the module comment for the order). */\nexport function compositeLayerEffects(input: EffectCompositeInput): void {\n const { backdrop, content, rect, maps, ctx } = input;\n const items = maps.items;\n const n = rect.width * rect.height;\n const shape = new Float32Array(n);\n for (let i = 0; i < n; i++) shape[i] = content[i * 4 + 3] / 255;\n const before = new Float32Array(n * 4);\n for (let i = 0; i < n * 4; i++) before[i] = backdrop[i] / 255;\n const work = new Float32Array(before);\n const fx = (i: number, k: number) => crop(maps.per[i][k], maps.rect, rect, 0);\n const bounds = maps.bounds ?? rect;\n const env: PaintEnv = { rect, bounds, canvas: input.canvas, anchor: { x: bounds.x, y: bounds.y }, ctx };\n const fill = clamp01(input.fillOpacity);\n const inside = (a: number) => a > INSIDE_EPS;\n // The first listed instance of a kind is on top: paint in reverse.\n const rev: number[] = [];\n for (let i = items.length - 1; i >= 0; i--) rev.push(i);\n\n const knockOut = (m: Float32Array, fillOpacity: number) => {\n const seeThrough = 1 - clamp01(fillOpacity);\n for (let i = 0; i < n; i++) m[i] *= 1 - shape[i] * seeThrough;\n };\n const normalLike = input.blend === 'normal';\n for (const i of rev) {\n const e = items[i];\n if (e.kind !== 'drop-shadow') continue;\n const m = fx(i, 0);\n // The layer hides the shadow beneath its see-through fill, and wholly in a mode that blends with it.\n if (e.knocksOut) knockOut(m, normalLike ? fill : 0);\n paintColor(work, m, e.color, e.blend, e.opacity);\n }\n for (const i of rev) {\n const e = items[i];\n if (e.kind !== 'outer-glow') continue;\n const m = fx(i, 0);\n knockOut(m, fill);\n paintGlow(work, m, e, env);\n }\n\n // The layer: content at fill opacity, then the interior effects, relative to the shape.\n const lay = new Float32Array(n * 4);\n for (let i = 0; i < n; i++) {\n lay[i * 4] = content[i * 4] / 255;\n lay[i * 4 + 1] = content[i * 4 + 1] / 255;\n lay[i * 4 + 2] = content[i * 4 + 2] / 255;\n lay[i * 4 + 3] = inside(shape[i]) ? fill : 0;\n }\n const premerge = !normalLike;\n if (premerge) {\n const sep = isSeparableBlendMode(input.blend);\n for (let i = 0; i < n; i++) {\n const p = i * 4;\n const sr = lay[p];\n const sg = lay[p + 1];\n const sb = lay[p + 2];\n const sa = lay[p + 3];\n lay[p] = work[p];\n lay[p + 1] = work[p + 1];\n lay[p + 2] = work[p + 2];\n lay[p + 3] = work[p + 3];\n compositeAt(lay, p, input.blend, sep, sr, sg, sb, sa);\n }\n }\n const rel = (m: Float32Array) => {\n for (let i = 0; i < n; i++) m[i] = inside(shape[i]) ? Math.min(m[i] / shape[i], 1) : 0;\n return m;\n };\n const full = new Float32Array(n);\n for (let i = 0; i < n; i++) full[i] = inside(shape[i]) ? 1 : 0;\n for (const i of rev) {\n const e = items[i];\n if (e.kind === 'pattern-overlay') paintPattern(lay, full, e, env, e.blend, e.opacity);\n }\n for (const i of rev) {\n const e = items[i];\n if (e.kind === 'gradient-overlay') paintGradient(lay, full, e.gradient, e.gradient.align ? bounds : input.canvas, env, e.blend, e.opacity);\n }\n for (const i of rev) {\n const e = items[i];\n if (e.kind === 'color-overlay') paintColor(lay, full, e.color, e.blend, e.opacity);\n }\n for (const i of rev) {\n const e = items[i];\n if (e.kind === 'satin') paintColor(lay, rel(fx(i, 0)), e.color, e.blend, e.opacity);\n }\n for (const i of rev) {\n const e = items[i];\n if (e.kind === 'inner-glow') paintGlow(lay, rel(fx(i, 0)), e, env);\n }\n for (const i of rev) {\n const e = items[i];\n if (e.kind === 'inner-shadow') paintColor(lay, rel(fx(i, 0)), e.color, e.blend, e.opacity);\n }\n // Strokes: inside parts over the layer (bottom instance first).\n const strokeIdx = items.map((e, i) => (e.kind === 'stroke' ? i : -1)).filter((i) => i >= 0);\n const din = strokeIdx.length && maps.din ? crop(maps.din, maps.rect, rect, 0) : null;\n const frameOf = (i: number) => maps.frames.get(i) ?? bounds;\n for (let k = strokeIdx.length - 1; k >= 0; k--) {\n const st = items[strokeIdx[k]] as StrokeEffect;\n const [inW] = strokeWidths(st);\n if (!(inW > 0) || !din) continue;\n const m = new Float32Array(n);\n for (let i = 0; i < n; i++) m[i] = inside(shape[i]) ? clamp01(inW + 0.5 - din[i]) : 0;\n paintFill(lay, m, st.fill, frameOf(strokeIdx[k]), env, st.blend, st.opacity);\n }\n for (const i of rev) {\n const e = items[i];\n if (e.kind !== 'bevel-emboss' || maps.bevelPaint[i] !== 'inner') continue;\n paintColor(lay, rel(fx(i, 0)), e.highlightColor, e.highlightBlend, e.highlightOpacity);\n paintColor(lay, rel(fx(i, 1)), e.shadowColor, e.shadowBlend, e.shadowOpacity);\n }\n // The shape's own alpha.\n for (let i = 0; i < n; i++) lay[i * 4 + 3] *= Math.min(shape[i], 1);\n\n // Outside parts of strokes lie beneath the layer; a higher stroke covers the ones below it.\n const outside = strokeIdx.filter((i) => strokeWidths(items[i] as StrokeEffect)[1] > 0);\n if (outside.length) {\n const dout = maps.dout ? crop(maps.dout, maps.rect, rect, FAR) : null;\n const doutExact = maps.doutExact ? crop(maps.doutExact, maps.rect, rect, FAR) : null;\n const base = new Float32Array(work);\n const acc = new Float32Array(n * 4);\n const cover = new Float32Array(n);\n for (const si of outside) {\n const st = items[si] as StrokeEffect;\n const outW = strokeWidths(st)[1];\n const d = (outW >= STROKE_CHAMFER_MAX ? doutExact : dout) ?? new Float32Array(n).fill(FAR);\n const share = new Float32Array(n);\n const ones = new Float32Array(n);\n for (let i = 0; i < n; i++) {\n const band = clamp01(outW + 0.5 - d[i]);\n let k: number;\n if (inside(shape[i])) {\n // Beneath the layer the stroke shows only through the part of the pixel the shape leaves.\n const c = clamp01(lay[i * 4 + 3]);\n k = c >= 1 ? 1 : clamp01((1 - clamp01(shape[i])) / (1 - c));\n } else k = band;\n share[i] = k * st.opacity * (1 - cover[i]);\n cover[i] += share[i];\n ones[i] = share[i] > 0 ? 1 : 0;\n }\n const blended = new Float32Array(base);\n paintFill(blended, ones, st.fill, frameOf(si), env, st.blend, 1);\n for (let i = 0; i < n; i++) {\n const s = share[i];\n if (!(s > 0)) continue;\n const p = i * 4;\n const ba = blended[p + 3];\n acc[p] += s * blended[p] * ba;\n acc[p + 1] += s * blended[p + 1] * ba;\n acc[p + 2] += s * blended[p + 2] * ba;\n acc[p + 3] += s * ba;\n }\n }\n for (let i = 0; i < n; i++) {\n const c = cover[i];\n if (c <= 0) continue;\n const p = i * 4;\n const k = 1 - Math.min(c, 1);\n const a = work[p + 3] * k + acc[p + 3];\n if (a > 0) {\n work[p] = (work[p] * work[p + 3] * k + acc[p]) / a;\n work[p + 1] = (work[p + 1] * work[p + 3] * k + acc[p + 1]) / a;\n work[p + 2] = (work[p + 2] * work[p + 3] * k + acc[p + 2]) / a;\n work[p + 3] = Math.min(a, 1);\n }\n }\n }\n for (const i of rev) {\n const e = items[i];\n if (e.kind !== 'bevel-emboss' || maps.bevelPaint[i] !== 'outer') continue;\n paintColor(work, fx(i, 0), e.highlightColor, e.highlightBlend, e.highlightOpacity);\n paintColor(work, fx(i, 1), e.shadowColor, e.shadowBlend, e.shadowOpacity);\n }\n // The layer (with its interior effects) onto the exterior result.\n const mode: BlendMode = premerge ? 'normal' : input.blend;\n const sep = isSeparableBlendMode(mode);\n for (let i = 0; i < n; i++) {\n const p = i * 4;\n if (lay[p + 3] > 0) compositeAt(work, p, mode, sep, lay[p], lay[p + 1], lay[p + 2], lay[p + 3]);\n }\n // Emboss styles shade the composited result.\n for (const i of rev) {\n const e = items[i];\n if (e.kind !== 'bevel-emboss' || maps.bevelPaint[i] !== 'both') continue;\n paintColor(work, fx(i, 0), e.highlightColor, e.highlightBlend, e.highlightOpacity);\n paintColor(work, fx(i, 1), e.shadowColor, e.shadowBlend, e.shadowOpacity);\n }\n // Layer opacity applies to the whole stack (premultiplied mix with what was there).\n const op = clamp01(input.opacity);\n for (let i = 0; i < n; i++) {\n const p = i * 4;\n const aa = before[p + 3];\n const ba = work[p + 3];\n const alpha = aa + (ba - aa) * op;\n if (alpha <= 0) {\n backdrop[p] = backdrop[p + 1] = backdrop[p + 2] = backdrop[p + 3] = 0;\n continue;\n }\n for (let c = 0; c < 3; c++) backdrop[p + c] = Math.round(((before[p + c] * aa + (work[p + c] * ba - before[p + c] * aa) * op) / alpha) * 255);\n backdrop[p + 3] = Math.round(alpha * 255);\n }\n}\n\n/**\n * Renders one layer with its effects over a whole region in one pass (no tiles): the reference\n * the per-tile compositor is checked against, and a convenience for one-off renders.\n */\nexport function renderLayerEffects(\n effects: LayerEffects,\n layer: { content: Uint8ClampedArray; rect: EffectRect; blend: BlendMode; opacity: number; fillOpacity?: number },\n backdrop: Uint8ClampedArray,\n canvas: EffectRect,\n ctx: EffectContext\n): void {\n const items = activeEffectItems(effects);\n const n = layer.rect.width * layer.rect.height;\n const shape = new Float32Array(n);\n for (let i = 0; i < n; i++) shape[i] = layer.content[i * 4 + 3] / 255;\n const maps = buildEffectMaps(items, shape, layer.rect, ctx);\n compositeLayerEffects({\n backdrop,\n content: layer.content,\n rect: layer.rect,\n maps,\n blend: layer.blend,\n opacity: layer.opacity,\n fillOpacity: layer.fillOpacity ?? 1,\n canvas,\n ctx,\n });\n}\n","/**\n * The per-tile layer compositor engine. Composites a layer stack (pixel layers and groups with\n * masks, clipping, blend modes, opacity, fill and pass-through) tile by tile in document space,\n * in pure TypeScript, so results are deterministic and testable in Node and Workers.\n *\n * Editors add their own layer kinds through a {@link CompositorProfile}: layers that filter\n * what is below them (adjustment layers), content layers drawn by the editor (annotations),\n * per-layer pixel filters, and parametric mask sources.\n *\n * Caches:\n * - Each top-level *step* (a content layer and the layers clipped to it, a run of filter layers,\n * a group) has a per-tile *signature*: its properties plus the identity of every surface tile\n * it reads. A tile whose signatures are unchanged returns the cached result (same buffer\n * object, so callers can skip uploading it).\n * - A prefix cache keeps the accumulator *before* the step that changed last time (and, when the\n * caller names it, before the active layer). Dragging a slider on layer k, or a stroke on layer\n * k, recomposites from step k upwards only; `dirty` rectangles limit that to where it changed.\n * - Spatial filters and feathered or parametric masks are computed once over the canvas and\n * cached by the identity of their inputs.\n * - Layer effects: a layer's effect maps (shadow, glow, bevel, stroke distance fields) are built\n * once over the layer's region (its bounds grown by the effects' margin, within the canvas grown\n * the same) and cached by the identity of every surface tile the shape reads; tiles crop them.\n * A tile's signature then reads the layer within the margin around it (or the whole region when\n * an effect is laid out over the layer's bounds), and `dirty` rectangles grow by the margin.\n */\n\nimport { blendChannel, blendRgb, isSeparableBlendMode, mixPigment8, type BlendMode } from './blend';\nimport { gaussianBlur } from './blur';\nimport { DEFAULT_GLOBAL_LIGHT, effectsFingerprint, effectsMargin, hasActiveEffects, type GlobalLight, type LayerEffects } from './effects/model';\nimport { builtinPattern, PatternTile, type EffectPattern } from './effects/patterns';\nimport { activeEffectItems, buildEffectMaps, compositeLayerEffects, effectsAreLocal, type EffectContext, type EffectMaps } from './effects/render';\nimport type { RasterMask, RasterNode } from './layers';\nimport {\n TILE,\n emptySurface,\n readRegion,\n surfaceFromPixels,\n tileKey,\n tilesInRect,\n type PixelSurface,\n type RasterRect,\n type TileKey,\n type TiledSurface,\n} from './surface';\n\nconst TILE_PIXELS = TILE * TILE;\nconst TILE_BYTES = TILE_PIXELS * 4;\n\n/** What the compositor needs from its host besides the layers. */\nexport interface RasterSources {\n /** Canvas (document) size: spatial filters and mask feathering clamp at this rect. */\n width: number;\n height: number;\n /** The pixels of a pixel layer (lets a host resolve layers whose pixels live elsewhere). */\n surfaceOf?(layer: CompositorLayer): PixelSurface;\n /** Changes whenever one of the host's callbacks would return something new for the same layers. */\n version?: number | string;\n /** The document's global light for effects that use it (default angle 120°, altitude 30°). */\n globalLight?: GlobalLight;\n /** A document pattern by id for pattern effects (built-in patterns are found without it). */\n pattern?(id: string): EffectPattern | null;\n}\n\n/** The fields the engine reads; editor layer types are wider. */\nexport interface CompositorLayer extends RasterNode {\n visible: boolean;\n opacity: number;\n blend: string;\n clipped?: boolean;\n mask?: RasterMask;\n /** Pixel layers. */\n surface?: PixelSurface;\n offset?: { x: number; y: number };\n fillOpacity?: number;\n /** Layer effects. */\n effects?: LayerEffects;\n /** Groups. */\n children?: readonly CompositorLayer[];\n}\n\n/** A pixel layer's filters as the engine runs them. */\nexport interface PixelFilterSpec {\n /** Identifies the settings (part of the cache key). */\n key: string;\n /** Needs neighbouring pixels: runs once over the canvas instead of per tile. */\n spatial: boolean;\n /** Filters RGBA `data` (`width × height`) in place. */\n apply(data: Uint8ClampedArray, width: number, height: number): void;\n}\n\n/** How an editor's own layer kinds composite. Every hook is optional. */\nexport interface CompositorProfile<L extends CompositorLayer, S extends RasterSources> {\n /** Layers that change what is below them; adjacent ones run as one step. */\n isFilterLayer?(layer: L): boolean;\n /** Applies a run of visible filter layers to `acc` (RGBA over `r`); `coverage` holds masked ones by id. */\n applyFilterRun?(layers: L[], acc: Uint8ClampedArray, r: RasterRect, coverage: Map<string, Float32Array> | undefined, sources: S): void;\n /** RGBA of a content layer that is neither a pixel layer nor a group, over `r`, or `null`. */\n contentTile?(layer: L, r: RasterRect, sources: S): Uint8ClampedArray | null;\n /** The blend mode a content layer draws with (default `layer.blend`). */\n contentBlend?(layer: L): BlendMode;\n /** Properties of a layer beyond the generic ones that change its pixels (part of its signature). */\n layerKey?(layer: L): unknown;\n /** Active filters of a pixel layer, or `null`. */\n pixelFilters?(layer: L): PixelFilterSpec | null;\n /** Canvas coverage (0..1, `width × height`) of a parametric mask source, or `null` to reveal all. */\n maskSourcePlane?(source: unknown, width: number, height: number, sources: S): Float32Array | null;\n}\n\nexport interface CompositorOptions {\n /** Upper bound for cached tile buffers (default 256 MiB). */\n maxCacheBytes?: number;\n}\n\nexport interface CompositeCallOptions {\n /**\n * The layer about to be edited (the active layer): every tile composited in this call keeps a\n * checkpoint below its step, so strokes and slider drags on it start there.\n */\n checkpointLayerId?: string | null;\n /**\n * Where the layers changed since the previous call, when the caller knows (a stroke's dabs).\n * Changed tiles are then recomposited only inside these rectangles. Changes outside them would\n * be missed, so pass nothing when unsure.\n */\n dirty?: RasterRect[];\n}\n\n// ---------------------------------------------------------------------------\n// Steps and signatures\n// ---------------------------------------------------------------------------\n\ntype Step<L> =\n | { kind: 'content'; layer: L; clipped: L[] }\n | { kind: 'adjust'; layers: L[] }\n | { kind: 'group'; layer: L; clipped: L[]; steps: Step<L>[] };\n\ninterface Sig {\n key: string;\n refs: unknown[];\n}\n\nfunction sameSig(a: Sig | undefined, b: Sig): boolean {\n if (!a || a.key !== b.key || a.refs.length !== b.refs.length) return false;\n for (let i = 0; i < a.refs.length; i++) if (a.refs[i] !== b.refs[i]) return false;\n return true;\n}\n\n/** The bounding box of the parts of `rects` inside tile (tx, ty), or `null` when none is. */\nfunction dirtyInTile(rects: RasterRect[], tx: number, ty: number): RasterRect | null {\n let x0 = Infinity;\n let y0 = Infinity;\n let x1 = -Infinity;\n let y1 = -Infinity;\n const ox = tx * TILE;\n const oy = ty * TILE;\n for (const r of rects) {\n const a = Math.max(ox, Math.floor(r.x));\n const b = Math.max(oy, Math.floor(r.y));\n const c = Math.min(ox + TILE, Math.ceil(r.x + r.width));\n const d = Math.min(oy + TILE, Math.ceil(r.y + r.height));\n if (c <= a || d <= b) continue;\n x0 = Math.min(x0, a);\n y0 = Math.min(y0, b);\n x1 = Math.max(x1, c);\n y1 = Math.max(y1, d);\n }\n return x0 === Infinity ? null : { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };\n}\n\n// ---------------------------------------------------------------------------\n// Blending\n// ---------------------------------------------------------------------------\n\n/**\n * Blends straight-alpha `src` over `dst` (both RGBA) in place with `mode`, scaling the source\n * alpha by `alpha` and, per pixel, by `coverage`. Same maths as `blendPixel` (including the\n * `pigment` paint mix, which caches each 8-bit colour's spectrum).\n */\nexport function blendTile(\n dst: Uint8ClampedArray,\n src: ArrayLike<number>,\n mode: BlendMode,\n alpha: number,\n coverage: ArrayLike<number> | null\n): void {\n if (!(alpha > 0)) return;\n const normal = mode === 'normal';\n const pigment = mode === 'pigment';\n const lut = !normal && isSeparableBlendMode(mode) ? separableLut(mode) : null;\n const cb = [0, 0, 0];\n const cs = [0, 0, 0];\n const n = dst.length;\n for (let i = 0, p = 0; i < n; i += 4, p++) {\n const sa = src[i + 3];\n if (sa === 0) continue;\n const k = coverage ? alpha * coverage[p] : alpha;\n if (!(k > 0)) continue;\n if (normal && sa === 255 && k >= 1) {\n // Opaque normal source: the result is the source (what the general formula rounds to).\n dst[i] = src[i];\n dst[i + 1] = src[i + 1];\n dst[i + 2] = src[i + 2];\n dst[i + 3] = 255;\n continue;\n }\n const as = (sa / 255) * k;\n const ab = dst[i + 3] / 255;\n const ao = as + ab * (1 - as);\n if (ao <= 0) {\n dst[i] = dst[i + 1] = dst[i + 2] = dst[i + 3] = 0;\n continue;\n }\n const d0 = dst[i];\n const d1 = dst[i + 1];\n const d2 = dst[i + 2];\n if (pigment) {\n // Not B(Cb, Cs): the colours mix like paint by amount, `as / ao` of the source.\n mixPigment8(d0, d1, d2, src[i], src[i + 1], src[i + 2], as / ao, cb);\n dst[i] = Math.round(cb[0] * 255);\n dst[i + 1] = Math.round(cb[1] * 255);\n dst[i + 2] = Math.round(cb[2] * 255);\n dst[i + 3] = Math.round(ao * 255);\n continue;\n }\n const b0 = d0 / 255;\n const b1 = d1 / 255;\n const b2 = d2 / 255;\n const s0 = src[i] / 255;\n const s1 = src[i + 1] / 255;\n const s2 = src[i + 2] / 255;\n let m0: number;\n let m1: number;\n let m2: number;\n if (normal) {\n m0 = s0;\n m1 = s1;\n m2 = s2;\n } else if (lut) {\n m0 = lut[(d0 << 8) | src[i]];\n m1 = lut[(d1 << 8) | src[i + 1]];\n m2 = lut[(d2 << 8) | src[i + 2]];\n } else {\n cb[0] = b0;\n cb[1] = b1;\n cb[2] = b2;\n cs[0] = s0;\n cs[1] = s1;\n cs[2] = s2;\n const m = blendRgb(mode, cb, cs);\n m0 = m[0];\n m1 = m[1];\n m2 = m[2];\n }\n const wb = (1 - as) * ab;\n const ws = (1 - ab) * as;\n const wm = as * ab;\n dst[i] = Math.round(((wb * b0 + ws * s0 + wm * m0) / ao) * 255);\n dst[i + 1] = Math.round(((wb * b1 + ws * s1 + wm * m1) / ao) * 255);\n dst[i + 2] = Math.round(((wb * b2 + ws * s2 + wm * m2) / ao) * 255);\n dst[i + 3] = Math.round(ao * 255);\n }\n}\n\nconst separableLuts = new Map<BlendMode, Float64Array>();\n\n/** `B(cb, cs)` of a separable mode for every pair of 8-bit values (index `cb << 8 | cs`). */\nfunction separableLut(mode: BlendMode): Float64Array {\n let lut = separableLuts.get(mode);\n if (!lut) {\n lut = new Float64Array(65536);\n for (let b = 0; b < 256; b++) for (let c = 0; c < 256; c++) lut[(b << 8) | c] = blendChannel(mode, b / 255, c / 255);\n separableLuts.set(mode, lut);\n }\n return lut;\n}\n\n/** Composites `src` onto `dst` but keeps `dst`'s alpha (clipping: source-atop). */\nfunction blendAtop(dst: Uint8ClampedArray, src: ArrayLike<number>, mode: BlendMode, alpha: number, coverage: ArrayLike<number> | null): void {\n const keep = new Uint8ClampedArray(dst.length >> 2);\n for (let i = 3, p = 0; i < dst.length; i += 4, p++) keep[p] = dst[i];\n blendTile(dst, src, mode, alpha, coverage);\n for (let i = 3, p = 0; i < dst.length; i += 4, p++) dst[i] = keep[p];\n}\n\n/** `dst = lerp(before, dst, alpha · coverage)` per channel (pass-through groups with opacity). */\nfunction lerpTile(dst: Uint8ClampedArray, before: Uint8ClampedArray, alpha: number, coverage: ArrayLike<number> | null): void {\n for (let i = 0, p = 0; i < dst.length; i += 4, p++) {\n const k = coverage ? alpha * coverage[p] : alpha;\n if (k >= 1) continue;\n for (let c = 0; c < 4; c++) dst[i + c] = Math.round(before[i + c] + (dst[i + c] - before[i + c]) * k);\n }\n}\n\n// ---------------------------------------------------------------------------\n// The compositor\n// ---------------------------------------------------------------------------\n\ninterface TileEntry<L> {\n sigs: Sig[];\n /** Accumulator before step `index` (index 0 is the empty accumulator and is never stored). */\n checkpoints: Map<number, { data: Uint8ClampedArray; empty: boolean }>;\n result: Uint8ClampedArray;\n /** The root list the entry was computed for (sigs are only comparable per root). */\n root: readonly L[];\n stamp: number;\n}\n\ninterface CoveragePlane {\n /** Coverage over the canvas (`width × height`). */\n data: Float32Array;\n width: number;\n height: number;\n}\n\nconst ZERO_TILE = new Uint8ClampedArray(TILE_BYTES);\n\n/** Statistics of the last `composite` call (for benchmarks and tests). */\nexport interface CompositeStats {\n tiles: number;\n /** Tiles returned straight from the cache. */\n cached: number;\n /** Steps actually run, summed over tiles. */\n stepsRun: number;\n}\n\nconst isGroup = (l: CompositorLayer) => l.type === 'group';\nconst isPixel = (l: CompositorLayer) => l.type === 'pixel';\n\nexport class RasterCompositor<L extends CompositorLayer = CompositorLayer, S extends RasterSources = RasterSources> {\n private readonly tiles = new Map<TileKey, TileEntry<L>>();\n private readonly maxCacheBytes: number;\n private cacheBytes = 0;\n private stamp = 0;\n private sources: S = { width: 1, height: 1 } as S;\n private sourcesVersion: unknown = undefined;\n private filteredCache = new WeakMap<object, Map<string, PixelSurface>>();\n private planeCache = new Map<string, CoveragePlane>();\n private planeIds = new WeakMap<object, number>();\n private nextPlaneId = 1;\n private contentCache = new WeakMap<object, Map<string, Uint8ClampedArray>>();\n private readonly stepsCache = new WeakMap<readonly L[], Step<L>[]>();\n private readonly propsKeyCache = new WeakMap<object, string>();\n /** Effect maps by layer id (see the module comment), least recently used first. */\n private effectCache = new Map<string, { key: string; refs: unknown[]; maps: EffectMaps }>();\n /** Effect maps already validated in this call, by layer object. */\n private effectMemo = new WeakMap<object, { stamp: number; maps: EffectMaps }>();\n private readonly patternTiles = new WeakMap<EffectPattern, PatternTile>();\n private readonly reachCache = new WeakMap<readonly L[], number>();\n private fxContext: EffectContext | null = null;\n lastStats: CompositeStats = { tiles: 0, cached: 0, stepsRun: 0 };\n private hint = -1;\n\n constructor(\n private readonly profile: CompositorProfile<L, S> = {},\n options: CompositorOptions = {}\n ) {\n this.maxCacheBytes = options.maxCacheBytes ?? 256 * 1024 * 1024;\n }\n\n /** Drops every cached tile and plane. */\n clear(): void {\n this.tiles.clear();\n this.cacheBytes = 0;\n this.planeCache.clear();\n this.filteredCache = new WeakMap();\n this.contentCache = new WeakMap();\n this.effectCache.clear();\n this.effectMemo = new WeakMap();\n }\n\n private setSources(sources: S): void {\n const version = `${sources.width}x${sources.height}:${sources.version ?? ''}`;\n if (version !== this.sourcesVersion) {\n this.clear();\n this.sourcesVersion = version;\n }\n this.sources = sources;\n this.fxContext = null;\n }\n\n /** The composite of `layers` over `rect` as `rect.width × rect.height` RGBA (straight alpha). */\n composite(layers: readonly L[], rect: RasterRect, sources: S, options: CompositeCallOptions = {}): Uint8ClampedArray {\n const out = new Uint8ClampedArray(Math.max(0, rect.width * rect.height * 4));\n this.compositeTiles(layers, rect, sources, (tx, ty, data) => {\n const ox = tx * TILE;\n const oy = ty * TILE;\n const x0 = Math.max(rect.x, ox);\n const y0 = Math.max(rect.y, oy);\n const x1 = Math.min(rect.x + rect.width, ox + TILE);\n const y1 = Math.min(rect.y + rect.height, oy + TILE);\n const span = (x1 - x0) * 4;\n for (let y = y0; y < y1; y++) {\n const s = ((y - oy) * TILE + (x0 - ox)) * 4;\n out.set(data.subarray(s, s + span), ((y - rect.y) * rect.width + (x0 - rect.x)) * 4);\n }\n }, options);\n return out;\n }\n\n /**\n * Composites every tile intersecting `rect` and calls `onTile` with each tile's RGBA buffer\n * (TILE × TILE). A tile whose inputs did not change since the last call returns the same buffer\n * object; never mutate it.\n */\n compositeTiles(\n layers: readonly L[],\n rect: RasterRect,\n sources: S,\n onTile: (tx: number, ty: number, data: Uint8ClampedArray, changed: boolean) => void,\n options: CompositeCallOptions = {}\n ): void {\n this.setSources(sources);\n const steps = this.buildSteps(layers);\n this.hint = options.checkpointLayerId ? this.stepIndexOf(steps, options.checkpointLayerId) : -1;\n this.stamp++;\n const stats: CompositeStats = { tiles: 0, cached: 0, stepsRun: 0 };\n // A change reaches as far as the effects of any layer reach.\n const grow = options.dirty ? this.effectsReach(layers) : 0;\n const dirty = grow > 0 ? options.dirty!.map((d) => inflateRect(d, grow)) : options.dirty;\n for (const { tx, ty } of tilesInRect(rect)) {\n stats.tiles++;\n const before = this.tiles.get(tileKey(tx, ty))?.result;\n const data = this.compositeTile(layers, steps, tx, ty, stats, dirty ? dirtyInTile(dirty, tx, ty) : null);\n onTile(tx, ty, data, data !== before);\n }\n this.lastStats = stats;\n this.evict();\n }\n\n /** The composite of one tile (cached; do not mutate the result). */\n tile(layers: readonly L[], tx: number, ty: number, sources: S): Uint8ClampedArray {\n this.setSources(sources);\n this.stamp++;\n const stats: CompositeStats = { tiles: 1, cached: 0, stepsRun: 0 };\n this.hint = -1;\n const data = this.compositeTile(layers, this.buildSteps(layers), tx, ty, stats);\n this.lastStats = stats;\n this.evict();\n return data;\n }\n\n /**\n * A layer's effective mask coverage (0..1, after density, feather or parametric evaluation)\n * over `r` in document space, or `null` when it reveals all of `r`.\n */\n maskCoverage(layer: L, r: RasterRect, sources: S): Float32Array | null {\n this.setSources(sources);\n return this.maskTile(layer, r);\n }\n\n // --- Steps -------------------------------------------------------------------\n\n private isFilter(l: L): boolean {\n return this.profile.isFilterLayer?.(l) ?? false;\n }\n\n /**\n * Groups a layer list into compositing steps: clipped layers join the nearest unclipped\n * content or group layer below; consecutive unclipped filter layers form one run.\n */\n private buildSteps(layers: readonly L[]): Step<L>[] {\n const cached = this.stepsCache.get(layers);\n if (cached) return cached;\n const steps: Step<L>[] = [];\n for (const layer of layers) {\n const last = steps[steps.length - 1];\n if (layer.clipped && last && (last.kind === 'content' || last.kind === 'group')) {\n last.clipped.push(layer);\n continue;\n }\n if (this.isFilter(layer)) {\n if (last && last.kind === 'adjust') last.layers.push(layer);\n else steps.push({ kind: 'adjust', layers: [layer] });\n } else if (isGroup(layer)) {\n steps.push({ kind: 'group', layer, clipped: [], steps: this.buildSteps((layer.children ?? []) as L[]) });\n } else {\n steps.push({ kind: 'content', layer, clipped: [] });\n }\n }\n this.stepsCache.set(layers, steps);\n return steps;\n }\n\n /** The index of the top-level step that holds layer `id` (searching groups and clipped layers), or -1. */\n private stepIndexOf(steps: Step<L>[], id: string): number {\n const holds = (l: CompositorLayer): boolean => l.id === id || (isGroup(l) && (l.children ?? []).some(holds));\n return steps.findIndex((s) => (s.kind === 'adjust' ? s.layers.some(holds) : holds(s.layer) || s.clipped.some(holds)));\n }\n\n /** Properties of a layer that change pixels, minus its surfaces (memoised per object). */\n private propsKey(layer: L): string {\n let key = this.propsKeyCache.get(layer);\n if (key !== undefined) return key;\n const mask = layer.mask\n ? [layer.mask.enabled !== false, layer.mask.density ?? 1, layer.mask.feather ?? 0, layer.mask.defaultValue, layer.mask.source ?? null]\n : null;\n const base: unknown[] = [layer.type, layer.id, layer.visible, layer.opacity, layer.blend, Boolean(layer.clipped), mask];\n if (isPixel(layer)) base.push(layer.offset?.x ?? 0, layer.offset?.y ?? 0, layer.fillOpacity ?? 1);\n if (this.hasEffects(layer)) base.push(effectsFingerprint(layer.effects));\n const extra = this.profile.layerKey?.(layer);\n if (extra !== undefined) base.push(extra);\n key = JSON.stringify(base);\n this.propsKeyCache.set(layer, key);\n return key;\n }\n\n private compositeTile(root: readonly L[], steps: Step<L>[], tx: number, ty: number, stats: CompositeStats, dirty: RasterRect | null = null): Uint8ClampedArray {\n const key = tileKey(tx, ty);\n const entry = this.tiles.get(key);\n const sigs = steps.map((s) => this.stepSig(s, tx, ty));\n let first = 0;\n if (entry) {\n while (first < sigs.length && first < entry.sigs.length && sameSig(entry.sigs[first], sigs[first])) first++;\n if (first === sigs.length && first === entry.sigs.length) {\n entry.stamp = this.stamp;\n stats.cached++;\n return entry.result;\n }\n }\n // Resume from the best checkpoint at or below the first changed step.\n let start = 0;\n let base: { data: Uint8ClampedArray; empty: boolean } | null = null;\n if (entry) {\n for (const [index, cp] of entry.checkpoints) {\n if (index <= first && index > start) {\n start = index;\n base = cp;\n }\n }\n }\n const tileRect = { x: tx * TILE, y: ty * TILE, width: TILE, height: TILE };\n const hint = this.hint;\n const checkpoints = new Map<number, { data: Uint8ClampedArray; empty: boolean }>();\n if (entry) for (const [index, cp] of entry.checkpoints) if (index <= start) checkpoints.set(index, cp);\n let result: Uint8ClampedArray;\n\n if (entry && dirty && (dirty.width < TILE || dirty.height < TILE)) {\n // Only `dirty` changed: recomposite that part from the checkpoint and patch the previous result.\n const r = dirty;\n const sub = (data: Uint8ClampedArray | null) => {\n const out = new Uint8ClampedArray(r.width * r.height * 4);\n if (!data) return out;\n for (let y = 0; y < r.height; y++) {\n const s = ((r.y - tileRect.y + y) * TILE + (r.x - tileRect.x)) * 4;\n out.set(data.subarray(s, s + r.width * 4), y * r.width * 4);\n }\n return out;\n };\n const acc = sub(base ? base.data : null);\n let empty = base ? base.empty : true;\n for (let i = start; i < steps.length; i++) {\n empty = this.runStep(steps[i], acc, empty, r);\n stats.stepsRun++;\n }\n result = new Uint8ClampedArray(entry.result);\n for (let y = 0; y < r.height; y++) {\n result.set(acc.subarray(y * r.width * 4, (y + 1) * r.width * 4), ((r.y - tileRect.y + y) * TILE + (r.x - tileRect.x)) * 4);\n }\n } else {\n const acc = base ? new Uint8ClampedArray(base.data) : new Uint8ClampedArray(TILE_BYTES);\n let empty = base ? base.empty : true;\n for (let i = start; i < steps.length; i++) {\n if ((i === first || i === hint) && i > 0 && !checkpoints.has(i)) checkpoints.set(i, { data: new Uint8ClampedArray(acc), empty });\n empty = this.runStep(steps[i], acc, empty, tileRect);\n stats.stepsRun++;\n }\n result = empty ? ZERO_TILE : acc;\n }\n // Keep few checkpoints: below the active layer, below the step that changed, and the newest.\n if (checkpoints.size > 2) {\n const keys = [...checkpoints.keys()].sort((a, b) => a - b);\n const keep = new Set([keys[keys.length - 1], first, hint]);\n for (const k of keys) if (!keep.has(k)) checkpoints.delete(k);\n }\n const prevBytes = entry ? this.entryBytes(entry) : 0;\n const next: TileEntry<L> = { sigs, checkpoints, result, root, stamp: this.stamp };\n this.tiles.set(key, next);\n this.cacheBytes += this.entryBytes(next) - prevBytes;\n return result;\n }\n\n private entryBytes(e: TileEntry<L>): number {\n let n = e.result === ZERO_TILE ? 0 : TILE_BYTES;\n for (const cp of e.checkpoints.values()) n += cp.data.byteLength;\n return n;\n }\n\n private evict(): void {\n if (this.cacheBytes <= this.maxCacheBytes) return;\n // Drop checkpoints first (oldest tiles first), then whole entries not used this call.\n const entries = [...this.tiles.entries()].sort((a, b) => a[1].stamp - b[1].stamp);\n for (const [, e] of entries) {\n if (this.cacheBytes <= this.maxCacheBytes) return;\n for (const cp of e.checkpoints.values()) this.cacheBytes -= cp.data.byteLength;\n e.checkpoints.clear();\n }\n for (const [k, e] of entries) {\n if (this.cacheBytes <= this.maxCacheBytes) return;\n if (e.stamp === this.stamp) continue;\n this.cacheBytes -= this.entryBytes(e);\n this.tiles.delete(k);\n }\n }\n\n // --- Signatures -----------------------------------------------------------\n\n private stepSig(step: Step<L>, tx: number, ty: number): Sig {\n if (step.kind === 'adjust') {\n const refs: unknown[] = [];\n let key = 'A';\n for (const l of step.layers) {\n key += this.propsKey(l);\n refs.push(...this.maskRefs(l, tx, ty, 0, 0));\n }\n return { key, refs };\n }\n const own = step.kind === 'content' ? this.contentSig(step.layer, tx, ty) : this.groupSig(step.layer, step.steps, tx, ty);\n if (!step.clipped.length) return own;\n const refs = [...own.refs];\n let key = own.key;\n for (const c of step.clipped) {\n const s = this.layerSig(c, tx, ty);\n key += '|' + s.key;\n refs.push(...s.refs);\n }\n return { key, refs };\n }\n\n private layerSig(layer: L, tx: number, ty: number): Sig {\n if (this.isFilter(layer)) return { key: this.propsKey(layer), refs: this.maskRefs(layer, tx, ty, 0, 0) };\n if (isGroup(layer)) return this.groupSig(layer, this.buildSteps((layer.children ?? []) as L[]), tx, ty);\n return this.contentSig(layer, tx, ty);\n }\n\n private groupSig(group: L, steps: Step<L>[], tx: number, ty: number): Sig {\n if (group.visible && this.hasEffects(group)) return this.effectSig(group, tx, ty);\n return this.plainGroupSig(group, steps, tx, ty);\n }\n\n private plainGroupSig(group: L, steps: Step<L>[], tx: number, ty: number): Sig {\n let key = 'G' + this.propsKey(group);\n const refs = this.maskRefs(group, tx, ty, 0, 0);\n if (!group.visible) return { key, refs };\n for (const s of steps) {\n const sig = this.stepSig(s, tx, ty);\n key += '[' + sig.key + ']';\n refs.push(...sig.refs);\n }\n return { key, refs };\n }\n\n private contentSig(layer: L, tx: number, ty: number): Sig {\n if (layer.visible && this.hasEffects(layer)) return this.effectSig(layer, tx, ty);\n return this.plainContentSig(layer, tx, ty);\n }\n\n private plainContentSig(layer: L, tx: number, ty: number): Sig {\n const key = 'C' + this.propsKey(layer);\n if (!layer.visible) return { key, refs: [] };\n if (!isPixel(layer)) return { key, refs: this.maskRefs(layer, tx, ty, 0, 0) };\n const { surface, dx, dy } = this.layerPixels(layer);\n const refs: unknown[] = [surface.defaultValue];\n const r = { x: tx * TILE - dx, y: ty * TILE - dy, width: TILE, height: TILE };\n for (const t of tilesInRect(r)) refs.push(surface.getTile(t.tx, t.ty) ?? null);\n refs.push(...this.maskRefs(layer, tx, ty, layer.offset?.x ?? 0, layer.offset?.y ?? 0));\n return { key, refs };\n }\n\n // --- Layer effects ------------------------------------------------------------\n\n private hasEffects(layer: L): boolean {\n return hasActiveEffects(layer.effects) && !this.isFilter(layer);\n }\n\n /** The largest effects margin of any visible layer in the stack (memoised per root). */\n private effectsReach(layers: readonly L[]): number {\n let m = this.reachCache.get(layers);\n if (m !== undefined) return m;\n m = 0;\n const walk = (list: readonly CompositorLayer[]) => {\n for (const l of list) {\n if (!l.visible) continue;\n if (this.hasEffects(l as L)) m = Math.max(m!, effectsMargin(l.effects));\n if (l.children) walk(l.children);\n }\n };\n walk(layers);\n this.reachCache.set(layers, m);\n return m;\n }\n\n private effectContext(): EffectContext {\n if (this.fxContext) return this.fxContext;\n const sources = this.sources;\n const tiles = this.patternTiles;\n this.fxContext = {\n light: sources.globalLight ?? DEFAULT_GLOBAL_LIGHT,\n pattern: (id) => {\n const p = sources.pattern?.(id) ?? builtinPattern(id);\n if (!p || !(p.width > 0 && p.height > 0) || p.data.length < p.width * p.height * 4) return null;\n let t = tiles.get(p);\n if (!t) {\n t = new PatternTile(p);\n tiles.set(p, t);\n }\n return t;\n },\n };\n return this.fxContext;\n }\n\n /**\n * Where a layer's effect maps are built: its bounds (pixel layers; the canvas otherwise) grown by\n * the effects' margin, within the canvas grown the same.\n */\n private effectRegion(layer: L): RasterRect {\n const m = effectsMargin(layer.effects);\n const canvas = { x: -m, y: -m, width: this.sources.width + 2 * m, height: this.sources.height + 2 * m };\n if (!isPixel(layer)) return canvas;\n const { surface, dx, dy } = this.layerPixels(layer);\n if (surface.defaultValue !== 0) return canvas;\n const b = surface.bounds;\n if (!b) return { x: 0, y: 0, width: 0, height: 0 };\n return intersectRect({ x: b.x + dx - m, y: b.y + dy - m, width: b.width + 2 * m, height: b.height + 2 * m }, canvas);\n }\n\n /** The surface tiles (and mask refs) a layer's shape reads over `region`. */\n private shapeRefs(layer: L, region: RasterRect): unknown[] {\n const refs: unknown[] = [];\n if (region.width <= 0 || region.height <= 0) return refs;\n for (const t of tilesInRect(region)) {\n const sig = isGroup(layer) ? this.plainGroupSig(layer, this.buildSteps((layer.children ?? []) as L[]), t.tx, t.ty) : this.plainContentSig(layer, t.tx, t.ty);\n for (const r of sig.refs) refs.push(r);\n }\n return refs;\n }\n\n private lightKey(): string {\n const l = this.effectContext().light;\n return `${l.angle},${l.altitude}`;\n }\n\n /**\n * The signature of a layer with effects for tile (tx, ty): its properties and the tiles its shape\n * reads within the effects' margin around the tile, or over its whole region when an effect is\n * laid out over the layer's bounds.\n */\n private effectSig(layer: L, tx: number, ty: number): Sig {\n const key = (isGroup(layer) ? 'Gfx' : 'Cfx') + this.propsKey(layer) + this.lightKey();\n const region = this.effectRegion(layer);\n const m = effectsMargin(layer.effects);\n // Local maps over the tile do not depend on how far the region reaches (it is zero beyond the\n // shape); maps laid out over the layer's bounds do.\n if (effectsAreLocal(activeEffectItems(layer.effects))) {\n return { key, refs: this.shapeRefs(layer, intersectRect({ x: tx * TILE - m, y: ty * TILE - m, width: TILE + 2 * m, height: TILE + 2 * m }, region)) };\n }\n return { key, refs: [`${region.x},${region.y},${region.width},${region.height}`, ...this.shapeRefs(layer, region)] };\n }\n\n /** A layer's effect maps over its region (cached; see the module comment). */\n private effectMapsOf(layer: L): EffectMaps {\n const memo = this.effectMemo.get(layer);\n if (memo && memo.stamp === this.stamp) return memo.maps;\n const region = this.effectRegion(layer);\n const key = `${this.propsKey(layer)}${this.lightKey()}|${region.x},${region.y},${region.width},${region.height}`;\n const refs = this.shapeRefs(layer, region);\n const hit = this.effectCache.get(layer.id);\n let maps: EffectMaps;\n if (hit && hit.key === key && sameSig({ key, refs: hit.refs }, { key, refs })) {\n this.effectCache.delete(layer.id);\n this.effectCache.set(layer.id, hit);\n maps = hit.maps;\n } else {\n maps = buildEffectMaps(activeEffectItems(layer.effects), this.layerShape(layer, region), region, this.effectContext());\n this.effectCache.delete(layer.id);\n this.effectCache.set(layer.id, { key, refs, maps });\n let bytes = 0;\n for (const e of this.effectCache.values()) bytes += e.maps.bytes;\n while (this.effectCache.size > 1 && (this.effectCache.size > 32 || bytes > this.maxCacheBytes / 2)) {\n const [oldest, e] = this.effectCache.entries().next().value as [string, { maps: EffectMaps }];\n bytes -= e.maps.bytes;\n this.effectCache.delete(oldest);\n }\n }\n this.effectMemo.set(layer, { stamp: this.stamp, maps });\n return maps;\n }\n\n /** A layer's alpha over `r` after its mask (0..1, quantised to bytes as tiles see it). */\n private layerShape(layer: L, r: RasterRect): Float32Array {\n const n = r.width * r.height;\n const shape = new Float32Array(n);\n if (n <= 0) return shape;\n const rgba = this.ownPixels(layer, r);\n if (!rgba) return shape;\n for (let i = 0; i < n; i++) shape[i] = rgba[i * 4 + 3] / 255;\n return shape;\n }\n\n /** A content or group layer's own pixels over `r` with its mask applied to alpha, or `null` when empty. */\n private ownPixels(layer: L, r: RasterRect): Uint8ClampedArray | null {\n let rgba: Uint8ClampedArray | null;\n if (isGroup(layer)) {\n const buf = new Uint8ClampedArray(r.width * r.height * 4);\n rgba = this.runSteps(this.buildSteps((layer.children ?? []) as L[]), buf, true, r) ? null : buf;\n } else {\n const content = this.contentTile(layer, r);\n rgba = content ? new Uint8ClampedArray(content) : null;\n }\n if (!rgba) return null;\n const cov = this.maskTile(layer, r);\n if (cov) for (let i = 0, p = 3; i < cov.length; i++, p += 4) rgba[p] = Math.round(rgba[p] * cov[i]);\n return rgba;\n }\n\n /**\n * The part of `r` a layer with effects can change: its shape's bounds grown by the effects'\n * margin. Beyond it the layer and every effect are transparent, so the backdrop stays as it is.\n */\n private effectArea(layer: L, maps: EffectMaps, r: RasterRect): RasterRect | null {\n if (!maps.bounds) return null;\n const a = intersectRect(inflateRect(maps.bounds, effectsMargin(layer.effects)), r);\n return a.width > 0 && a.height > 0 ? a : null;\n }\n\n /**\n * Composites `layer`'s pixels (with `clipped` applied) and its effects onto `acc` (RGBA over `r`),\n * only where they can reach. `keepAlpha`: clipped layers keep the base's alpha.\n */\n private compositeEffects(layer: L, clipped: L[], acc: Uint8ClampedArray, r: RasterRect, keepAlpha: boolean): boolean {\n const maps = this.effectMapsOf(layer);\n const a = this.effectArea(layer, maps, r);\n if (!a) return false;\n const whole = a.width === r.width && a.height === r.height;\n const sub = whole ? acc : copyRect(acc, r, a);\n const content = this.ownPixels(layer, a) ?? new Uint8ClampedArray(a.width * a.height * 4);\n if (clipped.length) this.applyClipped(clipped, content, a);\n const keep = keepAlpha ? sub.filter((_, i) => (i & 3) === 3) : null;\n const blend = (isGroup(layer) ? (layer.blend === 'pass-through' ? 'normal' : layer.blend) : this.contentBlend(layer)) as BlendMode;\n compositeLayerEffects({\n backdrop: sub,\n content,\n rect: a,\n maps,\n blend,\n opacity: layer.opacity,\n fillOpacity: isPixel(layer) ? (layer.fillOpacity ?? 1) : 1,\n canvas: { x: 0, y: 0, width: this.sources.width, height: this.sources.height },\n ctx: this.effectContext(),\n });\n if (keep) for (let i = 0; i < keep.length; i++) sub[i * 4 + 3] = keep[i];\n if (!whole) pasteRect(acc, r, sub, a);\n return true;\n }\n\n /** Composites a layer with active effects onto `acc` over `r`; returns whether `acc` is still empty. */\n private runEffectStep(layer: L, clipped: L[], acc: Uint8ClampedArray, empty: boolean, r: RasterRect): boolean {\n return this.compositeEffects(layer, clipped, acc, r, false) ? false : empty;\n }\n\n private maskRefs(layer: L, tx: number, ty: number, ox: number, oy: number): unknown[] {\n const mask = layer.mask;\n if (!mask || mask.enabled === false) return [];\n if (mask.source || (mask.feather ?? 0) > 0) return [this.planeFor(mask, ox, oy)];\n const refs: unknown[] = [];\n for (const t of tilesInRect({ x: tx * TILE - ox, y: ty * TILE - oy, width: TILE, height: TILE })) {\n refs.push(mask.surface.getTile(t.tx, t.ty) ?? null);\n }\n return refs;\n }\n\n // --- Layer pixels ------------------------------------------------------------\n\n /**\n * A pixel layer's pixels after spatial filters: the surface and the offset to apply. Layers\n * with a spatial filter are filtered once over the canvas (in document space, offset 0).\n */\n private layerPixels(layer: L): { surface: PixelSurface; dx: number; dy: number; perPixel: PixelFilterSpec | null } {\n const raw = this.sources.surfaceOf ? this.sources.surfaceOf(layer) : (layer.surface as PixelSurface);\n const ox = layer.offset?.x ?? 0;\n const oy = layer.offset?.y ?? 0;\n const filters = this.profile.pixelFilters?.(layer) ?? null;\n if (!filters?.spatial) return { surface: raw, dx: ox, dy: oy, perPixel: filters };\n const fkey = `${ox},${oy}:${filters.key}`;\n let byKey = this.filteredCache.get(raw);\n if (!byKey) {\n byKey = new Map();\n this.filteredCache.set(raw, byKey);\n }\n let filtered = byKey.get(fkey);\n if (!filtered) {\n filtered = filterOverCanvas(raw, ox, oy, filters, this.sources.width, this.sources.height);\n byKey.clear();\n byKey.set(fkey, filtered);\n }\n return { surface: filtered, dx: 0, dy: 0, perPixel: null };\n }\n\n /** RGBA of a content layer over `r` (document space, inside one tile), or `null` when it has nothing there. */\n private contentTile(layer: L, r: RasterRect): Uint8ClampedArray | null {\n if (!isPixel(layer)) return this.profile.contentTile?.(layer, r, this.sources) ?? null;\n const { surface, dx, dy, perPixel } = this.layerPixels(layer);\n const src = { x: r.x - dx, y: r.y - dy, width: r.width, height: r.height };\n const covered = tilesInRect(src).map((t) => surface.getTile(t.tx, t.ty));\n if (surface.defaultValue === 0 && covered.every((t) => !t)) return null;\n const aligned = r.width === TILE && r.height === TILE && src.x % TILE === 0 && src.y % TILE === 0;\n if (!perPixel) {\n if (aligned && covered[0]) return covered[0].data;\n return readRegion(surface, src);\n }\n // Per-pixel filters: cache the filtered tile by its source tile and the filter settings.\n const fkey = `${dx},${dy}:${perPixel.key}`;\n const anchor = (covered.find(Boolean) ?? surface) as object;\n let byKey = this.contentCache.get(anchor);\n const refsKey = `${r.x},${r.y}:${fkey}`;\n if (aligned) {\n const hit = byKey?.get(refsKey);\n if (hit) return hit;\n }\n const data = readRegion(surface, src);\n perPixel.apply(data, r.width, r.height);\n if (aligned) {\n if (!byKey) {\n byKey = new Map();\n this.contentCache.set(anchor, byKey);\n }\n byKey.set(refsKey, data);\n }\n return data;\n }\n\n // --- Masks -------------------------------------------------------------------\n\n private planeKey(mask: RasterMask, ox: number, oy: number): string {\n const W = this.sources.width;\n const H = this.sources.height;\n let id = this.planeIds.get(mask.surface);\n if (id === undefined) {\n id = this.nextPlaneId++;\n this.planeIds.set(mask.surface, id);\n }\n return `${id}:${ox},${oy}:${W}x${H}:${mask.feather ?? 0}:${mask.density ?? 1}:${mask.source ? JSON.stringify(mask.source) : ''}`;\n }\n\n /** Canvas-sized coverage of a feathered or parametric mask (cached). */\n private planeFor(mask: RasterMask, ox: number, oy: number): CoveragePlane {\n const key = this.planeKey(mask, ox, oy);\n let plane = this.planeCache.get(key);\n if (plane) {\n this.planeCache.delete(key);\n this.planeCache.set(key, plane);\n return plane;\n }\n const W = this.sources.width;\n const H = this.sources.height;\n let v: Float32Array = new Float32Array(W * H);\n if (mask.source) {\n const generated = this.profile.maskSourcePlane?.(mask.source, W, H, this.sources) ?? null;\n if (!generated || generated.length !== W * H) v.fill(1);\n else v.set(generated);\n } else {\n const bytes = readRegion(mask.surface, { x: -ox, y: -oy, width: W, height: H });\n for (let i = 0; i < v.length; i++) v[i] = bytes[i] / 255;\n }\n const feather = mask.feather ?? 0;\n if (feather > 0) v = gaussianBlur(v, W, H, feather, 'clamp') as Float32Array;\n const density = mask.density ?? 1;\n if (density !== 1) for (let i = 0; i < v.length; i++) v[i] = 1 - density * (1 - v[i]);\n plane = { data: v, width: W, height: H };\n this.planeCache.set(key, plane);\n while (this.planeCache.size > 8) this.planeCache.delete(this.planeCache.keys().next().value as string);\n return plane;\n }\n\n private maskTile(layer: L, r: RasterRect): Float32Array | null {\n const mask = layer.mask;\n if (!mask || mask.enabled === false) return null;\n const ox = isPixel(layer) ? (layer.offset?.x ?? 0) : 0;\n const oy = isPixel(layer) ? (layer.offset?.y ?? 0) : 0;\n const n = r.width * r.height;\n const out = new Float32Array(n);\n const density = mask.density ?? 1;\n if (mask.source || (mask.feather ?? 0) > 0) {\n const plane = this.planeFor(mask, ox, oy);\n const W = plane.width;\n const H = plane.height;\n // Outside the canvas the mask holds its default (feathering clamps at the canvas edge).\n const outside = mask.source ? 1 : 1 - density * (1 - mask.defaultValue / 255);\n out.fill(outside);\n const x0 = Math.max(0, r.x);\n const y0 = Math.max(0, r.y);\n const x1 = Math.min(W, r.x + r.width);\n const y1 = Math.min(H, r.y + r.height);\n for (let y = y0; y < y1; y++) {\n const s = y * W;\n const d = (y - r.y) * r.width - r.x;\n for (let x = x0; x < x1; x++) out[d + x] = plane.data[s + x];\n }\n } else {\n const bytes = readRegion(mask.surface, { x: r.x - ox, y: r.y - oy, width: r.width, height: r.height });\n let all = true;\n for (let i = 0; i < n; i++) {\n out[i] = bytes[i] / 255;\n if (bytes[i] !== 255) all = false;\n }\n if (all) return null;\n if (density !== 1) for (let i = 0; i < n; i++) out[i] = 1 - density * (1 - out[i]);\n }\n return out;\n }\n\n // --- Running steps ---------------------------------------------------------\n\n /** Runs one step onto `acc`; returns whether `acc` is still empty. */\n private runStep(step: Step<L>, acc: Uint8ClampedArray, empty: boolean, r: RasterRect): boolean {\n if (step.kind === 'adjust') {\n if (empty) return true;\n this.applyFilterRun(step.layers, acc, r);\n return false;\n }\n const layer = step.layer;\n if (!layer.visible) return empty;\n const clipped = step.clipped.filter((l) => l.visible);\n // Effects can reach into a tile the layer's mask hides.\n if (this.hasEffects(layer)) return this.runEffectStep(layer, clipped, acc, empty, r);\n const coverage = this.maskTile(layer, r);\n if (coverage && coverage.every((v) => v <= 0)) return empty;\n if (step.kind === 'group') {\n if (layer.blend === 'pass-through' && !clipped.length) {\n if (layer.opacity >= 1 && !coverage) return this.runSteps(step.steps, acc, empty, r);\n const before = new Uint8ClampedArray(acc);\n const stillEmpty = this.runSteps(step.steps, acc, empty, r);\n lerpTile(acc, before, layer.opacity, coverage);\n return empty && stillEmpty;\n }\n const buf = new Uint8ClampedArray(acc.length);\n if (this.runSteps(step.steps, buf, true, r)) return empty;\n this.applyClipped(clipped, buf, r);\n const mode = (layer.blend === 'pass-through' ? 'normal' : layer.blend) as BlendMode;\n return this.blendOnto(acc, buf, empty, mode, layer.opacity, coverage);\n }\n const content = this.contentTile(step.layer, r);\n if (!content) return empty;\n const fill = isPixel(step.layer) ? (step.layer.fillOpacity ?? 1) : 1;\n const blend = this.contentBlend(step.layer);\n if (!clipped.length) return this.blendOnto(acc, content, empty, blend, layer.opacity * fill, coverage);\n // Clipping group: the base (with its mask and fill) is the shape the clipped layers paint in.\n const buf = new Uint8ClampedArray(acc.length);\n blendTile(buf, content, 'normal', fill, coverage);\n this.applyClipped(clipped, buf, r);\n return this.blendOnto(acc, buf, empty, blend, layer.opacity, null);\n }\n\n private runSteps(steps: Step<L>[], acc: Uint8ClampedArray, empty: boolean, r: RasterRect): boolean {\n for (const s of steps) empty = this.runStep(s, acc, empty, r);\n return empty;\n }\n\n private contentBlend(layer: L): BlendMode {\n return this.profile.contentBlend?.(layer) ?? (layer.blend as BlendMode);\n }\n\n private blendOnto(acc: Uint8ClampedArray, src: Uint8ClampedArray, empty: boolean, mode: BlendMode, alpha: number, coverage: Float32Array | null): boolean {\n if (!(alpha > 0)) return empty;\n if (empty && mode === 'normal' && alpha >= 1 && !coverage) {\n // Over a transparent accumulator a normal, opaque, unmasked layer is a plain copy (which also\n // keeps the colour of transparent pixels, as a single-layer render always has).\n acc.set(src);\n return false;\n }\n blendTile(acc, src, mode, alpha, coverage);\n return false;\n }\n\n private applyClipped(clipped: L[], buf: Uint8ClampedArray, r: RasterRect): void {\n let run: L[] = [];\n const flush = () => {\n if (run.length) this.applyFilterRun(run, buf, r);\n run = [];\n };\n for (const c of clipped) {\n if (this.isFilter(c)) {\n run.push(c);\n continue;\n }\n flush();\n if (this.hasEffects(c) && !isGroup(c)) {\n // Clipped to the base: the layer and its effects composite, then the base's alpha is kept.\n this.compositeEffects(c, [], buf, r, true);\n continue;\n }\n const coverage = this.maskTile(c, r);\n if (isGroup(c)) {\n const inner = new Uint8ClampedArray(buf.length);\n if (this.runSteps(this.buildSteps((c.children ?? []) as L[]), inner, true, r)) continue;\n const mode = (c.blend === 'pass-through' ? 'normal' : c.blend) as BlendMode;\n blendAtop(buf, inner, mode, c.opacity, coverage);\n continue;\n }\n const content = this.contentTile(c, r);\n if (!content) continue;\n const fill = isPixel(c) ? (c.fillOpacity ?? 1) : 1;\n blendAtop(buf, content, this.contentBlend(c), c.opacity * fill, coverage);\n }\n flush();\n }\n\n private applyFilterRun(layers: L[], acc: Uint8ClampedArray, r: RasterRect): void {\n const visible: L[] = [];\n let masks: Map<string, Float32Array> | undefined;\n for (const node of layers) {\n if (!node.visible) continue;\n const cov = this.maskTile(node, r);\n if (cov) {\n if (cov.every((v) => v <= 0)) continue;\n (masks ??= new Map()).set(node.id, cov);\n }\n visible.push(node);\n }\n if (visible.length) this.profile.applyFilterRun?.(visible, acc, r, masks, this.sources);\n }\n}\n\n/**\n * Applies a layer's filters over the canvas rect in document space: the result is a surface at\n * offset 0 holding only canvas pixels (spatial filters clamp at the canvas edges).\n */\nfunction filterOverCanvas(surface: PixelSurface, ox: number, oy: number, filters: PixelFilterSpec, width: number, height: number): PixelSurface {\n if (width <= 0 || height <= 0) return emptySurface(4);\n const data = readRegion(surface, { x: -ox, y: -oy, width, height });\n filters.apply(data, width, height);\n return surfaceFromPixels({ width, height, data }, 4, 0, 0);\n}\n\n/** The part `a` of RGBA `data` over `r`, as its own buffer. */\nfunction copyRect(data: Uint8ClampedArray, r: RasterRect, a: RasterRect): Uint8ClampedArray {\n const out = new Uint8ClampedArray(a.width * a.height * 4);\n for (let y = 0; y < a.height; y++) {\n const s = ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4;\n out.set(data.subarray(s, s + a.width * 4), y * a.width * 4);\n }\n return out;\n}\n\n/** Writes `part` (RGBA over `a`) back into `data` (over `r`). */\nfunction pasteRect(data: Uint8ClampedArray, r: RasterRect, part: Uint8ClampedArray, a: RasterRect): void {\n for (let y = 0; y < a.height; y++) data.set(part.subarray(y * a.width * 4, (y + 1) * a.width * 4), ((a.y - r.y + y) * r.width + (a.x - r.x)) * 4);\n}\n\nfunction inflateRect(r: RasterRect, m: number): RasterRect {\n return { x: r.x - m, y: r.y - m, width: r.width + 2 * m, height: r.height + 2 * m };\n}\n\nfunction intersectRect(a: RasterRect, b: RasterRect): RasterRect {\n const x0 = Math.max(a.x, b.x);\n const y0 = Math.max(a.y, b.y);\n const x1 = Math.min(a.x + a.width, b.x + b.width);\n const y1 = Math.min(a.y + a.height, b.y + b.height);\n return x1 <= x0 || y1 <= y0 ? { x: x0, y: y0, width: 0, height: 0 } : { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };\n}\n\n/** Tiles that differ between two surfaces (by tile identity), as tile keys. */\nexport function changedTiles(a: TiledSurface, b: TiledSurface): TileKey[] {\n const out: TileKey[] = [];\n for (const [k, t] of b.tiles) if (a.tiles.get(k) !== t) out.push(k);\n for (const k of a.tiles.keys()) if (!b.tiles.has(k)) out.push(k);\n return out;\n}\n","/**\n * Synchronous zlib (RFC 1950) and raw deflate (RFC 1951), dependency-free, for the image editor's\n * layer PNGs and PSD ZIP channels. `CompressionStream` is asynchronous and not everywhere, and\n * layer pixels must serialize inside the synchronous `toJSON()`.\n *\n * - `inflate` handles stored, fixed and dynamic blocks and refuses to produce more than\n * `maxOutput` bytes (a decompression bomb guard).\n * - `deflate` is a greedy LZ77 (hash chains, 32 KiB window) with one dynamic Huffman block per\n * 64 Ki symbols. It favours simplicity over the last few percent of ratio.\n */\n\n// ---------------------------------------------------------------------------\n// Shared tables\n// ---------------------------------------------------------------------------\n\nconst LENGTH_BASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258];\nconst LENGTH_EXTRA = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];\nconst DIST_BASE = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577];\nconst DIST_EXTRA = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];\nconst CL_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];\n\nexport class ZlibError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ZlibError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Inflate\n// ---------------------------------------------------------------------------\n\ninterface Huffman {\n counts: Uint16Array;\n symbols: Uint16Array;\n}\n\nfunction buildHuffman(lengths: ArrayLike<number>, n: number): Huffman {\n const counts = new Uint16Array(16);\n for (let i = 0; i < n; i++) counts[lengths[i]]++;\n counts[0] = 0;\n const offs = new Uint16Array(16);\n for (let i = 1; i < 16; i++) offs[i] = offs[i - 1] + counts[i - 1];\n const symbols = new Uint16Array(n);\n for (let i = 0; i < n; i++) if (lengths[i]) symbols[offs[lengths[i]]++] = i;\n return { counts, symbols };\n}\n\nlet fixedLit: Huffman | null = null;\nlet fixedDist: Huffman | null = null;\nfunction fixedTables(): [Huffman, Huffman] {\n if (!fixedLit || !fixedDist) {\n const l = new Uint8Array(288);\n for (let i = 0; i < 144; i++) l[i] = 8;\n for (let i = 144; i < 256; i++) l[i] = 9;\n for (let i = 256; i < 280; i++) l[i] = 7;\n for (let i = 280; i < 288; i++) l[i] = 8;\n fixedLit = buildHuffman(l, 288);\n const d = new Uint8Array(30).fill(5);\n fixedDist = buildHuffman(d, 30);\n }\n return [fixedLit, fixedDist];\n}\n\nclass BitReader {\n pos = 0;\n bit = 0;\n bits = 0;\n constructor(readonly data: Uint8Array) {}\n need(n: number): number {\n while (this.bits < n) {\n if (this.pos >= this.data.length) throw new ZlibError('Unexpected end of compressed data');\n this.bit |= this.data[this.pos++] << this.bits;\n this.bits += 8;\n }\n const v = this.bit & ((1 << n) - 1);\n this.bit >>>= n;\n this.bits -= n;\n return v;\n }\n decode(h: Huffman): number {\n let code = 0;\n let first = 0;\n let index = 0;\n for (let len = 1; len < 16; len++) {\n code |= this.need(1);\n const count = h.counts[len];\n if (code - count < first) return h.symbols[index + (code - first)];\n index += count;\n first += count;\n first <<= 1;\n code <<= 1;\n }\n throw new ZlibError('Invalid Huffman code');\n }\n align(): void {\n this.bit = 0;\n this.bits = 0;\n }\n}\n\nclass Output {\n buf: Uint8Array;\n len = 0;\n constructor(hint: number, readonly max: number) {\n this.buf = new Uint8Array(Math.max(1024, Math.min(hint, max)));\n }\n ensure(n: number): void {\n if (this.len + n > this.max) throw new ZlibError(`Decompressed data exceeds ${this.max} bytes`);\n if (this.len + n <= this.buf.length) return;\n let size = this.buf.length * 2;\n while (size < this.len + n) size *= 2;\n const next = new Uint8Array(Math.min(size, Math.max(this.max, this.len + n)));\n next.set(this.buf.subarray(0, this.len));\n this.buf = next;\n }\n}\n\n/** Inflates raw deflate data (no zlib header). */\nexport function inflateRaw(data: Uint8Array, options: { maxOutput?: number; sizeHint?: number } = {}): Uint8Array {\n const max = options.maxOutput ?? 512 * 1024 * 1024;\n const out = new Output(options.sizeHint ?? data.length * 4, max);\n const br = new BitReader(data);\n let final = 0;\n while (!final) {\n final = br.need(1);\n const type = br.need(2);\n if (type === 0) {\n br.align();\n if (br.pos + 4 > data.length) throw new ZlibError('Unexpected end of compressed data');\n const len = data[br.pos] | (data[br.pos + 1] << 8);\n const nlen = data[br.pos + 2] | (data[br.pos + 3] << 8);\n br.pos += 4;\n if ((len ^ 0xffff) !== nlen) throw new ZlibError('Stored block length mismatch');\n if (br.pos + len > data.length) throw new ZlibError('Unexpected end of compressed data');\n out.ensure(len);\n out.buf.set(data.subarray(br.pos, br.pos + len), out.len);\n out.len += len;\n br.pos += len;\n continue;\n }\n let lit: Huffman;\n let dist: Huffman;\n if (type === 1) [lit, dist] = fixedTables();\n else if (type === 2) {\n const hlit = br.need(5) + 257;\n const hdist = br.need(5) + 1;\n const hclen = br.need(4) + 4;\n const cl = new Uint8Array(19);\n for (let i = 0; i < hclen; i++) cl[CL_ORDER[i]] = br.need(3);\n const clh = buildHuffman(cl, 19);\n const lengths = new Uint8Array(hlit + hdist);\n let i = 0;\n while (i < hlit + hdist) {\n const sym = br.decode(clh);\n if (sym < 16) lengths[i++] = sym;\n else {\n let rep = 0;\n let val = 0;\n if (sym === 16) {\n if (i === 0) throw new ZlibError('Repeat with no previous length');\n val = lengths[i - 1];\n rep = 3 + br.need(2);\n } else if (sym === 17) rep = 3 + br.need(3);\n else rep = 11 + br.need(7);\n if (i + rep > hlit + hdist) throw new ZlibError('Too many code lengths');\n while (rep--) lengths[i++] = val;\n }\n }\n lit = buildHuffman(lengths.subarray(0, hlit), hlit);\n dist = buildHuffman(lengths.subarray(hlit), hdist);\n } else throw new ZlibError('Invalid block type');\n for (;;) {\n const sym = br.decode(lit);\n if (sym < 256) {\n out.ensure(1);\n out.buf[out.len++] = sym;\n } else if (sym === 256) break;\n else {\n const li = sym - 257;\n if (li >= 29) throw new ZlibError('Invalid length symbol');\n const len = LENGTH_BASE[li] + br.need(LENGTH_EXTRA[li]);\n const di = br.decode(dist);\n if (di >= 30) throw new ZlibError('Invalid distance symbol');\n const d = DIST_BASE[di] + br.need(DIST_EXTRA[di]);\n if (d > out.len) throw new ZlibError('Distance too far back');\n out.ensure(len);\n const buf = out.buf;\n let p = out.len;\n for (let k = 0; k < len; k++, p++) buf[p] = buf[p - d];\n out.len = p;\n }\n }\n }\n return out.buf.slice(0, out.len);\n}\n\n/** Adler-32 checksum (zlib trailer). */\nexport function adler32(data: Uint8Array): number {\n let a = 1;\n let b = 0;\n const n = data.length;\n let i = 0;\n while (i < n) {\n const end = Math.min(n, i + 3800);\n for (; i < end; i++) {\n a += data[i];\n b += a;\n }\n a %= 65521;\n b %= 65521;\n }\n return ((b << 16) | a) >>> 0;\n}\n\n/** Inflates zlib data (header, deflate stream, Adler-32 trailer). */\nexport function inflate(data: Uint8Array, options: { maxOutput?: number; sizeHint?: number; verify?: boolean } = {}): Uint8Array {\n if (data.length < 6) throw new ZlibError('Truncated zlib stream');\n const cmf = data[0];\n const flg = data[1];\n if ((cmf & 15) !== 8 || ((cmf << 8) | flg) % 31 !== 0) throw new ZlibError('Not a zlib stream');\n if (flg & 32) throw new ZlibError('Preset dictionaries are not supported');\n const out = inflateRaw(data.subarray(2), options);\n if (options.verify !== false && data.length >= 6) {\n const t = data.length - 4;\n const want = ((data[t] << 24) | (data[t + 1] << 16) | (data[t + 2] << 8) | data[t + 3]) >>> 0;\n if (adler32(out) !== want) throw new ZlibError('Adler-32 mismatch');\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Deflate\n// ---------------------------------------------------------------------------\n\nclass BitWriter {\n buf: Uint8Array;\n len = 0;\n bit = 0;\n bits = 0;\n constructor(hint: number) {\n this.buf = new Uint8Array(Math.max(1024, hint));\n }\n private grow(): void {\n const next = new Uint8Array(this.buf.length * 2);\n next.set(this.buf);\n this.buf = next;\n }\n write(value: number, n: number): void {\n this.bit |= value << this.bits;\n this.bits += n;\n while (this.bits >= 8) {\n if (this.len >= this.buf.length) this.grow();\n this.buf[this.len++] = this.bit & 255;\n this.bit >>>= 8;\n this.bits -= 8;\n }\n }\n flush(): void {\n if (this.bits > 0) {\n if (this.len >= this.buf.length) this.grow();\n this.buf[this.len++] = this.bit & 255;\n }\n this.bit = 0;\n this.bits = 0;\n }\n bytes(): Uint8Array {\n return this.buf.slice(0, this.len);\n }\n}\n\n/** Code lengths (≤ maxBits) for symbol frequencies, by Huffman's algorithm with frequency flattening. */\nfunction codeLengths(freqs: Uint32Array, maxBits: number): Uint8Array {\n const n = freqs.length;\n const lengths = new Uint8Array(n);\n const f = Float64Array.from(freqs);\n for (;;) {\n const nodes: Array<{ w: number; sym: number; left?: number; right?: number }> = [];\n for (let i = 0; i < n; i++) if (f[i] > 0) nodes.push({ w: f[i], sym: i });\n lengths.fill(0);\n if (nodes.length === 0) return lengths;\n if (nodes.length === 1) {\n lengths[nodes[0].sym] = 1;\n return lengths;\n }\n // Simple binary heap of node indexes by weight.\n const heap: number[] = nodes.map((_, i) => i);\n const less = (a: number, b: number) => nodes[a].w < nodes[b].w || (nodes[a].w === nodes[b].w && a < b);\n const down = (i: number) => {\n for (;;) {\n const l = 2 * i + 1;\n const r = l + 1;\n let m = i;\n if (l < heap.length && less(heap[l], heap[m])) m = l;\n if (r < heap.length && less(heap[r], heap[m])) m = r;\n if (m === i) return;\n [heap[i], heap[m]] = [heap[m], heap[i]];\n i = m;\n }\n };\n const up = (i: number) => {\n while (i > 0) {\n const p = (i - 1) >> 1;\n if (!less(heap[i], heap[p])) return;\n [heap[i], heap[p]] = [heap[p], heap[i]];\n i = p;\n }\n };\n for (let i = (heap.length >> 1) - 1; i >= 0; i--) down(i);\n const pop = () => {\n const top = heap[0];\n const last = heap.pop()!;\n if (heap.length) {\n heap[0] = last;\n down(0);\n }\n return top;\n };\n while (heap.length > 1) {\n const a = pop();\n const b = pop();\n nodes.push({ w: nodes[a].w + nodes[b].w, sym: -1, left: a, right: b });\n heap.push(nodes.length - 1);\n up(heap.length - 1);\n }\n let maxLen = 0;\n const stack: Array<[number, number]> = [[heap[0], 0]];\n while (stack.length) {\n const [i, d] = stack.pop()!;\n const node = nodes[i];\n if (node.sym >= 0) {\n lengths[node.sym] = d;\n if (d > maxLen) maxLen = d;\n } else {\n stack.push([node.left!, d + 1], [node.right!, d + 1]);\n }\n }\n if (maxLen <= maxBits) return lengths;\n // Too deep: flatten the distribution and retry.\n for (let i = 0; i < n; i++) if (f[i] > 0) f[i] = Math.floor(f[i] / 2) + 1;\n }\n}\n\n/** Canonical codes for code lengths, bit-reversed for LSB-first output. */\nfunction canonicalCodes(lengths: Uint8Array): Uint16Array {\n const blCount = new Uint16Array(16);\n for (const l of lengths) blCount[l]++;\n blCount[0] = 0;\n const next = new Uint16Array(16);\n let code = 0;\n for (let bits = 1; bits < 16; bits++) {\n code = (code + blCount[bits - 1]) << 1;\n next[bits] = code;\n }\n const codes = new Uint16Array(lengths.length);\n for (let i = 0; i < lengths.length; i++) {\n const len = lengths[i];\n if (!len) continue;\n let c = next[len]++;\n let r = 0;\n for (let k = 0; k < len; k++) {\n r = (r << 1) | (c & 1);\n c >>= 1;\n }\n codes[i] = r;\n }\n return codes;\n}\n\nfunction lengthSymbol(len: number): number {\n let i = 28;\n while (LENGTH_BASE[i] > len) i--;\n return i;\n}\n\nfunction distSymbol(d: number): number {\n let i = 29;\n while (DIST_BASE[i] > d) i--;\n return i;\n}\n\nconst WINDOW = 32768;\nconst HASH_BITS = 15;\nconst HASH_SIZE = 1 << HASH_BITS;\nconst MAX_CHAIN = 24;\nconst MAX_MATCH = 258;\nconst BLOCK_SYMBOLS = 65536;\n\n/** Raw deflate (no zlib header). `level` 0 stores the data uncompressed. */\nexport function deflateRaw(data: Uint8Array, level: 0 | 1 = 1): Uint8Array {\n const w = new BitWriter(Math.ceil(data.length / 2) + 64);\n const n = data.length;\n if (level === 0 || n === 0) {\n let pos = 0;\n do {\n const len = Math.min(65535, n - pos);\n const final = pos + len >= n ? 1 : 0;\n w.write(final, 1);\n w.write(0, 2);\n w.flush();\n w.write(len & 0xffff, 16);\n w.write(~len & 0xffff, 16);\n for (let i = 0; i < len; i++) w.write(data[pos + i], 8);\n pos += len;\n } while (pos < n);\n return w.bytes();\n }\n\n const head = new Int32Array(HASH_SIZE).fill(-1);\n const prev = new Int32Array(WINDOW);\n // Symbols of the current block: literal (0..255) or length/distance pairs.\n const litLen = new Uint16Array(BLOCK_SYMBOLS);\n const dists = new Uint16Array(BLOCK_SYMBOLS);\n let count = 0;\n const hash = (i: number) => (((data[i] << 10) ^ (data[i + 1] << 5) ^ data[i + 2]) * 2654435761) >>> (32 - HASH_BITS);\n const insert = (i: number) => {\n if (i + 2 >= n) return;\n const h = hash(i);\n prev[i & (WINDOW - 1)] = head[h];\n head[h] = i;\n };\n\n const flushBlock = (final: boolean) => {\n const litFreq = new Uint32Array(286);\n const distFreq = new Uint32Array(30);\n for (let k = 0; k < count; k++) {\n if (dists[k] === 0) litFreq[litLen[k]]++;\n else {\n litFreq[257 + lengthSymbol(litLen[k])]++;\n distFreq[distSymbol(dists[k])]++;\n }\n }\n litFreq[256] = 1;\n let anyDist = false;\n for (const v of distFreq) if (v) anyDist = true;\n if (!anyDist) distFreq[0] = 1;\n const litLengths = codeLengths(litFreq, 15);\n const distLengths = codeLengths(distFreq, 15);\n let hlit = 286;\n while (hlit > 257 && !litLengths[hlit - 1]) hlit--;\n let hdist = 30;\n while (hdist > 1 && !distLengths[hdist - 1]) hdist--;\n const all = new Uint8Array(hlit + hdist);\n all.set(litLengths.subarray(0, hlit));\n all.set(distLengths.subarray(0, hdist), hlit);\n // Run-length encode the code lengths.\n const clSyms: number[] = [];\n const clExtra: number[] = [];\n for (let i = 0; i < all.length; ) {\n const v = all[i];\n let run = 1;\n while (i + run < all.length && all[i + run] === v) run++;\n if (v === 0 && run >= 3) {\n const r = Math.min(run, 138);\n if (r >= 11) {\n clSyms.push(18);\n clExtra.push(r - 11);\n } else {\n clSyms.push(17);\n clExtra.push(r - 3);\n }\n i += r;\n } else if (v !== 0 && run >= 4) {\n clSyms.push(v);\n clExtra.push(0);\n const r = Math.min(run - 1, 6);\n clSyms.push(16);\n clExtra.push(r - 3);\n i += r + 1;\n } else {\n clSyms.push(v);\n clExtra.push(0);\n i++;\n }\n }\n const clFreq = new Uint32Array(19);\n for (const s of clSyms) clFreq[s]++;\n const clLengths = codeLengths(clFreq, 7);\n let hclen = 19;\n while (hclen > 4 && !clLengths[CL_ORDER[hclen - 1]]) hclen--;\n const clCodes = canonicalCodes(clLengths);\n const litCodes = canonicalCodes(litLengths);\n const distCodes = canonicalCodes(distLengths);\n\n w.write(final ? 1 : 0, 1);\n w.write(2, 2);\n w.write(hlit - 257, 5);\n w.write(hdist - 1, 5);\n w.write(hclen - 4, 4);\n for (let i = 0; i < hclen; i++) w.write(clLengths[CL_ORDER[i]], 3);\n for (let i = 0; i < clSyms.length; i++) {\n const s = clSyms[i];\n w.write(clCodes[s], clLengths[s]);\n if (s === 16) w.write(clExtra[i], 2);\n else if (s === 17) w.write(clExtra[i], 3);\n else if (s === 18) w.write(clExtra[i], 7);\n }\n for (let k = 0; k < count; k++) {\n const d = dists[k];\n if (d === 0) {\n const s = litLen[k];\n w.write(litCodes[s], litLengths[s]);\n } else {\n const len = litLen[k];\n const li = lengthSymbol(len);\n w.write(litCodes[257 + li], litLengths[257 + li]);\n if (LENGTH_EXTRA[li]) w.write(len - LENGTH_BASE[li], LENGTH_EXTRA[li]);\n const di = distSymbol(d);\n w.write(distCodes[di], distLengths[di]);\n if (DIST_EXTRA[di]) w.write(d - DIST_BASE[di], DIST_EXTRA[di]);\n }\n }\n w.write(litCodes[256], litLengths[256]);\n count = 0;\n };\n\n let i = 0;\n while (i < n) {\n let bestLen = 0;\n let bestDist = 0;\n if (i + 2 < n) {\n let cand = head[hash(i)];\n let chain = MAX_CHAIN;\n const limit = Math.min(MAX_MATCH, n - i);\n while (cand >= 0 && i - cand <= WINDOW - 1 && chain-- > 0) {\n if (data[cand + bestLen] === data[i + bestLen]) {\n let l = 0;\n while (l < limit && data[cand + l] === data[i + l]) l++;\n if (l > bestLen) {\n bestLen = l;\n bestDist = i - cand;\n if (l === limit) break;\n }\n }\n const next = prev[cand & (WINDOW - 1)];\n if (next >= cand) break;\n cand = next;\n }\n }\n if (bestLen >= 3) {\n litLen[count] = bestLen;\n dists[count] = bestDist;\n count++;\n const end = i + bestLen;\n // Insert every position of long runs only up to a cap, which keeps solid areas fast.\n const insertEnd = bestLen > 64 ? i + 64 : end;\n for (let k = i; k < insertEnd; k++) insert(k);\n for (let k = Math.max(insertEnd, end - 3); k < end; k++) insert(k);\n i = end;\n } else {\n litLen[count] = data[i];\n dists[count] = 0;\n count++;\n insert(i);\n i++;\n }\n if (count >= BLOCK_SYMBOLS - 1 && i < n) flushBlock(false);\n }\n // The final block may hold only the end-of-block symbol.\n flushBlock(true);\n w.flush();\n return w.bytes();\n}\n\n/** zlib stream: header, raw deflate, Adler-32. */\nexport function deflate(data: Uint8Array, level: 0 | 1 = 1): Uint8Array {\n const raw = deflateRaw(data, level);\n const out = new Uint8Array(raw.length + 6);\n out[0] = 0x78;\n out[1] = 0x01;\n out.set(raw, 2);\n const a = adler32(data);\n const t = out.length - 4;\n out[t] = a >>> 24;\n out[t + 1] = (a >>> 16) & 255;\n out[t + 2] = (a >>> 8) & 255;\n out[t + 3] = a & 255;\n return out;\n}\n","/**\n * Dependency-free, synchronous PNG encode / decode for layer pixels and masks, plus base64 and\n * data-URL helpers. Canvas `toDataURL` is lossy for partly transparent pixels (it goes through\n * premultiplied alpha), so layer assets use this codec and round-trip exactly.\n *\n * Encodes 8-bit greyscale (masks) or RGBA (layers). Decodes non-interlaced 1/2/4/8/16-bit\n * greyscale, RGB, palette, grey + alpha and RGBA, always to 8-bit RGBA or grey.\n */\n\nimport { deflate, inflate } from './zlib';\n\nconst SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];\n\nlet crcTable: Uint32Array | null = null;\nfunction crc32(bytes: Uint8Array, start: number, end: number): number {\n if (!crcTable) {\n crcTable = new Uint32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n crcTable[n] = c >>> 0;\n }\n }\n let c = 0xffffffff;\n for (let i = start; i < end; i++) c = crcTable[(c ^ bytes[i]) & 255] ^ (c >>> 8);\n return (c ^ 0xffffffff) >>> 0;\n}\n\nexport interface DecodedPng {\n width: number;\n height: number;\n /** 4 for RGBA output, 1 for greyscale without alpha when `keepGray` was asked for. */\n channels: 1 | 4;\n data: Uint8ClampedArray;\n}\n\nfunction paeth(a: number, b: number, c: number): number {\n const p = a + b - c;\n const pa = Math.abs(p - a);\n const pb = Math.abs(p - b);\n const pc = Math.abs(p - c);\n return pa <= pb && pa <= pc ? a : pb <= pc ? b : c;\n}\n\n/** PNG bytes for `width × height` pixels of 1 (grey) or 4 (RGBA) bytes each. */\nexport function encodePng(width: number, height: number, data: ArrayLike<number>, channels: 1 | 4 = 4): Uint8Array {\n const stride = width * channels;\n const raw = new Uint8Array((stride + 1) * height);\n const bpp = channels;\n const cand = [new Uint8Array(stride), new Uint8Array(stride), new Uint8Array(stride), new Uint8Array(stride), new Uint8Array(stride)];\n for (let y = 0; y < height; y++) {\n const row = y * stride;\n const prevRow = (y - 1) * stride;\n // Try each filter and keep the one with the smallest sum of signed residues.\n let best = 0;\n let bestScore = Infinity;\n for (let f = 0; f < 5; f++) {\n const out = cand[f];\n let score = 0;\n for (let x = 0; x < stride; x++) {\n const v = data[row + x];\n const a = x >= bpp ? data[row + x - bpp] : 0;\n const b = y > 0 ? data[prevRow + x] : 0;\n const c = x >= bpp && y > 0 ? data[prevRow + x - bpp] : 0;\n let r: number;\n switch (f) {\n case 0: r = v; break;\n case 1: r = v - a; break;\n case 2: r = v - b; break;\n case 3: r = v - ((a + b) >> 1); break;\n default: r = v - paeth(a, b, c);\n }\n r &= 255;\n out[x] = r;\n score += r < 128 ? r : 256 - r;\n if (score >= bestScore) break;\n }\n if (score < bestScore) {\n bestScore = score;\n best = f;\n }\n }\n const o = y * (stride + 1);\n raw[o] = best;\n // Only a filter whose scoring loop ran to the end can win, so its row is complete.\n raw.set(cand[best], o + 1);\n }\n const idat = deflate(raw);\n const chunks: Array<[string, Uint8Array]> = [];\n const ihdr = new Uint8Array(13);\n writeU32(ihdr, 0, width);\n writeU32(ihdr, 4, height);\n ihdr[8] = 8;\n ihdr[9] = channels === 1 ? 0 : 6;\n chunks.push(['IHDR', ihdr], ['IDAT', idat], ['IEND', new Uint8Array(0)]);\n let size = 8;\n for (const [, d] of chunks) size += 12 + d.length;\n const out = new Uint8Array(size);\n out.set(SIGNATURE, 0);\n let p = 8;\n for (const [type, d] of chunks) {\n writeU32(out, p, d.length);\n for (let i = 0; i < 4; i++) out[p + 4 + i] = type.charCodeAt(i);\n out.set(d, p + 8);\n writeU32(out, p + 8 + d.length, crc32(out, p + 4, p + 8 + d.length));\n p += 12 + d.length;\n }\n return out;\n}\n\nfunction writeU32(b: Uint8Array, o: number, v: number): void {\n b[o] = v >>> 24;\n b[o + 1] = (v >>> 16) & 255;\n b[o + 2] = (v >>> 8) & 255;\n b[o + 3] = v & 255;\n}\n\nfunction readU32(b: Uint8Array, o: number): number {\n return ((b[o] << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3]) >>> 0;\n}\n\nexport function isPng(bytes: Uint8Array): boolean {\n return bytes.length > 8 && SIGNATURE.every((v, i) => bytes[i] === v);\n}\n\n/**\n * Decodes a PNG. Output is RGBA (4 channels) unless `keepGray` is set and the image is\n * greyscale without alpha, in which case it is one byte per pixel.\n */\nexport function decodePng(bytes: Uint8Array, options: { keepGray?: boolean; maxPixels?: number } = {}): DecodedPng {\n if (!isPng(bytes)) throw new Error('Not a PNG image.');\n let p = 8;\n let width = 0;\n let height = 0;\n let depth = 0;\n let type = 0;\n let interlace = 0;\n let palette: Uint8Array | null = null;\n let trns: Uint8Array | null = null;\n const idat: Uint8Array[] = [];\n while (p + 8 <= bytes.length) {\n const len = readU32(bytes, p);\n const t = String.fromCharCode(bytes[p + 4], bytes[p + 5], bytes[p + 6], bytes[p + 7]);\n const body = bytes.subarray(p + 8, p + 8 + len);\n if (p + 12 + len > bytes.length) throw new Error('Truncated PNG.');\n if (t === 'IHDR') {\n width = readU32(body, 0);\n height = readU32(body, 4);\n depth = body[8];\n type = body[9];\n interlace = body[12];\n } else if (t === 'PLTE') palette = body;\n else if (t === 'tRNS') trns = body;\n else if (t === 'IDAT') idat.push(body);\n else if (t === 'IEND') break;\n p += 12 + len;\n }\n if (!width || !height) throw new Error('PNG has no image header.');\n if (interlace) throw new Error('Interlaced PNGs are not supported here; decode them with a canvas.');\n const maxPixels = options.maxPixels ?? 1 << 28;\n if (width * height > maxPixels) throw new Error(`PNG is too large (${width}×${height}).`);\n const samples = type === 0 ? 1 : type === 2 ? 3 : type === 3 ? 1 : type === 4 ? 2 : type === 6 ? 4 : 0;\n if (!samples || ![1, 2, 4, 8, 16].includes(depth)) throw new Error(`Unsupported PNG format (type ${type}, depth ${depth}).`);\n let total = 0;\n for (const c of idat) total += c.length;\n const z = new Uint8Array(total);\n let o = 0;\n for (const c of idat) {\n z.set(c, o);\n o += c.length;\n }\n const bitsPerPixel = samples * depth;\n const stride = Math.ceil((width * bitsPerPixel) / 8);\n const raw = inflate(z, { maxOutput: (stride + 1) * height, sizeHint: (stride + 1) * height });\n if (raw.length < (stride + 1) * height) throw new Error('PNG image data is truncated.');\n const bpp = Math.max(1, bitsPerPixel >> 3);\n const lines = new Uint8Array(stride * height);\n for (let y = 0; y < height; y++) {\n const f = raw[y * (stride + 1)];\n const src = y * (stride + 1) + 1;\n const dst = y * stride;\n const up = dst - stride;\n for (let x = 0; x < stride; x++) {\n const v = raw[src + x];\n const a = x >= bpp ? lines[dst + x - bpp] : 0;\n const b = y > 0 ? lines[up + x] : 0;\n const c = x >= bpp && y > 0 ? lines[up + x - bpp] : 0;\n let r: number;\n switch (f) {\n case 0: r = v; break;\n case 1: r = v + a; break;\n case 2: r = v + b; break;\n case 3: r = v + ((a + b) >> 1); break;\n case 4: r = v + paeth(a, b, c); break;\n default: throw new Error(`Invalid PNG filter ${f}.`);\n }\n lines[dst + x] = r & 255;\n }\n }\n const sample = (y: number, i: number): number => {\n // The i-th sample of row y, scaled to 8 bits.\n const row = y * stride;\n if (depth === 8) return lines[row + i];\n if (depth === 16) return lines[row + i * 2];\n const perByte = 8 / depth;\n const byte = lines[row + Math.floor(i / perByte)];\n const shift = 8 - depth * ((i % perByte) + 1);\n const v = (byte >> shift) & ((1 << depth) - 1);\n return type === 3 ? v : Math.round((v * 255) / ((1 << depth) - 1));\n };\n const gray = options.keepGray && type === 0 && !trns;\n const channels: 1 | 4 = gray ? 1 : 4;\n const out = new Uint8ClampedArray(width * height * channels);\n const trnsGray = trns && type === 0 ? (depth === 16 ? trns[0] : sample16(trns, 0, depth)) : -1;\n const trnsRgb = trns && type === 2 ? [0, 1, 2].map((k) => (depth === 16 ? trns![k * 2] : sample16(trns!, k, depth))) : null;\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const d = (y * width + x) * channels;\n if (type === 0) {\n const v = sample(y, x);\n if (gray) out[d] = v;\n else {\n out[d] = out[d + 1] = out[d + 2] = v;\n out[d + 3] = v === trnsGray && trns ? 0 : 255;\n }\n } else if (type === 2) {\n const r = sample(y, x * 3);\n const g = sample(y, x * 3 + 1);\n const b = sample(y, x * 3 + 2);\n out[d] = r;\n out[d + 1] = g;\n out[d + 2] = b;\n out[d + 3] = trnsRgb && r === trnsRgb[0] && g === trnsRgb[1] && b === trnsRgb[2] ? 0 : 255;\n } else if (type === 3) {\n const i = sample(y, x);\n if (!palette || i * 3 + 2 >= palette.length) throw new Error('PNG palette index out of range.');\n out[d] = palette[i * 3];\n out[d + 1] = palette[i * 3 + 1];\n out[d + 2] = palette[i * 3 + 2];\n out[d + 3] = trns && i < trns.length ? trns[i] : 255;\n } else if (type === 4) {\n const v = sample(y, x * 2);\n out[d] = out[d + 1] = out[d + 2] = v;\n out[d + 3] = sample(y, x * 2 + 1);\n } else {\n out[d] = sample(y, x * 4);\n out[d + 1] = sample(y, x * 4 + 1);\n out[d + 2] = sample(y, x * 4 + 2);\n out[d + 3] = sample(y, x * 4 + 3);\n }\n }\n }\n return { width, height, channels, data: out };\n}\n\n/** A tRNS sample (stored as 16-bit big endian) scaled like the image samples. */\nfunction sample16(trns: Uint8Array, k: number, depth: number): number {\n const v = (trns[k * 2] << 8) | trns[k * 2 + 1];\n if (depth === 8) return v & 255;\n return Math.round(((v & ((1 << depth) - 1)) * 255) / ((1 << depth) - 1));\n}\n\n// ---------------------------------------------------------------------------\n// Base64 / data URLs\n// ---------------------------------------------------------------------------\n\nconst B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\nlet B64_INV: Int16Array | null = null;\n\nexport function bytesToBase64(bytes: Uint8Array): string {\n const parts: string[] = [];\n const CHUNK = 3 * 4096;\n for (let start = 0; start < bytes.length; start += CHUNK) {\n const end = Math.min(bytes.length, start + CHUNK);\n let s = '';\n let i = start;\n for (; i + 2 < end; i += 3) {\n const n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];\n s += B64[n >> 18] + B64[(n >> 12) & 63] + B64[(n >> 6) & 63] + B64[n & 63];\n }\n if (i < end) {\n const n = (bytes[i] << 16) | (i + 1 < end ? bytes[i + 1] << 8 : 0);\n s += B64[n >> 18] + B64[(n >> 12) & 63] + (i + 1 < end ? B64[(n >> 6) & 63] : '=') + '=';\n }\n parts.push(s);\n }\n return parts.join('');\n}\n\nexport function base64ToBytes(text: string): Uint8Array {\n if (!B64_INV) {\n B64_INV = new Int16Array(128).fill(-1);\n for (let i = 0; i < 64; i++) B64_INV[B64.charCodeAt(i)] = i;\n B64_INV['-'.charCodeAt(0)] = 62;\n B64_INV['_'.charCodeAt(0)] = 63;\n }\n const inv = B64_INV;\n const clean = text.replace(/[\\s=]/g, '');\n const out = new Uint8Array(Math.floor((clean.length * 3) / 4));\n let bits = 0;\n let acc = 0;\n let o = 0;\n for (let i = 0; i < clean.length; i++) {\n const c = clean.charCodeAt(i);\n const v = c < 128 ? inv[c] : -1;\n if (v < 0) throw new Error('Invalid base64 data.');\n acc = (acc << 6) | v;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[o++] = (acc >> bits) & 255;\n }\n }\n return out.subarray(0, o);\n}\n\n/** The bytes and MIME type of a `data:` URL (base64 or percent-encoded). */\nexport function parseDataUrl(url: string): { mime: string; bytes: Uint8Array } | null {\n const m = /^data:([^;,]*)((?:;[^;,]*)*?),/i.exec(url);\n if (!m) return null;\n const body = url.slice(m[0].length);\n const isBase64 = /;base64/i.test(m[2]);\n if (isBase64) return { mime: m[1] || 'text/plain', bytes: base64ToBytes(body) };\n const text = decodeURIComponent(body);\n const bytes = new Uint8Array(text.length);\n for (let i = 0; i < text.length; i++) bytes[i] = text.charCodeAt(i) & 255;\n return { mime: m[1] || 'text/plain', bytes };\n}\n\nexport function pngDataUrl(width: number, height: number, data: ArrayLike<number>, channels: 1 | 4 = 4): string {\n return `data:image/png;base64,${bytesToBase64(encodePng(width, height, data, channels))}`;\n}\n"]}
|