@faceless-photolib/lut
3D LUT parsing (.cube / .3dl / ACES .clf) and CPU-reference tetrahedral/trilinear interpolation, lowering every format to one rgba16float-shaped cube descriptor for the faceless-photolib engine.
3D LUT support for faceless-photolib — a headless, color-managed, GPU-accelerated image-editing engine.
Parses the three on-disk LUT formats — .cube (R-fastest float), .3dl (B-fastest int), and
ACES .clf (XML ProcessList) — each with its own index logic, and lowers them to one
backend-agnostic Lut3dDescriptor (a flat size³ × 4 RGBA grid shaped for upload as an
rgba16float 3D texture). Includes a CPU-reference sampler — tetrahedral by default, trilinear
fast path — plus an optional 1D shaper applied before the cube and a strength mix toward the
original input.
Malformed files fail loudly (validation-error); unsupported-but-well-formed features are
rejected as not-implemented rather than silently treated as identity.
Install
pnpm add @faceless-photolib/lutUsage
import {
parseLut,
sampleLut,
applyLut,
NO_SHAPER,
} from "@faceless-photolib/lut";
// parseLut returns a Result discriminated union — guard the "ok" branch.
const parsed = parseLut("cube", bytes); // bytes: Uint8Array
if (parsed.kind !== "ok") throw new Error("invalid LUT file");
const lut = parsed.value;
// CPU-reference sample at a normalized RGB coordinate (golden source).
const sampled = sampleLut(lut, [0.5, 0.5, 0.5]);
// Full apply: optional 1D shaper, then the 3D LUT, then a strength mix.
const mapped = applyLut(lut, [0.5, 0.5, 0.5], { shaper: NO_SHAPER, strength: 1 });API
| Export | Description |
|---|---|
parseLut(format, bytes) | Parse Uint8Array bytes of a declared LutFormat into Result<Lut3dDescriptor>. |
sampleLut(lut, rgb) | CPU-reference sample of the LUT at a normalized RGB triple (tetrahedral/trilinear per descriptor). |
applyLut(lut, rgb, options) | Optional shaper → LUT → blend toward the original input by options.strength (ApplyOptions). |
writeCube(lut) | Serialize a descriptor back to .cube text (Result<string>). |
resolveLut(ref, interpolation) | Resolve a LUT by ContentHash; needs an AssetStore injected by the runtime layer. |
parseShaperCube(text) | Parse a 1D .cube (LUT_1D_SIZE) into a Shaper1d. |
applyShaper(shaper, rgb) | Apply a per-channel 1D shaper to an RGB triple. |
CreateLut3dRequestSchema | Zod boundary schema for "create a 3D-LUT layer" requests. |
LutFormatSchema / LutInterpolationSchema | Zod enums mirroring LutFormat / LutInterpolation. |
SUPPORTED_SIZES / DEFAULT_SIZE | Validated grid sizes ([17, 33, 65]) and the default (33). |
NO_SHAPER | The named "no shaper" slot ({ kind: "none" }). |
Key types: Lut3dDescriptor, LutInterpolation ("tetrahedral" | "trilinear"), LutFormat
("cube" | "3dl" | "clf"), ApplyOptions, Shaper1d, NoShaper, ShaperSlot,
CreateLut3dRequest.
License
MIT
API reference
21 public exports · 18 documented · generated from source.
applyLutfunctionapplyLut(lut: Lut3dDescriptor, rgb: Rgb, options: ApplyOptions): RgbFull CPU reference apply: optionally run the 1D shaper, sample the 3D LUT, then blend the mapped result toward the *original* input by `strength` (lut3d spec: strength=0 is identity, strength=1 is the full LUT, intermediate is a proportional linear blend). The strength mix is against the original input, not the post-shaper value, so strength=0 is a true no-op.
applyShaperfunctionapplyShaper(shaper: Shaper1d, rgb: Rgb): RgbApply a 1D shaper to an RGB triple.
parseLutfunctionparseLut(format: LutFormat, bytes: Uint8Array<ArrayBufferLike>): Result<Lut3dDescriptor>Parse raw LUT bytes of a declared format into a descriptor. Each format keeps its own index logic (`.cube` R-fastest float, `.3dl` B-fastest int, `.clf` XML ProcessList B-fastest). A structurally malformed file is a `validation-error`; an unsupported but well-formed feature is `rejected` `not-implemented` with a beacon (never a silent identity).
parseShaperCubefunctionparseShaperCube(text: string): Result<Shaper1d>Parse a 1D `.cube` (`LUT_1D_SIZE`) into a `Shaper1d`. Each row is a single RGB triple; the same row index is the table index for all three channels.
resolveLutfunctionresolveLut(_ref: string & $brand<"ContentHash">, _interpolation: LutInterpolation): Result<Lut3dDescriptor>Resolve a LUT referenced by content hash into a descriptor via the asset store. The `lut` leaf package depends only on `schemas`/`color`/ `diagnostics`; the `AssetStore` port that turns a `ContentHash` into bytes lives in a later layer (engine-runtime), so resolution is injected there. Until that wiring exists this fails loudly rather than guessing.
sampleLutfunctionsampleLut(lut: Lut3dDescriptor, rgb: readonly [number, number, number]): readonly [number, number, number]CPU reference: sample the LUT at a normalized RGB coordinate (golden source).
writeCubefunctionwriteCube(lut: Lut3dDescriptor): Result<string>Serialize a descriptor back to `.cube` text (writer half of the codec).
ApplyOptionsinterfaceinterface ApplyOptionsOptions for the full CPU apply pipeline (shaper + LUT + strength mix).
Lut3dDescriptorinterfaceinterface Lut3dDescriptorBackend-agnostic 3D-LUT descriptor (Shader IR node). `data` is the flat float grid (`size³ × 4`, RGBA, R-fastest after normalization). `domainMin`/ `domainMax` are applied as a pre-scale before indexing.
NoShaperinterfaceinterface NoShaperNamed absence of a shaper.
Shaper1dinterfaceinterface Shaper1dOptional 1D shaper / pre-LUT applied per channel before the 3D LUT (design D6: "log/shaper LUT in front of the 3D LUT for scene-linear inputs"). Each channel table is `size` entries over the declared `domainMin..domainMax`. Absence is a named variant, not `undefined` (project.md §4): a layer with no shaper uses `{ kind: "none" }`.
CreateLut3dRequesttypetype CreateLut3dRequestLutFormattypetype LutFormatThe on-disk LUT formats this package parses.
LutInterpolationtypetype LutInterpolationInterpolation strategy for sampling a 3D LUT.
ShaperSlottypetype ShaperSlotCreateLut3dRequestSchemaconstconst CreateLut3dRequestSchemaBoundary request schema for "create a 3D-LUT layer". Validated with `safeParse` at a transport boundary; a failure yields `invalid-request` (project.md §4b) — distinct from a structurally malformed *file*, which is a `validation-error`.
DEFAULT_SIZEconstDEFAULT_SIZE: 33Default grid size per design D6 when a format omits a size declaration.
LutFormatSchemaconstLutFormatSchema: ZodEnum<{ cube: "cube"; "3dl": "3dl"; clf: "clf"; }>Supported on-disk LUT formats. Mirrors `LutFormat`.
LutInterpolationSchemaconstLutInterpolationSchema: ZodEnum<{ tetrahedral: "tetrahedral"; trilinear: "trilinear"; }>Interpolation strategy for sampling a 3D LUT. Mirrors `LutInterpolation`.
NO_SHAPERconstNO_SHAPER: NoShaperSUPPORTED_SIZESconstSUPPORTED_SIZES: readonly number[]The supported (validated, not silently resampled) cubic grid sizes.
@faceless-photolib/adjustments
Non-destructive image adjustments (curves, levels, exposure, contrast, hue/saturation, white balance, channel mixer) as color-managed shader-IR nodes with a declared working color space and a CPU reference.
@faceless-photolib/color-transform
ACES CLF reader and a constrained DCTL subset compiled to the faceless-photolib render IR — the DaVinci-Resolve-style color-transform layer.