@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.
Non-destructive adjustments for faceless-photolib — a headless, color-managed, GPU-accelerated image-editing engine.
Each adjustment (curves, levels, exposure, contrast, hue/saturation, white balance, channel mixer) is
a backend-agnostic shader-IR node that declares the working color space it operates in. The engine
auto-inserts reference↔working-space conversions around it, so the effect math always runs in the space
it was authored against. This package owns boundary validation (Zod safeParse — out-of-range params are
rejected, never clamped), effect dispatch (a table lookup; an unknown effect is not-implemented, never a
silent identity pass-through), and a CPU reference evaluator that is the golden source for the GPU path.
Install
pnpm add @faceless-photolib/adjustmentsUsage
import {
compileAdjustment,
applyAdjustment,
supportedEffects,
type PixelF,
} from "@faceless-photolib/adjustments";
import { AdjustmentParamsSchema, ColorSpaceIdSchema } from "@faceless-photolib/schemas";
// The working space is a registered color-space id (e.g. "acescg", "srgb-linear").
const workingSpace = ColorSpaceIdSchema.parse("srgb-linear");
// `AdjustmentParams` is the envelope { effect, params }; params are validated
// against the effect's own schema. `exposure` takes { stops } (one stop = ×2 light).
const request = AdjustmentParamsSchema.parse({
effect: "exposure",
params: { stops: 1 },
});
// Validate + lower into a renderable descriptor. Returns a discriminated `Result`.
const compiled = compileAdjustment(request, workingSpace);
if (compiled.kind !== "ok") {
throw new Error(`compile failed: ${compiled.kind}`);
}
// CPU reference: apply to a straight (un-premultiplied) RGBA pixel in connection space.
const input: PixelF = { r: 0.18, g: 0.18, b: 0.18, a: 1 };
const output = applyAdjustment(compiled.value, input);
// output ≈ { r: 0.36, g: 0.36, b: 0.36, a: 1 } — one stop brighter.
// Which effects this package recognizes.
console.log(supportedEffects());
// → ["curves", "levels", "exposure", "contrast", "hueSaturation", "whiteBalance", "channelMixer"]API
| Export | Description |
|---|---|
compileAdjustment(params, workingSpace) | Validates an AdjustmentParams envelope and declared ColorSpaceId, returning a Result<AdjustmentDescriptor>. Unknown effect → rejected("not-implemented"); out-of-range params → invalid-request with Zod issues; unresolvable space → failure. |
applyAdjustment(descriptor, pixel) | CPU reference: converts the PixelF into the descriptor's working space, applies the effect math, and converts back to connection space. Fails loud on a non-renderable descriptor. |
supportedEffects() | Returns the ids of the recognized adjustment effects (curves, levels, exposure, contrast, hueSaturation, whiteBalance, channelMixer). |
AdjustmentDescriptor | Shader-IR node for one adjustment: { node, effect, workingSpace, params }. |
PixelF | A straight (un-premultiplied) RGBA color, components as number. |
Alpha passes through unchanged — these tonal/color adjustments operate on straight RGB only.
License
MIT
API reference
5 public exports · 5 documented · generated from source.
applyAdjustmentfunctionapplyAdjustment(desc: AdjustmentDescriptor, pixel: PixelF): PixelFCPU reference: apply an adjustment to a single working-space pixel (golden source). The incoming pixel is in the connection (reference) space; this routine converts it into the descriptor's declared working space, applies the effect math there, and converts back to the connection space — the auto-inserted reference→workingSpace→reference wrapping the adjustments spec requires, realized against the real color-package conversion descriptors. Failure modes are loud (this returns a `PixelF`, not a `Result`): an unknown effect throws via `warnNotImplemented`, and a descriptor whose params or working space cannot be resolved is a `warnUnexpected` + throw — never a silent identity (a well-formed descriptor from `compileAdjustment` guarantees both resolve, so these guard against hand-built/forward-compat descriptors).
compileAdjustmentfunctioncompileAdjustment(params: { effect: string; params: Record<string, unknown>; }, workingSpace: string & $brand<"ColorSpaceId">): Result<AdjustmentDescriptor>Validate + lower an `AdjustmentParams` into a descriptor; unknown effect is rejected + beacon. The params record is validated against the effect's own Zod schema at this boundary (`safeParse`), and an out-of-range payload is `invalid-request` with the Zod issues — the engine never clamps or defaults out-of-range parameters (adjustments spec). The declared working space must resolve in the registry, otherwise the descriptor would be unrenderable; an unknown space is `not-implemented` + beacon.
supportedEffectsfunctionsupportedEffects(): readonly string[]Ids of the adjustment effects this package recognizes (e.g. curves, levels, hueSaturation).
AdjustmentDescriptorinterfaceinterface AdjustmentDescriptorBackend-agnostic descriptor for one adjustment (Shader IR node).
PixelFinterfaceinterface PixelFA straight (un-premultiplied) RGBA color in working (linear) space.
@faceless-photolib/blend
All 27 W3C Compositing & Blending Level 1 blend modes (the Photoshop set) over premultiplied Porter-Duff alpha, as a CPU golden reference for faceless-photolib.
@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.