@pixerate/editor 0.97.0 → 0.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/THIRD_PARTY_NOTICES.md +4 -2
  2. package/dist/animation-editor/index.cjs +55 -53
  3. package/dist/animation-editor/index.cjs.map +1 -1
  4. package/dist/animation-editor/index.d.cts +3 -2
  5. package/dist/animation-editor/index.d.ts +3 -2
  6. package/dist/animation-editor/index.js +11 -9
  7. package/dist/animation-editor/index.js.map +1 -1
  8. package/dist/chunk-DWGZUSES.cjs +694 -0
  9. package/dist/chunk-DWGZUSES.cjs.map +1 -0
  10. package/dist/chunk-GX332W2C.js +181 -0
  11. package/dist/chunk-GX332W2C.js.map +1 -0
  12. package/dist/{chunk-7MD7C6XQ.cjs → chunk-JF5PXN7W.cjs} +96 -744
  13. package/dist/chunk-JF5PXN7W.cjs.map +1 -0
  14. package/dist/chunk-KKREERP2.js +694 -0
  15. package/dist/chunk-KKREERP2.js.map +1 -0
  16. package/dist/chunk-MUUXNRU4.cjs +181 -0
  17. package/dist/chunk-MUUXNRU4.cjs.map +1 -0
  18. package/dist/{chunk-OEVSVA6V.js → chunk-WIR377F6.js} +788 -22
  19. package/dist/chunk-WIR377F6.js.map +1 -0
  20. package/dist/{chunk-4AB5BHYK.cjs → chunk-WT6ZAWJP.cjs} +788 -22
  21. package/dist/chunk-WT6ZAWJP.cjs.map +1 -0
  22. package/dist/{chunk-4JLB2USO.js → chunk-YGZNGDIT.js} +45 -693
  23. package/dist/chunk-YGZNGDIT.js.map +1 -0
  24. package/dist/image-editor/index.cjs +1280 -299
  25. package/dist/image-editor/index.cjs.map +1 -1
  26. package/dist/image-editor/index.d.cts +304 -5
  27. package/dist/image-editor/index.d.ts +304 -5
  28. package/dist/image-editor/index.js +991 -10
  29. package/dist/image-editor/index.js.map +1 -1
  30. package/dist/raster/index.cjs +23 -2
  31. package/dist/raster/index.cjs.map +1 -1
  32. package/dist/raster/index.d.cts +98 -2
  33. package/dist/raster/index.d.ts +98 -2
  34. package/dist/raster/index.js +23 -2
  35. package/dist/{svg-ymShkNtu.d.cts → svg-C9r284x8.d.cts} +6 -9
  36. package/dist/{svg-Dh17jCyA.d.ts → svg-MVKBqwD4.d.ts} +6 -9
  37. package/dist/types-CwMPoRMU.d.cts +40 -0
  38. package/dist/types-CwMPoRMU.d.ts +40 -0
  39. package/dist/vector/index.cjs +490 -554
  40. package/dist/vector/index.cjs.map +1 -1
  41. package/dist/vector/index.d.cts +154 -292
  42. package/dist/vector/index.d.ts +154 -292
  43. package/dist/vector/index.js +91 -155
  44. package/dist/vector/index.js.map +1 -1
  45. package/dist/warp-B8rAguzs.d.ts +165 -0
  46. package/dist/warp-nYs2DPrx.d.cts +165 -0
  47. package/package.json +1 -1
  48. package/skills/pixerate-image-editor/SKILL.md +49 -3
  49. package/dist/chunk-4AB5BHYK.cjs.map +0 -1
  50. package/dist/chunk-4JLB2USO.js.map +0 -1
  51. package/dist/chunk-7MD7C6XQ.cjs.map +0 -1
  52. package/dist/chunk-OEVSVA6V.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-WT6ZAWJP.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/transform.ts","../src/raster/zlib.ts","../src/raster/png.ts"],"names":["a","wy","d","paint","clamp01","blend","fill","i"],"mappings":"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;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;AVqpIqE;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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-WT6ZAWJP.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 /**\n * `false` unlinks the mask from its layer (Photoshop's chain icon): moving or transforming the\n * layer's pixels leaves the mask where it is. Missing = linked.\n */\n linked?: 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 linked 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 // Pigment mixing is not a B(Cb, Cs) mode, and effects paint through one.\n if (r[key] === 'pigment') fail(`${path}.${key}`, 'pigment is a layer blend mode only; effects cannot use it.');\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, mixPigmentRgb, 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/**\n * Fixed-point scale of the blur's running sums. Inputs are rounded to multiples of 1 / BLUR_FIX, so\n * the sums are of integers and exact (|sum| stays far below 2^53): a value does not depend on where\n * the line starts, which lets effect maps be rebuilt over a window and patched in.\n */\nconst BLUR_FIX = 2 ** 32;\n\nfunction tentLine(src: Float64Array, dst: Float64Array, bw: number, mid: Float64Array): void {\n const [r] = boxGeom(bw);\n const m = mid.subarray(0, src.length + 2 * r);\n for (let i = 0; i < src.length; i++) src[i] = Math.round(src[i] * BLUR_FIX);\n boxLine(src, -r, m, bw);\n for (let i = 0; i < m.length; i++) m[i] = Math.round(m[i]);\n boxLine(m, r, dst, bw);\n // Flush what would read as coverage at a float's noise level (noise would speckle it).\n for (let i = 0; i < dst.length; i++) {\n const v = dst[i] / BLUR_FIX;\n dst[i] = Math.abs(v) < BLUR_EPS ? 0 : v;\n }\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/** Pixel bounds (document space) of the alpha above half a step in `shape` over `rect`, or `null`. */\nfunction shapeBounds(shape: Float32Array, rect: EffectRect): EffectRect | null {\n const { width: w, height: h } = rect;\n const rowHit = (y: number) => {\n for (let x = 0, i = y * w; x < w; x++, i++) if (shape[i] > INSIDE_EPS) return true;\n return false;\n };\n let y0 = 0;\n while (y0 < h && !rowHit(y0)) y0++;\n if (y0 === h) return null;\n let y1 = h - 1;\n while (!rowHit(y1)) y1--;\n // Columns: each row only needs scanning outside the columns found so far.\n let x0 = w;\n let x1 = -1;\n for (let y = y0; y <= y1; y++) {\n const o = y * w;\n for (let x = 0; x < x0; x++) {\n if (shape[o + x] > INSIDE_EPS) {\n x0 = x;\n break;\n }\n }\n for (let x = w - 1; x > x1; x--) {\n if (shape[o + x] > INSIDE_EPS) {\n x1 = x;\n break;\n }\n }\n }\n return { x: rect.x + x0, y: rect.y + y0, width: x1 - x0 + 1, height: y1 - y0 + 1 };\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 const bounds = shapeBounds(shape, rect);\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\nconst rectArea = (r: EffectRect) => r.width * r.height;\n\nfunction clipRect(a: EffectRect, b: EffectRect): EffectRect | null {\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: x1 - x0, height: y1 - y0 } : null;\n}\n\nconst growRect = (r: EffectRect, m: number): EffectRect => ({ x: r.x - m, y: r.y - m, width: r.width + 2 * m, height: r.height + 2 * m });\n\nfunction unionRect(a: EffectRect, b: EffectRect): EffectRect {\n const x0 = Math.min(a.x, b.x);\n const y0 = Math.min(a.y, b.y);\n return { x: x0, y: y0, width: Math.max(a.x + a.width, b.x + b.width) - x0, height: Math.max(a.y + a.height, b.y + b.height) - y0 };\n}\n\n/** The parts of `r` outside `cut`, as up to four rectangles. */\nfunction rectMinus(r: EffectRect, cut: EffectRect): EffectRect[] {\n const i = clipRect(r, cut);\n if (!i) return [r];\n const parts: EffectRect[] = [\n { x: r.x, y: r.y, width: r.width, height: i.y - r.y },\n { x: r.x, y: i.y + i.height, width: r.width, height: r.y + r.height - (i.y + i.height) },\n { x: r.x, y: i.y, width: i.x - r.x, height: i.height },\n { x: i.x + i.width, y: i.y, width: r.x + r.width - (i.x + i.width), height: i.height },\n ];\n return parts.filter((p) => p.width > 0 && p.height > 0);\n}\n\n/** Copies the part `a` of `src` (over `from`) into `dst` (over `to`). */\nfunction pasteMap(dst: Float32Array, to: EffectRect, src: Float32Array, from: EffectRect, a: EffectRect): void {\n for (let y = a.y; y < a.y + a.height; y++) {\n const s = (y - from.y) * from.width + (a.x - from.x);\n dst.set(src.subarray(s, s + a.width), (y - to.y) * to.width + (a.x - to.x));\n }\n}\n\n/**\n * `prev` brought up to date for a shape over `rect` that differs from the one it was built from only\n * inside `changed` (document rects), or `null` when a full {@link buildEffectMaps} is the better\n * deal (the change covers much of the region) or the effects are not local\n * ({@link effectsAreLocal}). `items` must be the ones `prev` was built with, reaching at most\n * `margin` px, and `shapeOver(r)` returns the new shape over `r`.\n *\n * Every map pixel within `margin` of a change, or outside `prev.rect`, is rebuilt from the shape\n * within `margin` of it (a window of the change grown by twice the margin) and pasted in; the rest\n * is kept. A local map pixel depends only on the shape within the margin and the blur sums exactly,\n * so the result paints exactly what a full build over `rect` would. `prev`'s buffers are reused\n * when `rect` is unchanged.\n */\nexport function patchEffectMaps(\n prev: EffectMaps,\n items: LayerEffect[],\n rect: EffectRect,\n changed: readonly EffectRect[],\n margin: number,\n shapeOver: (r: EffectRect) => Float32Array,\n ctx: EffectContext\n): EffectMaps | null {\n if (!effectsAreLocal(items) || rectArea(rect) <= 0) return null;\n // Map pixels to rebuild: within the margin of a change, and new parts of the region.\n let areas: EffectRect[] = [];\n for (const c of changed) {\n const a = clipRect(growRect(c, margin), rect);\n if (a) areas.push(a);\n }\n areas.push(...rectMinus(rect, prev.rect));\n // One window for areas whose windows overlap.\n for (let merged = true; merged; ) {\n merged = false;\n for (let i = 0; i < areas.length && !merged; i++) {\n for (let j = i + 1; j < areas.length; j++) {\n if (clipRect(growRect(areas[i], 2 * margin), areas[j])) {\n areas[i] = unionRect(areas[i], areas[j]);\n areas.splice(j, 1);\n merged = true;\n break;\n }\n }\n }\n }\n const windows = areas.map((a) => clipRect(growRect(a, margin), rect)!);\n if (windows.reduce((n, w) => n + rectArea(w), 0) > rectArea(rect) / 2) return null;\n\n const same = rect.x === prev.rect.x && rect.y === prev.rect.y && rect.width === prev.rect.width && rect.height === prev.rect.height;\n const take = (v: Float32Array) => (same ? v : crop(v, prev.rect, rect, 0));\n const shape = take(prev.shape);\n const per = prev.per.map((maps) => maps.map(take));\n const din = prev.din && take(prev.din);\n const dout = prev.dout && take(prev.dout);\n const doutExact = prev.doutExact && take(prev.doutExact);\n areas.forEach((a, k) => {\n const w = windows[k];\n const part = buildEffectMaps(items, shapeOver(w), w, ctx);\n pasteMap(shape, rect, part.shape, w, a);\n per.forEach((maps, i) => maps.forEach((m, j) => pasteMap(m, rect, part.per[i][j], w, a)));\n if (din) pasteMap(din, rect, part.din!, w, a);\n if (dout) pasteMap(dout, rect, part.dout!, w, a);\n if (doutExact) pasteMap(doutExact, rect, part.doutExact!, w, a);\n });\n let bytes = shape.byteLength;\n for (const maps of per) for (const m of maps) bytes += m.byteLength;\n for (const d of [din, dout, doutExact]) if (d) bytes += d.byteLength;\n return { rect, items, bounds: shapeBounds(shape, rect), shape, per, bevelPaint: prev.bevelPaint, din, dout, doutExact, frames: new Map(), 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];\nconst rgbScratchM = [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 if (mode === 'pigment') {\n // Not B(Cb, Cs): the colours mix like paint by amount, `sa / ao` of the source (as blendTile).\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 mixPigmentRgb(rgbScratchB, rgbScratchS, sa / ao, rgbScratchM);\n dst[i] = rgbScratchM[0];\n dst[i + 1] = rgbScratchM[1];\n dst[i + 2] = rgbScratchM[2];\n dst[i + 3] = ao;\n return;\n }\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 * When only some of those tiles changed and every effect is local, the maps are rebuilt only\n * within the margin of the change (narrowed by `dirty` when it covers the change) and patched in.\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, effectReach, effectsFingerprint, effectsMargin, hasActiveEffects, MAX_EFFECT_REACH, type GlobalLight, type LayerEffects } from './effects/model';\nimport { builtinPattern, PatternTile, type EffectPattern } from './effects/patterns';\nimport { activeEffectItems, buildEffectMaps, compositeLayerEffects, effectsAreLocal, patchEffectMaps, 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 (grown by the effects' reach),\n * tiles they miss keep their pixels, and effect maps are rebuilt only around them. Changes outside\n * them would 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\n/** A layer's cached effect maps: built for `key` over `region` from the shape tiles `tiles`. */\ninterface EffectEntry {\n key: string;\n region: RasterRect;\n tiles: Map<TileKey, Sig>;\n maps: EffectMaps;\n /** The call that last validated it. */\n stamp: number;\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 /** Layers whose effect maps were built over their whole region. */\n effectsBuilt: number;\n /** Layers whose effect maps were rebuilt only around a change and patched in. */\n effectsPatched: 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, EffectEntry>();\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, effectsBuilt: 0, effectsPatched: 0 };\n private hint = -1;\n /** The `dirty` rectangles of the current call, as passed. */\n private callDirty: RasterRect[] | null = null;\n private callStats: CompositeStats = this.lastStats;\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 this.callDirty = options.dirty ?? null;\n const stats: CompositeStats = { tiles: 0, cached: 0, stepsRun: 0, effectsBuilt: 0, effectsPatched: 0 };\n this.callStats = stats;\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, effectsBuilt: 0, effectsPatched: 0 };\n this.callStats = stats;\n this.hint = -1;\n this.callDirty = null;\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) || this.changedOnlyNearby(steps, sigs, entry, dirty)) {\n entry.sigs = sigs;\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 /**\n * Whether a tile with no part in this call's (grown) `dirty` rectangles changed only in what local\n * effects read beyond it: every changed step is a pixel layer with local effects whose changes stay\n * inside `dirty` (and so are farther than its margin), which leaves the tile's pixels as they were.\n */\n private changedOnlyNearby(steps: Step<L>[], sigs: Sig[], entry: TileEntry<L>, dirty: RasterRect | null): boolean {\n if (this.callDirty === null || dirty || entry.stamp !== this.stamp - 1 || sigs.length !== entry.sigs.length) return false;\n return steps.every((step, i) => {\n if (sameSig(entry.sigs[i], sigs[i])) return true;\n if (step.kind !== 'content' || entry.sigs[i].key !== sigs[i].key) return false;\n const { layer, clipped } = step;\n return this.hasEffects(layer) && effectsAreLocal(activeEffectItems(layer.effects)) && this.dirtyCoversShape(layer) && clipped.every((c) => this.dirtyCoversShape(c));\n });\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 signature of a layer's shape (its surface tiles and mask refs) per tile of `region`. */\n private shapeSigs(layer: L, region: RasterRect): Map<TileKey, Sig> {\n const sigs = new Map<TileKey, Sig>();\n if (region.width <= 0 || region.height <= 0) return sigs;\n const steps = isGroup(layer) ? this.buildSteps((layer.children ?? []) as L[]) : null;\n for (const t of tilesInRect(region)) {\n sigs.set(tileKey(t.tx, t.ty), steps ? this.plainGroupSig(layer, steps, t.tx, t.ty) : this.plainContentSig(layer, t.tx, t.ty));\n }\n return sigs;\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 for (const sig of this.shapeSigs(layer, region).values()) for (const r of sig.refs) refs.push(r);\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();\n const tiles = this.shapeSigs(layer, region);\n const hit = this.effectCache.get(layer.id);\n this.effectCache.delete(layer.id);\n let entry: EffectEntry;\n if (hit && hit.key === key && sameRect(hit.region, region) && [...tiles].every(([k, sig]) => sameSig(hit.tiles.get(k), sig))) {\n entry = hit;\n } else {\n const items = activeEffectItems(layer.effects);\n const ctx = this.effectContext();\n let maps: EffectMaps | null = null;\n if (hit && hit.key === key && items.every((e) => effectReach(e) <= MAX_EFFECT_REACH)) {\n const changed = this.shapeChanges(layer, hit, tiles, region);\n maps = patchEffectMaps(hit.maps, items, region, changed, effectsMargin(layer.effects), (r) => this.layerShape(layer, r), ctx);\n }\n if (maps) this.callStats.effectsPatched++;\n else this.callStats.effectsBuilt++;\n entry = { key, region, tiles, maps: maps ?? buildEffectMaps(items, this.layerShape(layer, region), region, ctx), stamp: 0 };\n }\n entry.stamp = this.stamp;\n this.effectCache.set(layer.id, entry);\n if (entry !== hit) {\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, EffectEntry];\n bytes -= e.maps.bytes;\n this.effectCache.delete(oldest);\n }\n }\n this.effectMemo.set(layer, { stamp: this.stamp, maps: entry.maps });\n return entry.maps;\n }\n\n /**\n * Where a layer's shape may differ from the one `prev` was built from: the tiles whose signature\n * changed (a tile new to the region that reads nothing did not), narrowed to this call's `dirty`\n * rectangles when `prev` is from the previous call and they cover every change of the shape.\n */\n private shapeChanges(layer: L, prev: EffectEntry, tiles: Map<TileKey, Sig>, region: RasterRect): RasterRect[] {\n const out: RasterRect[] = [];\n for (const { tx, ty } of tilesInRect(region)) {\n const sig = tiles.get(tileKey(tx, ty))!;\n const old = prev.tiles.get(tileKey(tx, ty));\n if (old ? sameSig(old, sig) : sig.refs.every((r) => r === null || r === 0)) continue;\n const r = intersectRect({ x: tx * TILE, y: ty * TILE, width: TILE, height: TILE }, region);\n if (r.width > 0 && r.height > 0) out.push(r);\n }\n const dirty = this.callDirty;\n if (!dirty || prev.stamp !== this.stamp - 1 || !this.dirtyCoversShape(layer)) return out;\n const narrowed: RasterRect[] = [];\n for (const r of out) {\n for (const d of dirty) {\n const a = intersectRect(r, { x: Math.floor(d.x), y: Math.floor(d.y), width: Math.ceil(d.x + d.width) - Math.floor(d.x), height: Math.ceil(d.y + d.height) - Math.floor(d.y) });\n if (a.width > 0 && a.height > 0) narrowed.push(a);\n }\n }\n return narrowed;\n }\n\n /**\n * Whether a change to a layer stays inside the `dirty` rectangles that name it: a pixel layer\n * without filters or a feathered or parametric mask (those spread a change beyond it).\n */\n private dirtyCoversShape(layer: L): boolean {\n if (!isPixel(layer) || this.profile.pixelFilters?.(layer)) return false;\n const mask = layer.mask;\n return !mask || mask.enabled === false || (!mask.source && !((mask.feather ?? 0) > 0));\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 sameRect(a: RasterRect, b: RasterRect): boolean {\n return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;\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","// Portions ported from photocraft/crates/algo/src/transform.rs and crates/algo/src/warp.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Resampling tiled surfaces through geometric maps: Free Transform (scale, rotate, skew, distort,\n * perspective: one projective map) and Warp (any forward map, such as a Bézier mesh).\n *\n * - Inverse mapping per destination tile: each destination tile reads only the source window its\n * pixels map into, so memory stays bounded by one tile's footprint, and the result is a new\n * copy-on-write surface (an identity or a whole-pixel move returns the source, or shares its\n * tiles).\n * - Nearest, bilinear and Photoshop's Bicubic (the Keys cubic at a = −0.75). Colour is sampled\n * premultiplied by alpha, so partially transparent edges don't pick up the colour of the\n * transparent pixels around them; outside the source everything is transparent.\n * - Large reductions with Bicubic are pre-filtered: the source is box-averaged by whole factors\n * first and the cubic widened by the rest, so shrinking a layer doesn't alias. Bilinear and\n * nearest sample the source directly, as Photoshop's do.\n * - Masks (one channel) are warped as \"value + inside\": outside the source and outside the warped\n * area they read as the surface's default value, so a reveal-all mask stays white around a\n * transformed hole.\n * - A Distort dragged past the horizon draws only the side of the horizon most of the source maps\n * to (no mirrored copy wrapped round infinity).\n *\n * No DOM: runs in Node, tests and workers.\n */\n\nimport {\n TILE,\n TiledSurface,\n contentBounds,\n emptySurface,\n finishTile,\n readRegion,\n tileKey,\n tilesInRect,\n translateSurface,\n writeRegion,\n type RasterRect,\n type Tile,\n type TileKey,\n} from './surface';\nimport {\n applyHomography,\n cornerBounds,\n homographyFrontSide,\n homographyW,\n invertHomography,\n isIdentityHomography,\n multiplyHomographies,\n boxQuad,\n} from '../geom/homography';\nimport type { Homography, Vec2 } from '../geom/types';\n\n/** Resampling of a transform (Photoshop's interpolation menu). */\nexport type Interpolation = 'nearest' | 'bilinear' | 'bicubic';\n\nexport const INTERPOLATIONS: readonly Interpolation[] = ['nearest', 'bilinear', 'bicubic'];\n\nexport function isInterpolation(value: unknown): value is Interpolation {\n return typeof value === 'string' && (INTERPOLATIONS as readonly string[]).includes(value);\n}\n\n/**\n * The largest result (pixels) one resample may build: about 11 600 × 11 600. Only the size of the\n * result counts, not where it lands. A scale past it is refused rather than clipped.\n */\nexport const MAX_TRANSFORM_PIXELS = 1 << 27;\n\n/** A transform whose result would be too large; the message gives the size and the limit. */\nexport class TransformTooLargeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'TransformTooLargeError';\n }\n}\n\nexport interface ResampleOptions {\n /** Default `bicubic`. */\n interpolation?: Interpolation;\n /** The source region to transform (default: the surface's content bounds). Pixels outside it are not moved. */\n source?: RasterRect;\n /** Keep only destination pixels inside this rectangle (a preview of the visible area). */\n clip?: RasterRect;\n /**\n * Preview speed-up: sample once per `step × step` block of destination pixels and fill the\n * block (a power of two, 1..64; default 1, every pixel).\n */\n step?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Kernels\n// ---------------------------------------------------------------------------\n\nconst KEYS_A = -0.75;\n\n/**\n * Photoshop's Bicubic: the Keys cubic at a = −0.75 (Catmull-Rom is −0.5). Measured on Photoshop\n * 27.11's Free Transform by photocraft: a 200 % step edge matches within 0.5 levels.\n */\nexport function keysWeight(x: number): number {\n x = Math.abs(x);\n if (x < 1) return ((KEYS_A + 2) * x - (KEYS_A + 3)) * x * x + 1;\n if (x < 2) return KEYS_A * (((x - 5) * x + 8) * x - 4);\n return 0;\n}\n\n/** Snaps a sample position within float noise of the pixel grid onto it, so an identity is exact. */\nconst snap = (t: number) => (Math.abs(t - Math.round(t)) < 1e-7 ? Math.round(t) : t);\n\n/** Most taps of a widened cubic along one axis (the residual widening is below 4 after pre-reduction). */\nconst MAX_TAPS = 20;\n\n/** First tap index and tap count of the last `cubicTaps` call (module state, so sampling allocates nothing). */\nlet tapI0 = 0;\nlet tapN = 0;\n\n/** Taps of the cubic at `u` (pixel-index units), widened by `s` ≥ 1: sets `tapI0` / `tapN` and fills `w` with normalised weights. */\nfunction cubicTaps(u: number, s: number, w: Float64Array): void {\n if (s <= 1.0001) {\n const f = Math.floor(u);\n const t = u - f;\n w[0] = keysWeight(1 + t);\n w[1] = keysWeight(t);\n w[2] = keysWeight(1 - t);\n w[3] = keysWeight(2 - t);\n tapI0 = f - 1;\n tapN = 4;\n return;\n }\n const r = 2 * s;\n const i0 = Math.ceil(u - r);\n const n = Math.min(MAX_TAPS, Math.floor(u + r) - i0 + 1);\n let sum = 0;\n for (let k = 0; k < n; k++) {\n const v = keysWeight((i0 + k - u) / s);\n w[k] = v;\n sum += v;\n }\n if (sum !== 0) for (let k = 0; k < n; k++) w[k] /= sum;\n tapI0 = i0;\n tapN = n;\n}\n\n// ---------------------------------------------------------------------------\n// Source windows\n// ---------------------------------------------------------------------------\n\n/**\n * A float copy of part of a source: premultiplied RGBA (colour 0..255 × alpha, alpha 0..1) for\n * layers, `[value, inside]` for one-channel masks (inside is 1 within the source region). Zero\n * outside the source region.\n */\ninterface Window {\n x: number;\n y: number;\n w: number;\n h: number;\n /** Values per pixel: 4 (RGBA) or 2 (mask value, inside). */\n n: 2 | 4;\n data: Float32Array;\n}\n\nfunction intersect(a: RasterRect, b: RasterRect): RasterRect | null {\n const x = Math.max(a.x, b.x);\n const y = 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 > x && y1 > y ? { x, y, width: x1 - x, height: y1 - y } : null;\n}\n\nfunction hasTilesIn(src: TiledSurface, r: RasterRect): boolean {\n for (const { tx, ty } of tilesInRect(r)) if (src.getTile(tx, ty)) return true;\n return false;\n}\n\nfunction readWindow(src: TiledSurface, srcRect: RasterRect, rect: RasterRect): Window {\n const n: 2 | 4 = src.channels === 4 ? 4 : 2;\n const data = new Float32Array(rect.width * rect.height * n);\n const win: Window = { x: rect.x, y: rect.y, w: rect.width, h: rect.height, n, data };\n const r = intersect(rect, srcRect);\n if (!r) return win;\n const px = readRegion(src, r);\n for (let y = 0; y < r.height; y++) {\n let s = y * r.width * src.channels;\n let d = ((r.y - rect.y + y) * rect.width + (r.x - rect.x)) * n;\n if (n === 4) {\n for (let x = 0; x < r.width; x++, s += 4, d += 4) {\n const a = px[s + 3] / 255;\n data[d] = px[s] * a;\n data[d + 1] = px[s + 1] * a;\n data[d + 2] = px[s + 2] * a;\n data[d + 3] = a;\n }\n } else {\n for (let x = 0; x < r.width; x++, s++, d += 2) {\n data[d] = px[s];\n data[d + 1] = 1;\n }\n }\n }\n return win;\n}\n\n/** One stored pixel, for packing samples before they are copied over a preview block. */\nconst scratch = new Uint8ClampedArray(4);\nconst scratchWord = new Uint32Array(scratch.buffer);\n\nconst wx = new Float64Array(MAX_TAPS);\nconst wy = new Float64Array(MAX_TAPS);\n\n/**\n * One sample of the window at pixel-index position (`u`, `v`) (pixel centres at integers) into\n * `acc` (premultiplied). `su`, `sv`: widening of the cubic along each source axis (≥ 1).\n */\nfunction sample(win: Window, interp: Interpolation, u: number, v: number, su: number, sv: number, acc: Float64Array): void {\n const n = win.n;\n const d = win.data;\n for (let c = 0; c < n; c++) acc[c] = 0;\n if (interp === 'nearest') {\n const ix = Math.floor(u + 0.5) - win.x;\n const iy = Math.floor(v + 0.5) - win.y;\n if (ix < 0 || iy < 0 || ix >= win.w || iy >= win.h) return;\n const o = (iy * win.w + ix) * n;\n for (let c = 0; c < n; c++) acc[c] = d[o + c];\n return;\n }\n if (interp === 'bilinear') {\n const fx = Math.floor(u);\n const fy = Math.floor(v);\n const tx = u - fx;\n const ty = v - fy;\n for (let j = 0; j < 2; j++) {\n const iy = fy + j - win.y;\n if (iy < 0 || iy >= win.h) continue;\n const kyw = j ? ty : 1 - ty;\n if (!kyw) continue;\n for (let i = 0; i < 2; i++) {\n const ix = fx + i - win.x;\n if (ix < 0 || ix >= win.w) continue;\n const k = (i ? tx : 1 - tx) * kyw;\n if (!k) continue;\n const o = (iy * win.w + ix) * n;\n for (let c = 0; c < n; c++) acc[c] += d[o + c] * k;\n }\n }\n return;\n }\n u = snap(u);\n v = snap(v);\n cubicTaps(u, su, wx);\n const x0 = tapI0 - win.x;\n const nu = tapN;\n cubicTaps(v, sv, wy);\n const y0 = tapI0 - win.y;\n const nv = tapN;\n const W = win.w;\n if (n === 4 && nu === 4 && nv === 4 && x0 >= 0 && y0 >= 0 && x0 + 4 <= W && y0 + 4 <= win.h) {\n // The common case: a plain 4 × 4 cubic fully inside the window.\n let r = 0;\n let g = 0;\n let b = 0;\n let a = 0;\n const w0 = wx[0];\n const w1 = wx[1];\n const w2 = wx[2];\n const w3 = wx[3];\n for (let j = 0; j < 4; j++) {\n const kyw = wy[j];\n if (kyw === 0) continue;\n let o = ((y0 + j) * W + x0) * 4;\n const r0 = d[o] * w0 + d[o + 4] * w1 + d[o + 8] * w2 + d[o + 12] * w3;\n const g0 = d[o + 1] * w0 + d[o + 5] * w1 + d[o + 9] * w2 + d[o + 13] * w3;\n const b0 = d[o + 2] * w0 + d[o + 6] * w1 + d[o + 10] * w2 + d[o + 14] * w3;\n const a0 = d[o + 3] * w0 + d[o + 7] * w1 + d[o + 11] * w2 + d[o + 15] * w3;\n r += r0 * kyw;\n g += g0 * kyw;\n b += b0 * kyw;\n a += a0 * kyw;\n }\n acc[0] = r;\n acc[1] = g;\n acc[2] = b;\n acc[3] = a;\n return;\n }\n for (let j = 0; j < nv; j++) {\n const kyw = wy[j];\n if (kyw === 0) continue;\n const iy = y0 + j;\n if (iy < 0 || iy >= win.h) continue;\n const row = iy * W;\n for (let i = 0; i < nu; i++) {\n const k = wx[i] * kyw;\n if (k === 0) continue;\n const ix = x0 + i;\n if (ix < 0 || ix >= W) continue;\n const o = (row + ix) * n;\n if (n === 4) {\n acc[0] += d[o] * k;\n acc[1] += d[o + 1] * k;\n acc[2] += d[o + 2] * k;\n acc[3] += d[o + 3] * k;\n } else {\n acc[0] += d[o] * k;\n acc[1] += d[o + 1] * k;\n }\n }\n }\n}\n\n/**\n * Writes one premultiplied sample into a tile buffer at byte offset `o`: straight RGBA for layers,\n * the value flattened onto the default for masks. False when it adds nothing.\n */\nfunction store(out: Uint8ClampedArray, o: number, acc: Float64Array, ch: 1 | 4, def: number): boolean {\n if (ch === 4) {\n const al = acc[3] > 1 ? 1 : acc[3];\n if (!(al > 1e-6)) return false;\n out[o] = acc[0] / al;\n out[o + 1] = acc[1] / al;\n out[o + 2] = acc[2] / al;\n out[o + 3] = al * 255;\n return true;\n }\n const inside = acc[1] > 1 ? 1 : acc[1];\n if (!(inside > 1e-6)) return false;\n out[o] = acc[0] + def * (1 - inside);\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Pre-reduction (Bicubic shrinking)\n// ---------------------------------------------------------------------------\n\n/**\n * The source box-averaged by `kx × ky` blocks aligned to multiples of the factors (premultiplied\n * for RGBA), so reduced pixel (i, j) covers source pixels [i·kx, (i+1)·kx) × [j·ky, (j+1)·ky).\n * Returns the reduced surface and the reduced source rectangle. Works in strips of about one tile\n * row, so memory stays bounded.\n */\nfunction boxReduce(src: TiledSurface, srcRect: RasterRect, kx: number, ky: number): { surface: TiledSurface; rect: RasterRect } {\n const ch = src.channels;\n const rx0 = Math.floor(srcRect.x / kx);\n const ry0 = Math.floor(srcRect.y / ky);\n const rx1 = Math.ceil((srcRect.x + srcRect.width) / kx);\n const ry1 = Math.ceil((srcRect.y + srcRect.height) / ky);\n const rw = rx1 - rx0;\n const rect = { x: rx0, y: ry0, width: rw, height: ry1 - ry0 };\n let out = emptySurface(ch, src.defaultValue);\n const rowsPer = Math.max(1, Math.floor(TILE / ky));\n const area = kx * ky;\n for (let ry = ry0; ry < ry1; ry += rowsPer) {\n const rows = Math.min(rowsPer, ry1 - ry);\n const strip = { x: rx0 * kx, y: ry * ky, width: rw * kx, height: rows * ky };\n const px = readRegion(src, strip);\n const red = new Uint8ClampedArray(rw * rows * ch);\n for (let j = 0; j < rows; j++) {\n for (let i = 0; i < rw; i++) {\n if (ch === 4) {\n let a = 0;\n let r = 0;\n let g = 0;\n let b = 0;\n for (let y = 0; y < ky; y++) {\n let s = ((j * ky + y) * strip.width + i * kx) * 4;\n for (let x = 0; x < kx; x++, s += 4) {\n const al = px[s + 3];\n a += al;\n r += px[s] * al;\n g += px[s + 1] * al;\n b += px[s + 2] * al;\n }\n }\n const o = (j * rw + i) * 4;\n if (a > 0) {\n red[o] = r / a;\n red[o + 1] = g / a;\n red[o + 2] = b / a;\n red[o + 3] = a / area;\n }\n } else {\n let v = 0;\n for (let y = 0; y < ky; y++) {\n const s = (j * ky + y) * strip.width + i * kx;\n for (let x = 0; x < kx; x++) v += px[s + x];\n }\n red[j * rw + i] = v / area;\n }\n }\n }\n out = writeRegion(out, { x: rx0, y: ry, width: rw, height: rows }, red);\n }\n return { surface: out, rect };\n}\n\n/**\n * The surface box-averaged by `factor` (a whole number ≥ 1) in both directions: reduced pixel\n * (i, j) covers pixels [i·factor, (i+1)·factor) of each axis. For previews: transform the reduced\n * surface through the map composed with a scale by `factor`, so a preview at a low zoom reads a\n * fraction of the pixels.\n */\nexport function reduceSurface(src: TiledSurface, factor: number): TiledSurface {\n const k = Math.max(1, Math.floor(factor));\n const b = contentBounds(src);\n if (k === 1 || !b) return src;\n return boxReduce(src, b, k, k).surface;\n}\n\n/** Source pixels per destination pixel along each source axis at destination point (X, Y), for `inv` (destination → source). */\nfunction inverseStretch(inv: Homography, X: number, Y: number): [number, number] {\n const w = inv[6] * X + inv[7] * Y + inv[8];\n if (!(Math.abs(w) > 1e-12)) return [1, 1];\n const U = (inv[0] * X + inv[1] * Y + inv[2]) / w;\n const V = (inv[3] * X + inv[4] * Y + inv[5]) / w;\n const ux = (inv[0] - U * inv[6]) / w;\n const uy = (inv[1] - U * inv[7]) / w;\n const vx = (inv[3] - V * inv[6]) / w;\n const vy = (inv[4] - V * inv[7]) / w;\n return [Math.hypot(ux, uy), Math.hypot(vx, vy)];\n}\n\n/** Whole reduction factor for a stretch: the largest power of two ≤ `s` (1..64). */\nconst reduction = (s: number) => (s >= 2 && Number.isFinite(s) ? Math.min(64, 2 ** Math.floor(Math.log2(s))) : 1);\n\n// ---------------------------------------------------------------------------\n// Projective transforms\n// ---------------------------------------------------------------------------\n\nfunction normalizeStep(step: number | undefined): number {\n if (!step || !(step > 1)) return 1;\n return Math.min(64, 2 ** Math.floor(Math.log2(step)));\n}\n\nfunction checkBudget(dst: RasterRect, what = 'result'): void {\n if (dst.width * dst.height > MAX_TRANSFORM_PIXELS) {\n throw new TransformTooLargeError(\n `The transformed ${what} would be ${dst.width} × ${dst.height} px, more than the ${MAX_TRANSFORM_PIXELS} px limit; use a smaller scale.`\n );\n }\n}\n\n/** The new surface from `tiles` written over an empty one (default value kept). */\nfunction assemble(src: TiledSurface, tiles: Map<TileKey, Tile>): TiledSurface {\n return new TiledSurface(src.channels, tiles, src.defaultValue);\n}\n\n/** The destination rectangle `transformSurface` fills for `source` under `h`, or `null` when it is empty or unbounded. */\nexport function transformedBounds(source: RasterRect, h: Homography): RasterRect | null {\n if (!(source.width > 0 && source.height > 0) || !invertHomography(h)) return null;\n const pts = boxQuad(source).map((p) => applyHomography(h, p));\n if (pts.some((p) => !p)) return null;\n return cornerBounds(pts as Vec2[]);\n}\n\n/**\n * The surface's content inside `options.source` (default: all of it) warped by `h` (source →\n * destination, both in the surface's pixel space). Pixels outside the warped area are the default\n * (transparent for layers). Throws `TransformTooLargeError` past `MAX_TRANSFORM_PIXELS`.\n */\nexport function transformSurface(src: TiledSurface, h: Homography, options: ResampleOptions = {}): TiledSurface {\n const interp = options.interpolation ?? 'bicubic';\n const ch = src.channels;\n const def = src.defaultValue;\n const empty = emptySurface(ch, def);\n const given = options.source;\n const step = normalizeStep(options.step);\n if (isIdentityHomography(h, 1e-12) && !given && !options.clip) return src;\n // A whole-pixel move is exact and shares tiles when it is a multiple of the tile size.\n const dx = h[2] / h[8];\n const dy = h[5] / h[8];\n if (!given && !options.clip && isIdentityHomography([h[0], h[1], 0, h[3], h[4], 0, h[6], h[7], h[8]], 1e-12) && Math.abs(dx - Math.round(dx)) < 1e-9 && Math.abs(dy - Math.round(dy)) < 1e-9) {\n return translateSurface(src, Math.round(dx), Math.round(dy));\n }\n let srcRect = given ? intersect(given, given) : contentBounds(src);\n if (!srcRect) return empty;\n let H = h;\n let S = src;\n // Pre-reduce for large downscales (bicubic alone aliases below ~50 %): box-average by whole\n // factors so the cubic, widened by the rest, samples at most about 2:1.\n if (interp === 'bicubic') {\n const inv0 = invertHomography(H);\n const c = applyHomography(H, { x: srcRect.x + srcRect.width / 2, y: srcRect.y + srcRect.height / 2 });\n if (inv0 && c) {\n const [su, sv] = inverseStretch(inv0, c.x, c.y);\n const kx = reduction(su);\n const ky = reduction(sv);\n if (kx > 1 || ky > 1) {\n const red = boxReduce(src, srcRect, kx, ky);\n S = red.surface;\n srcRect = red.rect;\n H = multiplyHomographies([kx, 0, 0, 0, ky, 0, 0, 0, 1], H);\n }\n }\n }\n const inv = invertHomography(H);\n if (!inv) return empty;\n const sr = srcRect;\n // While the forward w keeps one sign over the source (plus the margin the sampler reads), every\n // sampled pixel lies at least `wLo` from the inverse horizon, so clipping a tile there keeps its\n // footprint exact and bounded even when a steep Distort brings that horizon into the tile.\n const fwd = [\n { x: sr.x - 1, y: sr.y - 1 },\n { x: sr.x + sr.width + 1, y: sr.y - 1 },\n { x: sr.x + sr.width + 1, y: sr.y + sr.height + 1 },\n { x: sr.x - 1, y: sr.y + sr.height + 1 },\n ].map((p) => homographyW(H, p));\n const side = fwd.every((w) => w > 0) ? 1 : fwd.every((w) => w < 0) ? -1 : 0;\n const wLo = 1 / fwd.reduce((m, w) => Math.max(m, Math.abs(w)), 0);\n // A source straddling the horizon draws only its front side.\n const front = side === 0 ? homographyFrontSide(H, sr) : 0;\n const corners = boxQuad(sr).map((p) => applyHomography(H, p));\n const bounds = corners.every(Boolean) ? cornerBounds(corners as Vec2[]) : null;\n let dst: RasterRect | null;\n if (options.clip && bounds && side !== 0) dst = intersect(bounds, options.clip);\n else dst = options.clip ?? bounds;\n if (!dst) return empty;\n if (!options.clip) checkBudget(dst);\n const affine = Math.abs(inv[6]) < 1e-15 && Math.abs(inv[7]) < 1e-15;\n const [su0, sv0] = inverseStretch(inv, dst.x + dst.width / 2, dst.y + dst.height / 2);\n const widen = (s: number) => (interp === 'bicubic' ? Math.min(4, Math.max(1, s)) : 1);\n const maxWiden = interp === 'bicubic' ? 4 : 1;\n const pad = 3 + Math.ceil(2 * maxWiden);\n const margin = interp === 'bicubic' ? Math.max(1, widen(su0), widen(sv0)) : 1;\n const tiles = new Map<TileKey, Tile>();\n const acc = new Float64Array(4);\n const sx0 = sr.x;\n const sy0 = sr.y;\n const sx1 = sr.x + sr.width - 1;\n const sy1 = sr.y + sr.height - 1;\n for (const { tx, ty } of tilesInRect(dst)) {\n const t = intersect({ x: tx * TILE, y: ty * TILE, width: TILE, height: TILE }, dst);\n if (!t) continue;\n // Source footprint of this tile (inverse-mapped corners of its sampled part), padded for the filter.\n const tc: Vec2[] = [\n { x: t.x, y: t.y },\n { x: t.x + t.width, y: t.y },\n { x: t.x + t.width, y: t.y + t.height },\n { x: t.x, y: t.y + t.height },\n ];\n const wc = tc.map((p) => homographyW(inv, p));\n let poly: Vec2[] = tc;\n if (side !== 0) {\n // Sutherland-Hodgman against side · w ≥ wLo.\n const dd = wc.map((w) => side * w - wLo);\n poly = [];\n for (let i = 0; i < 4; i++) {\n const p = tc[i];\n const q = tc[(i + 1) % 4];\n const dp = dd[i];\n const dq = dd[(i + 1) % 4];\n if (dp >= 0) poly.push(p);\n if (dp >= 0 !== dq >= 0) {\n const k = dp / (dp - dq);\n poly.push({ x: p.x + (q.x - p.x) * k, y: p.y + (q.y - p.y) * k });\n }\n }\n }\n if (!poly.length) continue;\n const straddles = side === 0 && wc.some((w) => w <= 0) && wc.some((w) => w >= 0);\n let foot: RasterRect | null;\n if (straddles) foot = sr;\n else {\n const fp = poly.map((p) => applyHomography(inv, p));\n if (fp.some((p) => !p)) foot = sr;\n else {\n const xs = (fp as Vec2[]).map((p) => p.x);\n const ys = (fp as Vec2[]).map((p) => p.y);\n const fx0 = Math.floor(Math.min(...xs)) - pad;\n const fy0 = Math.floor(Math.min(...ys)) - pad;\n const fx1 = Math.ceil(Math.max(...xs)) + pad;\n const fy1 = Math.ceil(Math.max(...ys)) + pad;\n foot = intersect({ x: fx0, y: fy0, width: fx1 - fx0, height: fy1 - fy0 }, { x: sr.x - pad, y: sr.y - pad, width: sr.width + 2 * pad, height: sr.height + 2 * pad });\n }\n }\n if (!foot) continue;\n const readable = intersect(foot, sr);\n if (!readable || !hasTilesIn(S, readable)) continue;\n const win = readWindow(S, sr, foot);\n const out = new Uint8ClampedArray(TILE * TILE * ch);\n if (def) out.fill(def);\n let any = false;\n const ox = tx * TILE;\n const oy = ty * TILE;\n let su = widen(su0);\n let sv = widen(sv0);\n const ystart = t.y - ((((t.y - oy) % step) + step) % step);\n const xstart = t.x - ((((t.x - ox) % step) + step) % step);\n const blocks = Math.ceil((t.x + t.width - xstart) / step);\n // One block row of samples (as packed pixels), then copied down the rows of the block.\n const words = ch === 4 ? new Uint32Array(out.buffer) : null;\n const rowVals = new Float64Array(blocks);\n const rowSet = new Uint8Array(blocks);\n for (let y = ystart; y < t.y + t.height; y += step) {\n const Y = y + step / 2;\n let anyInRow = false;\n for (let bi = 0, x = xstart; bi < blocks; bi++, x += step) {\n rowSet[bi] = 0;\n const X = x + step / 2;\n const w = inv[6] * X + inv[7] * Y + inv[8];\n if (front !== 0 && front * w <= 0) continue;\n const u = (inv[0] * X + inv[1] * Y + inv[2]) / w - 0.5;\n const v = (inv[3] * X + inv[4] * Y + inv[5]) / w - 0.5;\n if (!(u >= sx0 - margin && v >= sy0 - margin && u <= sx1 + margin && v <= sy1 + margin)) continue;\n if (!affine && interp === 'bicubic') {\n const s = inverseStretch(inv, X, Y);\n su = widen(s[0]);\n sv = widen(s[1]);\n }\n sample(win, interp, u, v, su, sv, acc);\n if (!store(scratch, 0, acc, ch, def)) continue;\n rowSet[bi] = 1;\n rowVals[bi] = ch === 4 ? scratchWord[0] : scratch[0];\n anyInRow = true;\n }\n if (!anyInRow) continue;\n any = true;\n const y0 = Math.max(y, t.y);\n const y1 = Math.min(y + step, t.y + t.height);\n for (let yy = y0; yy < y1; yy++) {\n const row = (yy - oy) * TILE - ox;\n for (let bi = 0, x = xstart; bi < blocks; bi++, x += step) {\n if (!rowSet[bi]) continue;\n const x0 = Math.max(x, t.x);\n const x1 = Math.min(x + step, t.x + t.width);\n const val = rowVals[bi];\n if (words) for (let xx = x0; xx < x1; xx++) words[row + xx] = val;\n else for (let xx = x0; xx < x1; xx++) out[row + xx] = val;\n }\n }\n }\n if (!any) continue;\n const tile = finishTile(out, ch, def);\n if (tile) tiles.set(tileKey(tx, ty), tile);\n }\n return assemble(src, tiles);\n}\n\n// ---------------------------------------------------------------------------\n// Mesh warps (any forward map)\n// ---------------------------------------------------------------------------\n\n/** Grid cell size (source px) used to approximate the map. */\nconst CELL = 4;\n/** Most cells along one axis (bounds memory and time on huge layers). */\nconst MAX_CELLS = 512;\n/** Most subsamples per axis when a warp shrinks the source. */\nconst MAX_SS = 8;\n\n/**\n * The surface's content inside `options.source` (default: all of it) warped through the forward\n * map `f` (source → destination pixel space; `null` where it is undefined).\n *\n * The source rectangle is subdivided into a fine grid whose vertices are mapped forward; each\n * cell's two triangles are rasterized in destination space with the source position interpolated\n * barycentrically, then the source is sampled there. Cells are a few source pixels across, so the\n * piecewise-affine inverse is within a small fraction of a pixel of the true one; affine maps (the\n * identity included) are reproduced exactly. Where the warp shrinks the source, each pixel instead\n * averages a grid of bilinear subsamples over its source footprint, so thin lines don't alias.\n * Where the warp folds over itself the later (lower-right) cell wins.\n */\nexport function warpSurface(src: TiledSurface, f: (p: Vec2) => Vec2 | null, options: ResampleOptions = {}): TiledSurface {\n const interp = options.interpolation ?? 'bicubic';\n const ch = src.channels;\n const def = src.defaultValue;\n const empty = emptySurface(ch, def);\n const sr = options.source ? intersect(options.source, options.source) : contentBounds(src);\n if (!sr) return empty;\n const step = normalizeStep(options.step);\n const nx = Math.min(MAX_CELLS, Math.max(1, Math.ceil(sr.width / CELL)));\n const ny = Math.min(MAX_CELLS, Math.max(1, Math.ceil(sr.height / CELL)));\n const w1 = nx + 1;\n // Forward-mapped vertices: destination x, y and source x, y.\n const dvx = new Float64Array(w1 * (ny + 1));\n const dvy = new Float64Array(w1 * (ny + 1));\n const svx = new Float64Array(w1 * (ny + 1));\n const svy = new Float64Array(w1 * (ny + 1));\n let x0 = Infinity;\n let y0 = Infinity;\n let x1 = -Infinity;\n let y1 = -Infinity;\n for (let j = 0; j <= ny; j++) {\n for (let i = 0; i <= nx; i++) {\n const k = j * w1 + i;\n const sx = sr.x + (sr.width * i) / nx;\n const sy = sr.y + (sr.height * j) / ny;\n const d = f({ x: sx, y: sy });\n if (!d || !Number.isFinite(d.x) || !Number.isFinite(d.y)) return empty;\n dvx[k] = d.x;\n dvy[k] = d.y;\n svx[k] = sx;\n svy[k] = sy;\n if (d.x < x0) x0 = d.x;\n if (d.y < y0) y0 = d.y;\n if (d.x > x1) x1 = d.x;\n if (d.y > y1) y1 = d.y;\n }\n }\n const lim = 1 << 20;\n const bx0 = Math.floor(Math.max(-lim, x0)) - 1;\n const by0 = Math.floor(Math.max(-lim, y0)) - 1;\n const full = { x: bx0, y: by0, width: Math.ceil(Math.min(lim, x1)) + 1 - bx0, height: Math.ceil(Math.min(lim, y1)) + 1 - by0 };\n const dst = options.clip ? intersect(full, options.clip) : full;\n if (!dst) return empty;\n if (!options.clip) checkBudget(dst);\n // Triangles (vertex indices, in draw order), binned per destination tile.\n const triCount = nx * ny * 2;\n const tris = new Int32Array(triCount * 3);\n for (let j = 0, t = 0; j < ny; j++) {\n for (let i = 0; i < nx; i++) {\n const a = j * w1 + i;\n tris[t++] = a;\n tris[t++] = a + 1;\n tris[t++] = a + w1 + 1;\n tris[t++] = a;\n tris[t++] = a + w1 + 1;\n tris[t++] = a + w1;\n }\n }\n const tx0 = Math.floor(dst.x / TILE);\n const ty0 = Math.floor(dst.y / TILE);\n const cols = Math.floor((dst.x + dst.width - 1) / TILE) - tx0 + 1;\n const rows = Math.floor((dst.y + dst.height - 1) / TILE) - ty0 + 1;\n const bins: number[][] = Array.from({ length: cols * rows }, () => []);\n for (let k = 0; k < triCount; k++) {\n const a = tris[3 * k];\n const b = tris[3 * k + 1];\n const c = tris[3 * k + 2];\n const tminx = Math.floor(Math.min(dvx[a], dvx[b], dvx[c]));\n const tminy = Math.floor(Math.min(dvy[a], dvy[b], dvy[c]));\n const tmaxx = Math.ceil(Math.max(dvx[a], dvx[b], dvx[c]));\n const tmaxy = Math.ceil(Math.max(dvy[a], dvy[b], dvy[c]));\n const cx0 = Math.max(0, Math.floor(Math.max(tminx, dst.x) / TILE) - tx0);\n const cy0 = Math.max(0, Math.floor(Math.max(tminy, dst.y) / TILE) - ty0);\n const cx1 = Math.min(cols - 1, Math.floor(Math.min(tmaxx, dst.x + dst.width - 1) / TILE) - tx0);\n const cy1 = Math.min(rows - 1, Math.floor(Math.min(tmaxy, dst.y + dst.height - 1) / TILE) - ty0);\n for (let cy = cy0; cy <= cy1; cy++) for (let cx = cx0; cx <= cx1; cx++) bins[cy * cols + cx].push(k);\n }\n const tiles = new Map<TileKey, Tile>();\n const acc = new Float64Array(4);\n const one = new Float64Array(4);\n const n = ch === 4 ? 4 : 2;\n const count = (jx: number, jy: number) => (interp === 'nearest' ? 1 : Math.min(MAX_SS, Math.max(1, Math.round(Math.hypot(jx, jy)))));\n for (let cy = 0; cy < rows; cy++) {\n for (let cx = 0; cx < cols; cx++) {\n const list = bins[cy * cols + cx];\n if (!list.length) continue;\n const ttx = tx0 + cx;\n const tty = ty0 + cy;\n const t = intersect({ x: ttx * TILE, y: tty * TILE, width: TILE, height: TILE }, dst);\n if (!t) continue;\n const tw = t.width;\n const th = t.height;\n // Source position per destination pixel (NaN = not covered) and the triangle it came from.\n const us = new Float64Array(tw * th).fill(NaN);\n const vs = new Float64Array(tw * th);\n const owner = new Int32Array(tw * th);\n // Per triangle: the inverse map's Jacobian (source step per destination pixel along x and y) and subsample counts.\n const jac = new Float64Array(list.length * 4);\n const ssx = new Int32Array(list.length);\n const ssy = new Int32Array(list.length);\n for (let li = 0; li < list.length; li++) {\n const k = list[li];\n const a = tris[3 * k];\n const b = tris[3 * k + 1];\n const c = tris[3 * k + 2];\n const p0x = dvx[a];\n const p0y = dvy[a];\n const p1x = dvx[b];\n const p1y = dvy[b];\n const p2x = dvx[c];\n const p2y = dvy[c];\n const det = (p1x - p0x) * (p2y - p0y) - (p2x - p0x) * (p1y - p0y);\n ssx[li] = 1;\n ssy[li] = 1;\n if (Math.abs(det) < 1e-12) continue;\n const d1x = svx[b] - svx[a];\n const d1y = svy[b] - svy[a];\n const d2x = svx[c] - svx[a];\n const d2y = svy[c] - svy[a];\n const l1x = (p2y - p0y) / det;\n const l1y = (p0x - p2x) / det;\n const l2x = (p0y - p1y) / det;\n const l2y = (p1x - p0x) / det;\n // Capped, so a sliver triangle can't ask for an unbounded footprint.\n const cap = (vx: number, vy: number): [number, number] => {\n const l = Math.hypot(vx, vy);\n return l > MAX_SS ? [(vx * MAX_SS) / l, (vy * MAX_SS) / l] : [vx, vy];\n };\n const jx = cap(l1x * d1x + l2x * d2x, l1x * d1y + l2x * d2y);\n const jy = cap(l1y * d1x + l2y * d2x, l1y * d1y + l2y * d2y);\n const cnx = count(jx[0], jx[1]);\n const cny = count(jy[0], jy[1]);\n if (cnx * cny > 1) {\n jac[li * 4] = jx[0];\n jac[li * 4 + 1] = jx[1];\n jac[li * 4 + 2] = jy[0];\n jac[li * 4 + 3] = jy[1];\n ssx[li] = cnx;\n ssy[li] = cny;\n }\n const rx0 = Math.max(t.x, Math.floor(Math.min(p0x, p1x, p2x) - 0.5));\n const ry0 = Math.max(t.y, Math.floor(Math.min(p0y, p1y, p2y) - 0.5));\n const rx1 = Math.min(t.x + tw, Math.ceil(Math.max(p0x, p1x, p2x) + 0.5));\n const ry1 = Math.min(t.y + th, Math.ceil(Math.max(p0y, p1y, p2y) + 0.5));\n const eps = -1e-9;\n // A preview rasterizes only each block's first pixel (blocks are aligned to the tile).\n const ay0 = step === 1 ? ry0 : ry0 + ((((t.y - ry0) % step) + step) % step);\n const ax0 = step === 1 ? rx0 : rx0 + ((((t.x - rx0) % step) + step) % step);\n for (let y = ay0; y < ry1; y += step) {\n const py = y + 0.5;\n for (let x = ax0; x < rx1; x += step) {\n const px = x + 0.5;\n const l1 = ((px - p0x) * (p2y - p0y) - (p2x - p0x) * (py - p0y)) / det;\n const l2 = ((p1x - p0x) * (py - p0y) - (px - p0x) * (p1y - p0y)) / det;\n const l0 = 1 - l1 - l2;\n if (l0 < eps || l1 < eps || l2 < eps) continue;\n const o = (y - t.y) * tw + (x - t.x);\n us[o] = l0 * svx[a] + l1 * svx[b] + l2 * svx[c];\n vs[o] = l0 * svy[a] + l1 * svy[b] + l2 * svy[c];\n owner[o] = li;\n }\n }\n }\n // Source footprint of the covered pixels, including the spread of their subsamples.\n let fx0 = Infinity;\n let fy0 = Infinity;\n let fx1 = -Infinity;\n let fy1 = -Infinity;\n for (let o = 0; o < us.length; o++) {\n const u = us[o];\n if (Number.isNaN(u)) continue;\n const li = owner[o];\n const ex = 0.5 * (Math.abs(jac[li * 4]) + Math.abs(jac[li * 4 + 2]));\n const ey = 0.5 * (Math.abs(jac[li * 4 + 1]) + Math.abs(jac[li * 4 + 3]));\n if (u - ex < fx0) fx0 = u - ex;\n if (vs[o] - ey < fy0) fy0 = vs[o] - ey;\n if (u + ex > fx1) fx1 = u + ex;\n if (vs[o] + ey > fy1) fy1 = vs[o] + ey;\n }\n if (fx0 > fx1) continue;\n const foot = intersect({ x: Math.floor(fx0) - 3, y: Math.floor(fy0) - 3, width: Math.ceil(fx1) - Math.floor(fx0) + 6, height: Math.ceil(fy1) - Math.floor(fy0) + 6 }, sr);\n if (!foot || !hasTilesIn(src, foot)) continue;\n const win = readWindow(src, sr, foot);\n const out = new Uint8ClampedArray(TILE * TILE * ch);\n if (def) out.fill(def);\n let any = false;\n const ox = ttx * TILE;\n const oy = tty * TILE;\n const pointAt = (o: number, into: Float64Array) => {\n const li = owner[o];\n const cnx = ssx[li];\n const cny = ssy[li];\n if (cnx * cny === 1) {\n sample(win, interp, us[o] - 0.5, vs[o] - 0.5, 1, 1, into);\n return;\n }\n for (let c = 0; c < n; c++) into[c] = 0;\n const j0 = jac[li * 4];\n const j1 = jac[li * 4 + 1];\n const j2 = jac[li * 4 + 2];\n const j3 = jac[li * 4 + 3];\n for (let sy = 0; sy < cny; sy++) {\n const tt = (sy + 0.5) / cny - 0.5;\n for (let sx = 0; sx < cnx; sx++) {\n const s = (sx + 0.5) / cnx - 0.5;\n sample(win, 'bilinear', us[o] + s * j0 + tt * j2 - 0.5, vs[o] + s * j1 + tt * j3 - 0.5, 1, 1, one);\n for (let c = 0; c < n; c++) into[c] += one[c];\n }\n }\n const k = 1 / (cnx * cny);\n for (let c = 0; c < n; c++) into[c] *= k;\n };\n if (step === 1) {\n for (let o = 0; o < us.length; o++) {\n if (Number.isNaN(us[o])) continue;\n pointAt(o, acc);\n const x = t.x + (o % tw);\n const y = t.y + Math.floor(o / tw);\n if (store(out, ((y - oy) * TILE + (x - ox)) * ch, acc, ch, def)) any = true;\n }\n } else {\n // Preview: the sample at each block's first pixel fills the block.\n for (let by = 0; by < th; by += step) {\n for (let bx = 0; bx < tw; bx += step) {\n const o = by * tw + bx;\n if (Number.isNaN(us[o])) continue;\n pointAt(o, acc);\n const first = ((t.y + by - oy) * TILE + (t.x + bx - ox)) * ch;\n if (!store(out, first, acc, ch, def)) continue;\n any = true;\n for (let yy = by; yy < Math.min(th, by + step); yy++) {\n const row = ((t.y + yy - oy) * TILE + (t.x - ox)) * ch;\n for (let xx = bx; xx < Math.min(tw, bx + step); xx++) for (let c = 0; c < ch; c++) out[row + xx * ch + c] = out[first + c];\n }\n }\n }\n }\n if (!any) continue;\n const tile = finishTile(out, ch, def);\n if (tile) tiles.set(tileKey(ttx, tty), tile);\n }\n }\n return assemble(src, tiles);\n}\n\n// ---------------------------------------------------------------------------\n// Compositing a moved region back\n// ---------------------------------------------------------------------------\n\n/**\n * `top` (RGBA, straight alpha) composited over `base` with the Normal blend. A one-channel `base`\n * (a mask) takes `top`'s red channel as the value, weighted by its alpha: that is how a moved part\n * of a mask travels (value and coverage) and lands. Only tiles `top` stores are touched, so every\n * other tile of `base` stays shared.\n */\nexport function compositeSurfaceOver(base: TiledSurface, top: TiledSurface): TiledSurface {\n if (top.channels !== 4) throw new Error('compositeSurfaceOver: top must be RGBA');\n if (!top.tiles.size) return base;\n const ch = base.channels;\n const tiles = new Map(base.tiles);\n for (const [key, t] of top.tiles) {\n const under = base.tiles.get(key);\n if (!under && ch === 4) {\n tiles.set(key, t);\n continue;\n }\n const d = new Uint8ClampedArray(TILE * TILE * ch);\n if (under) d.set(under.data);\n else if (base.defaultValue) d.fill(base.defaultValue);\n const s = t.data;\n if (ch === 4) {\n for (let i = 0; i < d.length; i += 4) {\n const ta = s[i + 3] / 255;\n if (ta <= 0) continue;\n if (ta >= 1) {\n d[i] = s[i];\n d[i + 1] = s[i + 1];\n d[i + 2] = s[i + 2];\n d[i + 3] = 255;\n continue;\n }\n const da = d[i + 3] / 255;\n const oa = ta + da * (1 - ta);\n for (let c = 0; c < 3; c++) d[i + c] = (s[i + c] * ta + d[i + c] * da * (1 - ta)) / oa;\n d[i + 3] = oa * 255;\n }\n } else {\n for (let p = 0, i = 0; p < d.length; p++, i += 4) {\n const ta = s[i + 3] / 255;\n if (ta > 0) d[p] = s[i] * ta + d[p] * (1 - ta);\n }\n }\n const tile = finishTile(d, ch, base.defaultValue);\n if (tile) tiles.set(key, tile);\n else tiles.delete(key);\n }\n return new TiledSurface(ch, tiles, base.defaultValue);\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"]}