thi-ng/umbrella

View on GitHub
packages/pixel/src/sample.ts

Summary

Maintainability
A
0 mins
Test Coverage
import type { Fn, IObjectOf, NumericArray } from "@thi.ng/api";
import { assert } from "@thi.ng/errors/assert";
import { clamp } from "@thi.ng/math/interval";
import { mixBicubic, mixBilinear } from "@thi.ng/math/mix";
import { fract, mod } from "@thi.ng/math/prec";
import type {
    Filter,
    FloatSampler,
    IntSampler,
    IPixelBuffer,
    Wrap,
} from "./api.js";
import type { FloatBuffer } from "./float.js";
import type { IntBuffer } from "./int.js";

export function defSampler(
    src: FloatBuffer,
    filter?: Filter,
    wrap?: Wrap
): FloatSampler;
export function defSampler(
    src: IntBuffer,
    filter?: Filter,
    wrap?: Wrap
): IntSampler;
export function defSampler(
    src: IPixelBuffer,
    filter: Filter = "linear",
    wrap: Wrap = "clamp"
) {
    const isFloat = !!(<any>src.format).__float;
    const suffix = (<any>src.format).channels.length === 1 ? "1" : "";
    const id = `${filter[0]}${wrap[0]}${suffix}`;
    const impl = (
        isFloat
            ? <IObjectOf<Fn<FloatBuffer, FloatSampler>>>{
                    nc1: sampleFNC,
                    nw1: sampleFNW,
                    nr1: sampleFNR,
                    nc: sampleFNC,
                    nw: sampleFNW,
                    nr: sampleFNR,
                    lc1: (src) => bilinearGrayF(sampleINC(src)),
                    lw1: (src) => bilinearGrayF(sampleINW(src)),
                    lr1: (src) => bilinearGrayF(sampleINR(src)),
                    lc: (src) => bilinearFloat(src, sampleFNC(src)),
                    lw: (src) => bilinearFloat(src, sampleFNW(src)),
                    lr: (src) => bilinearFloat(src, sampleFNR(src)),
                    cc1: (src) => bicubicGrayF(sampleINC(src)),
                    cw1: (src) => bicubicGrayF(sampleINW(src)),
                    cr1: (src) => bicubicGrayF(sampleINR(src)),
                    cc: (src) => bicubicFloat(src, sampleFNC(src)),
                    cw: (src) => bicubicFloat(src, sampleFNW(src)),
                    cr: (src) => bicubicFloat(src, sampleFNR(src)),
              }
            : <IObjectOf<Fn<IntBuffer, IntSampler>>>{
                    nc1: sampleINC,
                    nw1: sampleINW,
                    nr1: sampleINR,
                    nc: sampleINC,
                    nw: sampleINW,
                    nr: sampleINR,
                    lc1: (src) => bilinearGray(sampleINC(src)),
                    lw1: (src) => bilinearGray(sampleINW(src)),
                    lr1: (src) => bilinearGray(sampleINR(src)),
                    lc: (src) => bilinearABGR(src, sampleINC(src)),
                    lw: (src) => bilinearABGR(src, sampleINW(src)),
                    lr: (src) => bilinearABGR(src, sampleINR(src)),
                    cc1: (src) => bicubicGrayI(src, sampleINC(src)),
                    cw1: (src) => bicubicGrayI(src, sampleINW(src)),
                    cr1: (src) => bicubicGrayI(src, sampleINR(src)),
                    cc: (src) => bicubicABGR(src, sampleINC(src)),
                    cw: (src) => bicubicABGR(src, sampleINW(src)),
                    cr: (src) => bicubicABGR(src, sampleINR(src)),
              }
    )[id];
    assert(!!impl, `missing impl for ${id}`);
    return impl(<any>src);
}

const sampleINC =
    ({ data, width, height }: IPixelBuffer): IntSampler =>
    (x, y) =>
        x >= 0 && x < width && y >= 0 && y < height
            ? data[(y | 0) * width + (x | 0)]
            : 0;

const sampleINW =
    ({ data, width, height }: IPixelBuffer): IntSampler =>
    (x, y) =>
        data[mod(y | 0, height) * width + mod(x | 0, width)];

const sampleINR = ({ data, width, height }: IPixelBuffer): IntSampler => {
    const w1 = width - 1;
    const h1 = height - 1;
    return (x, y) => data[clamp(y | 0, 0, h1) * width + clamp(x | 0, 0, w1)];
};

const sampleFNC =
    ({
        data,
        width,
        height,
        stride: [stride, rowStride],
    }: FloatBuffer): FloatSampler =>
    (x, y) => {
        let i: number;
        return x >= 0 && x < width && y >= 0 && y < height
            ? ((i = (y | 0) * rowStride + (x | 0) * stride),
              data.slice(i, i + stride))
            : [0];
    };

const sampleFNW =
    ({
        data,
        width,
        height,
        stride: [stride, rowStride],
    }: FloatBuffer): FloatSampler =>
    (x, y) => {
        let i = mod(y | 0, height) * rowStride + mod(x | 0, width) * stride;
        return data.slice(i, i + stride);
    };

const sampleFNR = ({
    data,
    width,
    height,
    stride: [stride, rowStride],
}: FloatBuffer): FloatSampler => {
    const w1 = width - 1;
    const h1 = height - 1;
    return (x, y) => {
        let i = clamp(y | 0, 0, h1) * rowStride + clamp(x | 0, 0, w1) * stride;
        return data.slice(i, i + stride);
    };
};

const mixBilinearChan = (
    buf: NumericArray,
    u: number,
    v: number,
    i: number,
    s = 4
) => mixBilinear(buf[i], buf[i + s], buf[i + 2 * s], buf[i + 3 * s], u, v);

const bilinearGray =
    (sample: IntSampler): IntSampler =>
    (x, y) => {
        x -= 0.5;
        y -= 0.5;
        return mixBilinear(
            sample(x, y),
            sample(x + 1, y),
            sample(x, y + 1),
            sample(x + 1, y + 1),
            fract(x),
            fract(y)
        );
    };

const bilinearGrayF = (sample: IntSampler): FloatSampler => {
    sample = bilinearGray(sample);
    return (x, y) => [sample(x, y)];
};

const bilinearABGR = (src: IntBuffer, sample1: IntSampler): IntSampler => {
    const { fromABGR, toABGR } = src.format;
    const u32 = new Uint32Array(4);
    const u8 = new Uint8Array(u32.buffer);
    return (x, y) => {
        x -= 0.5;
        y -= 0.5;
        u32[0] = toABGR(sample1(x, y));
        u32[1] = toABGR(sample1(x + 1, y));
        u32[2] = toABGR(sample1(x, y + 1));
        u32[3] = toABGR(sample1(x + 1, y + 1));
        const u = fract(x);
        const v = fract(y);
        return (
            fromABGR(
                mixBilinearChan(u8, u, v, 0) |
                    (mixBilinearChan(u8, u, v, 1) << 8) |
                    (mixBilinearChan(u8, u, v, 2) << 16) |
                    (mixBilinearChan(u8, u, v, 3) << 24)
            ) >>> 0
        );
    };
};

const bilinearFloat = (
    { stride: [stride] }: FloatBuffer,
    sample1: FloatSampler
): FloatSampler => {
    const f32 = new Float32Array(stride * 4);
    return (x, y) => {
        x -= 0.5;
        y -= 0.5;
        f32.set(sample1(x, y), 0);
        f32.set(sample1(x + 1, y), stride);
        f32.set(sample1(x, y + 1), stride * 2);
        f32.set(sample1(x + 1, y + 1), stride * 3);
        const u = fract(x);
        const v = fract(y);
        let res = [];
        for (let i = 0; i < stride; i++) {
            res.push(mixBilinearChan(f32, u, v, i, stride));
        }
        return res;
    };
};

const bicubicGray =
    (sample: IntSampler): IntSampler =>
    (x, y) => {
        x -= 0.5;
        y -= 0.5;
        const x1 = x - 1;
        const x2 = x + 1;
        const x3 = x + 2;
        const y1 = y - 1;
        const y2 = y + 1;
        const y3 = y + 2;
        return mixBicubic(
            sample(x1, y1),
            sample(x, y1),
            sample(x2, y1),
            sample(x3, y1),
            sample(x1, y),
            sample(x, y),
            sample(x2, y),
            sample(x3, y),
            sample(x1, y2),
            sample(x, y2),
            sample(x2, y2),
            sample(x3, y2),
            sample(x1, y3),
            sample(x, y3),
            sample(x2, y3),
            sample(x3, y3),
            fract(x),
            fract(y)
        );
    };

const bicubicGrayI = (src: IntBuffer, sample: IntSampler): IntSampler => {
    const max = src.format.channels[0].mask0;
    sample = bicubicGray(sample);
    return (x, y) => clamp(sample(x, y), 0, max);
};

const bicubicGrayF = (sample: IntSampler): FloatSampler => {
    sample = bicubicGray(sample);
    return (x, y) => [sample(x, y)];
};

const mixBicubicChan = (
    buf: NumericArray,
    u: number,
    v: number,
    i: number,
    s = 4
) =>
    mixBicubic(
        buf[i],
        buf[i + s],
        buf[i + 2 * s],
        buf[i + 3 * s],
        buf[i + 4 * s],
        buf[i + 5 * s],
        buf[i + 6 * s],
        buf[i + 7 * s],
        buf[i + 8 * s],
        buf[i + 9 * s],
        buf[i + 10 * s],
        buf[i + 11 * s],
        buf[i + 12 * s],
        buf[i + 13 * s],
        buf[i + 14 * s],
        buf[i + 15 * s],
        u,
        v
    );

const mixBicubicChanClamped = (
    buf: NumericArray,
    u: number,
    v: number,
    i: number,
    s = 4
) => clamp(mixBicubicChan(buf, u, v, i, s), 0, 255);

const bicubicABGR = (src: IntBuffer, sample: IntSampler): IntSampler => {
    const { fromABGR, toABGR } = src.format;
    const u32 = new Uint32Array(16);
    const u8 = new Uint8Array(u32.buffer);
    return (x, y) => {
        x -= 0.5;
        y -= 0.5;
        const x1 = x - 1;
        const x2 = x + 1;
        const x3 = x + 2;
        const y1 = y - 1;
        const y2 = y + 1;
        const y3 = y + 2;
        const u = fract(x);
        const v = fract(y);
        u32[0] = toABGR(sample(x1, y1));
        u32[1] = toABGR(sample(x, y1));
        u32[2] = toABGR(sample(x2, y1));
        u32[3] = toABGR(sample(x3, y1));
        u32[4] = toABGR(sample(x1, y));
        u32[5] = toABGR(sample(x, y));
        u32[6] = toABGR(sample(x2, y));
        u32[7] = toABGR(sample(x3, y));
        u32[8] = toABGR(sample(x1, y2));
        u32[9] = toABGR(sample(x, y2));
        u32[10] = toABGR(sample(x2, y2));
        u32[11] = toABGR(sample(x3, y2));
        u32[12] = toABGR(sample(x1, y3));
        u32[13] = toABGR(sample(x, y3));
        u32[14] = toABGR(sample(x2, y3));
        u32[15] = toABGR(sample(x3, y3));
        return (
            fromABGR(
                mixBicubicChanClamped(u8, u, v, 0) |
                    (mixBicubicChanClamped(u8, u, v, 1) << 8) |
                    (mixBicubicChanClamped(u8, u, v, 2) << 16) |
                    (mixBicubicChanClamped(u8, u, v, 3) << 24)
            ) >>> 0
        );
    };
};

const bicubicFloat = (
    { stride: [stride] }: FloatBuffer,
    sample: FloatSampler
): FloatSampler => {
    const f32 = new Float32Array(stride * 16);
    return (x, y) => {
        x -= 0.5;
        y -= 0.5;
        const x1 = x - 1;
        const x2 = x + 1;
        const x3 = x + 2;
        const y1 = y - 1;
        const y2 = y + 1;
        const y3 = y + 2;
        const u = fract(x);
        const v = fract(y);
        f32.set(sample(x1, y1), 0);
        f32.set(sample(x, y1), stride);
        f32.set(sample(x2, y1), 2 * stride);
        f32.set(sample(x3, y1), 3 * stride);
        f32.set(sample(x1, y), 4 * stride);
        f32.set(sample(x, y), 5 * stride);
        f32.set(sample(x2, y), 6 * stride);
        f32.set(sample(x3, y), 7 * stride);
        f32.set(sample(x1, y2), 8 * stride);
        f32.set(sample(x, y2), 9 * stride);
        f32.set(sample(x2, y2), 10 * stride);
        f32.set(sample(x3, y2), 11 * stride);
        f32.set(sample(x1, y3), 12 * stride);
        f32.set(sample(x, y3), 13 * stride);
        f32.set(sample(x2, y3), 14 * stride);
        f32.set(sample(x3, y3), 15 * stride);
        let res = [];
        for (let i = 0; i < stride; i++) {
            res.push(mixBicubicChan(f32, u, v, i, stride));
        }
        return res;
    };
};