faceless-photolib

@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/adjustments

Usage

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

ExportDescription
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).
AdjustmentDescriptorShader-IR node for one adjustment: { node, effect, workingSpace, params }.
PixelFA 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.

applyAdjustmentfunction
applyAdjustment(desc: AdjustmentDescriptor, pixel: PixelF): PixelF

CPU 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).

compileAdjustmentfunction
compileAdjustment(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.

supportedEffectsfunction
supportedEffects(): readonly string[]

Ids of the adjustment effects this package recognizes (e.g. curves, levels, hueSaturation).

AdjustmentDescriptorinterface
interface AdjustmentDescriptor

Backend-agnostic descriptor for one adjustment (Shader IR node).

PixelFinterface
interface PixelF

A straight (un-premultiplied) RGBA color in working (linear) space.

On this page