@thi.ng/pixel
Version:
Typedarray integer & float pixel buffers w/ customizable formats, blitting, drawing, convolution
435 lines (434 loc) • 11.6 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
import { IGrid2DMixin } from "@thi.ng/api/mixins/igrid";
import {
typedArray,
uintTypeForBits
} from "@thi.ng/api/typedarray";
import { isNumber } from "@thi.ng/checks/is-number";
import { isString } from "@thi.ng/checks/is-string";
import { assert } from "@thi.ng/errors/assert";
import {
isPremultipliedInt,
postmultiplyInt,
premultiplyInt
} from "@thi.ng/porter-duff/premultiply";
import {
Lane,
ROT_IDS
} from "./api.js";
import { canvasPixels } from "./canvas.js";
import {
ensureAlpha,
ensureChannel,
ensureImageData,
ensureImageDataSize,
ensureSize
} from "./checks.js";
import { FloatBuffer } from "./float.js";
import { ABGR8888 } from "./format/abgr8888.js";
import { defIntFormat } from "./format/int-format.js";
import { imageCanvas } from "./image.js";
import {
__compileGrayFromABGR,
__compileGrayToABGR
} from "./internal/codegen.js";
import {
__blitCanvas,
__clampRegion,
__prepRegions,
__setChannelConvert,
__setChannelSame,
__setChannelUni,
__transformABGR
} from "./internal/utils.js";
import { defSampler } from "./sample.js";
function intBuffer(...args) {
return args[0] instanceof IntBuffer ? args[0].as(args[1]) : (
// @ts-ignore
new IntBuffer(...args)
);
}
const intBufferFromImage = (img, fmt, width, height = width) => intBufferFromCanvas(imageCanvas(img, width, height).canvas, fmt);
const intBufferFromCanvas = (canvas, fmt = ABGR8888) => {
const { data } = canvasPixels(canvas);
const w = canvas.width;
const h = canvas.height;
let dest;
if (fmt === ABGR8888) {
dest = data;
} else {
dest = typedArray(fmt.type, w * h);
const src = data;
const from = fmt.fromABGR;
for (let i = dest.length; i-- > 0; ) {
dest[i] = from(src[i]);
}
}
return new IntBuffer(w, h, fmt, dest);
};
let IntBuffer = class {
size;
stride;
format;
data;
constructor(w, h, fmt = ABGR8888, data) {
this.size = [w, h];
this.stride = [1, w];
this.format = fmt.__packed ? fmt : defIntFormat(fmt);
this.data = data || typedArray(fmt.type, w * h);
}
/** @deprecated use `.data` instead */
get pixels() {
return this.data;
}
get width() {
return this.size[0];
}
get height() {
return this.size[1];
}
// TODO support custom offsets (via ctor arg)
get offset() {
return 0;
}
get dim() {
return 2;
}
*[Symbol.iterator]() {
yield* this.data;
}
as(fmt) {
if (!fmt.__float)
return this.getRegion(
0,
0,
this.width,
this.height,
fmt
);
const dest = new FloatBuffer(this.width, this.height, fmt);
const {
data: dbuf,
format: dfmt,
stride: [stride]
} = dest;
const { data: sbuf, format: sfmt } = this;
for (let i = sbuf.length; i-- > 0; ) {
dbuf.set(dfmt.fromABGR(sfmt.toABGR(sbuf[i])), i * stride);
}
return dest;
}
copy() {
const dest = this.empty();
dest.data.set(this.data);
return dest;
}
empty() {
return new IntBuffer(this.width, this.height, this.format);
}
// @ts-ignore mixin
order() {
}
// @ts-ignore mixin
includes(x, y) {
}
// @ts-ignore mixin
indexAt(x, y) {
}
// @ts-ignore mixin
indexAtUnsafe(x, y) {
}
// @ts-ignore mixin
getAt(x, y) {
}
// @ts-ignore mixin
getAtUnsafe(x, y) {
}
// @ts-ignore mixin
setAt(x, y, col) {
}
// @ts-ignore mixin
setAtUnsafe(x, y, col) {
}
getChannelAt(x, y, id, normalized = false) {
const chan = ensureChannel(this.format, id);
const col = this.getAt(x, y);
return normalized ? chan.float(col) : chan.int(col);
}
setChannelAt(x, y, id, col, normalized = false) {
const chan = ensureChannel(this.format, id);
const src = this.getAt(x, y);
normalized ? chan.setFloat(src, col) : chan.setInt(src, col);
return this;
}
blend(op, dest, opts) {
let sw = this.width;
let dw = dest.width;
const { sx, sy, dx, dy, rw, rh } = __prepRegions(this, dest, opts);
if (rw < 1 || rh < 1) return dest;
const sbuf = this.data;
const dbuf = dest.data;
const sf = this.format.toABGR;
const df1 = dest.format.toABGR;
const df2 = dest.format.fromABGR;
for (let si = (sx | 0) + (sy | 0) * sw, di = (dx | 0) + (dy | 0) * dw, yy = 0; yy < rh; yy++, si += sw, di += dw) {
for (let xx = 0; xx < rw; xx++) {
dbuf[di + xx] = df2(op(sf(sbuf[si + xx]), df1(dbuf[di + xx])));
}
}
return dest;
}
blit(dest, opts) {
let sw = this.width;
let dw = dest.width;
const { sx, sy, dx, dy, rw, rh } = __prepRegions(this, dest, opts);
if (rw < 1 || rh < 1) return dest;
const sbuf = this.data;
const dbuf = dest.data;
const sf = this.format.toABGR;
const df = dest.format.fromABGR;
const blitRow = this.format !== dest.format ? (si, di) => {
for (let xx = 0; xx < rw; xx++) {
dbuf[di + xx] = df(sf(sbuf[si + xx]));
}
} : (si, di) => dbuf.set(sbuf.subarray(si, si + rw), di);
for (let si = (sx | 0) + (sy | 0) * sw, di = (dx | 0) + (dy | 0) * dw, yy = 0; yy < rh; yy++, si += sw, di += dw) {
blitRow(si, di);
}
return dest;
}
blitCanvas(canvas, opts = {}) {
__blitCanvas(this, canvas, opts);
}
setImageData(idata) {
ensureImageDataSize(idata, this.width, this.height);
const src = new Uint32Array(idata.data.buffer);
const dest = this.data;
const fmt = this.format.fromABGR;
for (let i = src.length; i-- > 0; ) {
dest[i] = fmt(src[i]);
}
return this;
}
toImageData(idata) {
idata = ensureImageData(idata, this.width, this.height);
const dest = new Uint32Array(idata.data.buffer);
const src = this.data;
const fmt = this.format.toABGR;
for (let i = dest.length; i-- > 0; ) {
dest[i] = fmt(src[i]);
}
return idata;
}
getRegion(x, y, width, height, fmt) {
const [sx, sy, w, h] = __clampRegion(
x,
y,
width,
height,
this.width,
this.height
);
if (w < 1 || h < 1) return;
return this.blit(new IntBuffer(w, h, fmt || this.format), {
sx,
sy,
w,
h
});
}
getChannel(id) {
const chan = ensureChannel(this.format, id);
const buf = new IntBuffer(this.width, this.height, {
type: uintTypeForBits(chan.size),
size: chan.size,
channels: [{ size: chan.size, lane: Lane.RED }],
fromABGR: __compileGrayFromABGR(chan.size),
toABGR: __compileGrayToABGR(chan.size)
});
const src = this.data;
const dest = buf.data;
const get = chan.int;
for (let i = src.length; i-- > 0; ) {
dest[i] = get(src[i]);
}
return buf;
}
setChannel(id, src) {
const chan = ensureChannel(this.format, id);
const dbuf = this.data;
const set = chan.setInt;
if (isNumber(src)) {
__setChannelUni(dbuf, src, set);
} else {
const sbuf = src.data;
const schan = src.format.channels[0];
ensureSize(sbuf, this.width, this.height);
if (chan.size === schan.size) {
__setChannelSame(dbuf, sbuf, schan.int, set);
} else {
__setChannelConvert(
dbuf,
sbuf,
this.format.fromABGR,
src.format.toABGR,
chan.maskA
);
}
}
return this;
}
invert() {
const { data, format } = this;
const mask = Math.pow(2, format.size - format.alpha) - 1;
for (let i = data.length; i-- > 0; ) {
data[i] ^= mask;
}
return this;
}
premultiply() {
ensureAlpha(this.format);
__transformABGR(this.data, this.format, premultiplyInt);
return this;
}
postmultiply() {
__transformABGR(this.data, this.format, postmultiplyInt);
return this;
}
isPremultiplied() {
const pix = this.data;
const to = this.format.toABGR;
for (let i = pix.length; i-- > 0; ) {
if (!isPremultipliedInt(to(pix[i]))) {
return false;
}
}
return true;
}
forEach(f) {
const pix = this.data;
for (let i = pix.length; i-- > 0; ) {
pix[i] = f(pix[i], i);
}
return this;
}
fill(x) {
this.data.fill(x);
}
/**
* Flips image horizontally.
*/
flipX() {
const { data, width } = this;
for (let i = 0; i < data.length; i += width) {
data.subarray(i, i + width).reverse();
}
return this;
}
/**
* Flips image vertically.
*/
flipY() {
const { data, width } = this;
const tmp = typedArray(this.format.type, width);
for (let i = 0, j = data.length - width; i < j; i += width, j -= width) {
tmp.set(data.subarray(i, i + width));
data.copyWithin(i, j, j + width);
data.set(tmp, j);
}
return this;
}
rotateByID(id) {
return id > 0 ? this[ROT_IDS[id - 1]]() : this;
}
rotateCW() {
const { width, height } = this;
const h1 = height - 1;
this._rotate((x, y) => x * height + h1 - y);
this.size[0] = height;
this.size[1] = width;
return this;
}
rotateCCW() {
const { width, height } = this;
const w1 = width - 1;
this._rotate((x, y) => (w1 - x) * height + y);
this.size[0] = height;
this.size[1] = width;
return this;
}
rotate180() {
const { width, height } = this;
const w1 = width - 1;
const h1 = height - 1;
this._rotate((x, y) => (h1 - y) * width + w1 - x);
return this;
}
/**
* Returns scaled version of this buffer using given sampler or filter
* (default: `"linear"`) for interpolation. Syntax sugar for
* {@link IntBuffer.resize}.
*
* @param scale -
*/
scale(scale, sampler = "linear") {
assert(scale > 0, `scale must be > 0`);
return this.resize(this.width * scale, this.height * scale, sampler);
}
resize(w, h, sampler = "linear") {
w |= 0;
h |= 0;
assert(w > 0 && h > 0, `target width & height must be > 0`);
const dest = intBuffer(w, h, this.format);
const dpix = dest.data;
const scaleX = w > 0 ? this.width / w : 0;
const scaleY = h > 0 ? this.height / h : 0;
sampler = isString(sampler) ? defSampler(this, sampler, "repeat") : sampler;
for (let y = 0, i = 0; y < h; y++) {
const yy = y * scaleY;
for (let x = 0; x < w; x++, i++) {
dpix[i] = sampler(x * scaleX, yy);
}
}
return dest;
}
upsize() {
const { width, height, data } = this;
const dest = new IntBuffer(width * 2, height * 2, this.format);
const dpix = dest.data;
for (let y = 0, si = 0; y < height; y++) {
for (let x = 0, di = y * width * 4; x < width; x++, si++, di += 2) {
dpix[di] = data[si];
}
}
return dest;
}
_rotate(idxFn) {
const { data, format, width, height } = this;
const tmp = typedArray(format.type, width * height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
tmp[idxFn(x, y)] = data[y * width + x];
}
}
this.data = tmp;
}
};
IntBuffer = __decorateClass([
IGrid2DMixin
], IntBuffer);
export {
IntBuffer,
intBuffer,
intBufferFromCanvas,
intBufferFromImage
};