Linear picture to display values
32-bit in partEach pixel's final red, green and blue from the linear picture and the post settings.
AP1 × wb → Rec.709 × 2^exposure → vignette → highlights → contrast → view transform → sRGB → 8 bits
Reads the linear picture from a Float32Array, keeps the working image in a Float32Array and writes either 8-bit values rounded to the nearest step or 32-bit floats in [0, 1]. The steps for one pixel between those stores are 64-bit.
export function post(lin, w, h, p, { exposure = 0, wb = null, lut = null, float = false } = {}) {
p = p || POST_DEFAULTS;
const n = w * h, k = Math.pow(2, exposure);
let img = new Float32Array(n * 3);
for (let i = 0; i < n; i++) {
const a = [lin[i * 3] * (wb ? wb[0] : 1), lin[i * 3 + 1] * (wb ? wb[1] : 1), lin[i * 3 + 2] * (wb ? wb[2] : 1)];
const c = mul3(AP1_TO_REC709, a);
for (let j = 0; j < 3; j++) img[i * 3 + j] = Number.isFinite(c[j]) ? Math.max(0, c[j]) * k : 0;
}
if (p.bloom > 0) img = bloomPass(img, w, h, p.bloom, p.bloomSize);
if (p.glare > 0) img = glarePass(img, w, h, p.glare, p.streaks);
const out = float ? new Float32Array(n * 3) : new Uint8ClampedArray(n * 4), cx = (w - 1) / 2, cy = (h - 1) / 2, rmax = Math.hypot(cx, cy) || 1, px = [0, 0, 0], lo = [0, 0, 0];
const knee = 1, hs = p.highlights * 2;
for (let i = 0; i < n; i++) {
px[0] = img[i * 3]; px[1] = img[i * 3 + 1]; px[2] = img[i * 3 + 2];
if (p.vignette > 0) {
const r = Math.hypot((i % w) - cx, Math.floor(i / w) - cy) / rmax, t = Math.min(1, Math.max(0, (r - 0.2) / 0.8)), f = 1 - p.vignette * t * t * (3 - 2 * t);
px[0] *= f; px[1] *= f; px[2] *= f;
}
if (hs > 0) {
const L = 0.2126 * px[0] + 0.7152 * px[1] + 0.0722 * px[2];
if (L > knee) { const L2 = knee + (L - knee) / (1 + (L - knee) * hs), s = L2 / L; px[0] *= s; px[1] *= s; px[2] *= s; }
}
if (p.contrast) for (let j = 0; j < 3; j++) px[j] = 0.18 * Math.pow(px[j] / 0.18, 1 + p.contrast);
// ACES keeps its own 1/0.6 pre-scale (acesFilmic), so the default path is displayPixel's, number for number
const t = p.view === 'aces' ? acesFilmic(px, 0) : viewTransform(p.view, px);
let r = linearToSrgb(t[0]), g = linearToSrgb(t[1]), b = linearToSrgb(t[2]);
if (lut && p.lutStrength > 0) {
applyCube(lut, r, g, b, lo);
const s = p.lutStrength;
r += (lo[0] - r) * s; g += (lo[1] - g) * s; b += (lo[2] - b) * s;
}
if (float) { out[i * 3] = Math.min(1, Math.max(0, r)); out[i * 3 + 1] = Math.min(1, Math.max(0, g)); out[i * 3 + 2] = Math.min(1, Math.max(0, b)); continue; }
out[i * 4] = Math.round(Math.min(1, Math.max(0, r)) * 255); out[i * 4 + 1] = Math.round(Math.min(1, Math.max(0, g)) * 255); out[i * 4 + 2] = Math.round(Math.min(1, Math.max(0, b)) * 255); out[i * 4 + 3] = 255;
}
return out;
}