@thi.ng/pixel
Advanced tools
Comparing version 0.6.1 to 0.7.1
92
api.d.ts
@@ -1,2 +0,2 @@ | ||
import type { Fn, Fn2, IObjectOf, NumericArray, TypedArray, UintType } from "@thi.ng/api"; | ||
import type { FloatArray, Fn, Fn2, Fn3, FnN, IObjectOf, NumericArray, TypedArray, UintType } from "@thi.ng/api"; | ||
/** | ||
@@ -299,2 +299,92 @@ * ABGR 8bit lane/channel IDs | ||
} | ||
export declare type PoolTemplate = Fn3<string[], number, number, string>; | ||
export interface ConvolutionKernelSpec { | ||
/** | ||
* Kernel coefficients. | ||
*/ | ||
spec: NumericArray; | ||
/** | ||
* Kernel size. If given as number, expands to `[size, size]`. | ||
*/ | ||
size: number | [number, number]; | ||
} | ||
export interface PoolKernelSpec { | ||
/** | ||
* Code template function for {@link defPoolKernel}. | ||
*/ | ||
pool: PoolTemplate; | ||
/** | ||
* Kernel size. If given as number, expands to `[size, size]`. | ||
*/ | ||
size: number | [number, number]; | ||
} | ||
export interface KernelFnSpec { | ||
/** | ||
* Kernel factory. | ||
*/ | ||
fn: Fn<IPixelBuffer<FloatArray, NumericArray>, FnN>; | ||
/** | ||
* Kernel size. If given as number, expands to `[size, size]`. | ||
*/ | ||
size: number | [number, number]; | ||
} | ||
export declare type KernelSpec = ConvolutionKernelSpec | PoolKernelSpec | KernelFnSpec; | ||
export interface ConvolveOpts { | ||
/** | ||
* Convolution kernel details/implementation. | ||
*/ | ||
kernel: KernelSpec; | ||
/** | ||
* Channel ID to convolve. | ||
* | ||
* @defaultValue 0 | ||
*/ | ||
channel?: number; | ||
/** | ||
* If true, the result image will be same size as source image with empty | ||
* (padded) border pixels. | ||
* | ||
* @defaultValue true | ||
*/ | ||
pad?: boolean; | ||
/** | ||
* Result scale factor | ||
* | ||
* @defaultValue 1 | ||
*/ | ||
scale?: number; | ||
/** | ||
* Step size to process only every nth pixel. | ||
* | ||
* @defaultValue 1 | ||
*/ | ||
stride?: number | [number, number]; | ||
} | ||
export interface NormalMapOpts { | ||
/** | ||
* Channel ID to use for gradient extraction in source image. | ||
* | ||
* @defaultValue 0 | ||
*/ | ||
channel: number; | ||
/** | ||
* Step size (aka number of pixels) between left/right, top/bottom | ||
* neighbors. | ||
* | ||
* @defaultValue 0 | ||
*/ | ||
step: number; | ||
/** | ||
* Result gradient scale factor(s). | ||
* | ||
* @defaultValue 1 | ||
*/ | ||
scale: number | [number, number]; | ||
/** | ||
* Z-axis value to use in blue channel of normal map. | ||
* | ||
* @defaultValue 1 | ||
*/ | ||
z: number; | ||
} | ||
//# sourceMappingURL=api.d.ts.map |
@@ -6,2 +6,38 @@ # Change Log | ||
## [0.7.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.7.0...@thi.ng/pixel@0.7.1) (2021-03-03) | ||
**Note:** Version bump only for package @thi.ng/pixel | ||
# [0.7.0](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.6.1...@thi.ng/pixel@0.7.0) (2021-03-03) | ||
### Bug Fixes | ||
* **pixel:** add clamping for float->ABGR conversion ([41540e0](https://github.com/thi-ng/umbrella/commit/41540e085b2261208e44e6f25b327e3371eae2df)) | ||
* **pixel:** fix POOL_NEAREST index ([b98d05d](https://github.com/thi-ng/umbrella/commit/b98d05d7827d98d3971bdbcd562735b96fa9b7ec)) | ||
### Features | ||
* **pixel:** add 5x5 kernel presets ([56f96f4](https://github.com/thi-ng/umbrella/commit/56f96f4842e6a57087a565a8e5ce82e590da7d66)) | ||
* **pixel:** add convolve() & preset kernels ([6a31dc3](https://github.com/thi-ng/umbrella/commit/6a31dc38f3f0ae48853d899420d0fbebcc6b8678)) | ||
* **pixel:** add defKernel() kernel fn codegen ([25b97a3](https://github.com/thi-ng/umbrella/commit/25b97a341fa54ee8a82e3083fcb85a8061db8b1f)) | ||
* **pixel:** add defLargeKernel(), conv presets ([9c71165](https://github.com/thi-ng/umbrella/commit/9c71165adb71103fa88a5486987f270fecd2f439)) | ||
* **pixel:** add gradientImage() & FLOAT_NORMAL format ([78683b7](https://github.com/thi-ng/umbrella/commit/78683b701418bf184b2a1327cfd5e50397d687e0)) | ||
* **pixel:** add IEmpty impls for Float/PackedBuffer ([46ac1a1](https://github.com/thi-ng/umbrella/commit/46ac1a1906b256eefab0934efea300c67db7ea28)) | ||
* **pixel:** add normalMap(), add more kernels ([f32686d](https://github.com/thi-ng/umbrella/commit/f32686d463ffcb49b37e9b1b811ff5de06b58fed)) | ||
* **pixel:** add POOL_THRESHOLD preset ([5f1c1de](https://github.com/thi-ng/umbrella/commit/5f1c1dea87251f8a584cbe94d83784e7e4cc31a5)) | ||
* **pixel:** add step size support for normalMap() ([ab72a79](https://github.com/thi-ng/umbrella/commit/ab72a79532baab3f07f53419cb5970e90e97e0dd)) | ||
* **pixel:** add/update buffer factory fns ([ba38e13](https://github.com/thi-ng/umbrella/commit/ba38e137c6913d048bb4d678137241ee179d160c)) | ||
* **pixel:** update PackedBuffer.fromCanvas() ([3bdb086](https://github.com/thi-ng/umbrella/commit/3bdb0860bcd35a0475e83ebe948847f1ecd42db6)) | ||
* **pixel:** update/extend/refactor convolveChannel/Image() ([6692865](https://github.com/thi-ng/umbrella/commit/6692865d5facb75bf667056afa9cfee93ade2da6)) | ||
## [0.6.1](https://github.com/thi-ng/umbrella/compare/@thi.ng/pixel@0.6.0...@thi.ng/pixel@0.6.1) (2021-02-20) | ||
@@ -8,0 +44,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { Fn, NumericArray } from "@thi.ng/api"; | ||
import { Fn, ICopy, IEmpty, NumericArray } from "@thi.ng/api"; | ||
import type { BlendFnFloat, BlitOpts, FloatFormat, FloatFormatSpec, IPixelBuffer, PackedFormat } from "./api"; | ||
@@ -12,4 +12,5 @@ import { PackedBuffer } from "./packed"; | ||
*/ | ||
export declare const floatBuffer: (w: number, h: number, fmt: FloatFormat | FloatFormatSpec, pixels?: Float32Array | undefined) => FloatBuffer; | ||
export declare class FloatBuffer implements IPixelBuffer<Float32Array, NumericArray> { | ||
export declare function floatBuffer(w: number, h: number, fmt: FloatFormat | FloatFormatSpec, pixels?: Float32Array): FloatBuffer; | ||
export declare function floatBuffer(src: PackedBuffer, fmt: FloatFormat | FloatFormatSpec): FloatBuffer; | ||
export declare class FloatBuffer implements IPixelBuffer<Float32Array, NumericArray>, ICopy<FloatBuffer>, IEmpty<FloatBuffer> { | ||
/** | ||
@@ -36,2 +37,3 @@ * Creates a new `FloatBuffer` from given {@link PackedBuffer} and using | ||
copy(): FloatBuffer; | ||
empty(): FloatBuffer; | ||
getAt(x: number, y: number): NumericArray; | ||
@@ -38,0 +40,0 @@ setAt(x: number, y: number, col: NumericArray): this; |
21
float.js
@@ -8,11 +8,9 @@ import { assert } from "@thi.ng/api"; | ||
import { clampRegion, ensureChannel, ensureSize, prepRegions } from "./utils"; | ||
/** | ||
* Syntax sugar for {@link FloatBuffer} ctor. | ||
* | ||
* @param w - | ||
* @param h - | ||
* @param fmt - | ||
* @param pixels - | ||
*/ | ||
export const floatBuffer = (w, h, fmt, pixels) => new FloatBuffer(w, h, fmt, pixels); | ||
export function floatBuffer(...args) { | ||
return args[0] instanceof PackedBuffer | ||
? // @ts-ignore | ||
FloatBuffer.fromPacked(...args) | ||
: // @ts-ignore | ||
new FloatBuffer(...args); | ||
} | ||
export class FloatBuffer { | ||
@@ -59,6 +57,9 @@ constructor(w, h, fmt, pixels) { | ||
copy() { | ||
const dest = new FloatBuffer(this.width, this.height, this.format); | ||
const dest = this.empty(); | ||
dest.pixels.set(this.pixels); | ||
return dest; | ||
} | ||
empty() { | ||
return new FloatBuffer(this.width, this.height, this.format); | ||
} | ||
getAt(x, y) { | ||
@@ -65,0 +66,0 @@ const { width, stride } = this; |
@@ -0,1 +1,2 @@ | ||
import { clamp01 } from "@thi.ng/math"; | ||
import { Lane } from "../api"; | ||
@@ -11,3 +12,3 @@ import { luminanceABGR } from "../utils"; | ||
} | ||
const to = (col, i) => ((col[i] * 0xff + 0.5) | 0) << chanShift[chan[i]]; | ||
const to = (col, i) => ((clamp01(col[i]) * 0xff + 0.5) | 0) << chanShift[chan[i]]; | ||
const from = (col, i) => ((col >>> chanShift[chan[i]]) & 0xff) / 0xff; | ||
@@ -42,3 +43,3 @@ switch (chan.length) { | ||
const defConvert1Gray = (res) => { | ||
res.toABGR = (col) => ((((col[0] * 0xff + 0.5) | 0) * 0x010101) | 0xff000000) >>> 0; | ||
res.toABGR = (col) => ((((clamp01(col[0]) * 0xff + 0.5) | 0) * 0x010101) | 0xff000000) >>> 0; | ||
res.fromABGR = (col, out = []) => ((out[0] = luminanceABGR(col) / 0xff), out); | ||
@@ -63,3 +64,3 @@ }; | ||
res.toABGR = (col) => { | ||
let out = ((col[gray] * 0xff + 0.5) | 0) * 0x010101; | ||
let out = ((clamp01(col[gray]) * 0xff + 0.5) | 0) * 0x010101; | ||
out |= ((col[alpha] * 0xff + 0.5) | 0) << 24; | ||
@@ -66,0 +67,0 @@ return out >>> 0; |
export * from "./api"; | ||
export * from "./canvas"; | ||
export * from "./codegen"; | ||
export * from "./convolve"; | ||
export * from "./dither"; | ||
export * from "./float"; | ||
export * from "./normal-map"; | ||
export * from "./packed"; | ||
@@ -25,4 +27,5 @@ export * from "./utils"; | ||
export * from "./format/float-hsva"; | ||
export * from "./format/float-norm"; | ||
export * from "./format/float-rgb"; | ||
export * from "./format/float-rgba"; | ||
//# sourceMappingURL=index.d.ts.map |
export * from "./api"; | ||
export * from "./canvas"; | ||
export * from "./codegen"; | ||
export * from "./convolve"; | ||
export * from "./dither"; | ||
export * from "./float"; | ||
export * from "./normal-map"; | ||
export * from "./packed"; | ||
@@ -25,3 +27,4 @@ export * from "./utils"; | ||
export * from "./format/float-hsva"; | ||
export * from "./format/float-norm"; | ||
export * from "./format/float-rgb"; | ||
export * from "./format/float-rgba"; |
389
lib/index.js
@@ -126,2 +126,8 @@ 'use strict'; | ||
}; | ||
const asVec = (x) => checks.isNumber(x) ? [x, x] : x; | ||
const asIntVec = (x) => { | ||
const v = asVec(x); | ||
return [v[0] | 0, v[1] | 0]; | ||
}; | ||
const range = (n) => new Uint8Array(n).map((_, i) => i); | ||
@@ -178,39 +184,2 @@ const compileLShift = (x, shift) => shift > 0 | ||
const init = (x, y, size, val, step, mat) => { | ||
if (size === 1) { | ||
!mat[y] && (mat[y] = []); | ||
mat[y][x] = val; | ||
return mat; | ||
} | ||
size >>= 1; | ||
const step4 = step << 2; | ||
init(x, y, size, val, step4, mat); | ||
init(x + size, y + size, size, val + step, step4, mat); | ||
init(x + size, y, size, val + step * 2, step4, mat); | ||
init(x, y + size, size, val + step * 3, step4, mat); | ||
return mat; | ||
}; | ||
const defBayer = (size) => ({ | ||
mat: init(0, 0, size, 0, 1, []), | ||
invSize: 1 / (size * size), | ||
mask: size - 1, | ||
}); | ||
const orderedDither = ({ mat, mask, invSize }, dsteps, drange, srange, x, y, val) => { | ||
val = | ||
(dsteps * (val / srange) + mat[y & mask][x & mask] * invSize - 0.5) | 0; | ||
dsteps--; | ||
return math.clamp(val, 0, dsteps) * ((drange - 1) / dsteps); | ||
}; | ||
const ditherPixels = (dest, src, width, height, mat, dsteps, drange, srange) => { | ||
!dest && (dest = src.slice()); | ||
drange--; | ||
for (let y = 0, i = 0; y < height; y++) { | ||
for (let x = 0; x < width; x++) { | ||
dest[i] = orderedDither(mat, dsteps, drange, srange, x, y, src[i]); | ||
i++; | ||
} | ||
} | ||
return dest; | ||
}; | ||
const defFloatFormat = (fmt) => { | ||
@@ -224,3 +193,3 @@ const chan = fmt.channels; | ||
} | ||
const to = (col, i) => ((col[i] * 0xff + 0.5) | 0) << chanShift[chan[i]]; | ||
const to = (col, i) => ((math.clamp01(col[i]) * 0xff + 0.5) | 0) << chanShift[chan[i]]; | ||
const from = (col, i) => ((col >>> chanShift[chan[i]]) & 0xff) / 0xff; | ||
@@ -255,3 +224,3 @@ switch (chan.length) { | ||
const defConvert1Gray = (res) => { | ||
res.toABGR = (col) => ((((col[0] * 0xff + 0.5) | 0) * 0x010101) | 0xff000000) >>> 0; | ||
res.toABGR = (col) => ((((math.clamp01(col[0]) * 0xff + 0.5) | 0) * 0x010101) | 0xff000000) >>> 0; | ||
res.fromABGR = (col, out = []) => ((out[0] = luminanceABGR(col) / 0xff), out); | ||
@@ -276,3 +245,3 @@ }; | ||
res.toABGR = (col) => { | ||
let out = ((col[gray] * 0xff + 0.5) | 0) * 0x010101; | ||
let out = ((math.clamp01(col[gray]) * 0xff + 0.5) | 0) * 0x010101; | ||
out |= ((col[alpha] * 0xff + 0.5) | 0) << 24; | ||
@@ -325,2 +294,39 @@ return out >>> 0; | ||
const init = (x, y, size, val, step, mat) => { | ||
if (size === 1) { | ||
!mat[y] && (mat[y] = []); | ||
mat[y][x] = val; | ||
return mat; | ||
} | ||
size >>= 1; | ||
const step4 = step << 2; | ||
init(x, y, size, val, step4, mat); | ||
init(x + size, y + size, size, val + step, step4, mat); | ||
init(x + size, y, size, val + step * 2, step4, mat); | ||
init(x, y + size, size, val + step * 3, step4, mat); | ||
return mat; | ||
}; | ||
const defBayer = (size) => ({ | ||
mat: init(0, 0, size, 0, 1, []), | ||
invSize: 1 / (size * size), | ||
mask: size - 1, | ||
}); | ||
const orderedDither = ({ mat, mask, invSize }, dsteps, drange, srange, x, y, val) => { | ||
val = | ||
(dsteps * (val / srange) + mat[y & mask][x & mask] * invSize - 0.5) | 0; | ||
dsteps--; | ||
return math.clamp(val, 0, dsteps) * ((drange - 1) / dsteps); | ||
}; | ||
const ditherPixels = (dest, src, width, height, mat, dsteps, drange, srange) => { | ||
!dest && (dest = src.slice()); | ||
drange--; | ||
for (let y = 0, i = 0; y < height; y++) { | ||
for (let x = 0; x < width; x++) { | ||
dest[i] = orderedDither(mat, dsteps, drange, srange, x, y, src[i]); | ||
i++; | ||
} | ||
} | ||
return dest; | ||
}; | ||
const defChannel = (ch, idx, shift) => { | ||
@@ -380,3 +386,8 @@ const num = 1 << ch.size; | ||
const packedBuffer = (w, h, fmt, pixels) => new PackedBuffer(w, h, fmt, pixels); | ||
function packedBuffer(...args) { | ||
return args[0] instanceof PackedBuffer | ||
? args[0].as(args[1]) | ||
: | ||
new PackedBuffer(...args); | ||
} | ||
const buffer = packedBuffer; | ||
@@ -393,16 +404,22 @@ class PackedBuffer { | ||
static fromImage(img, fmt, width, height = width) { | ||
const ctx = imageCanvas(img, width, height); | ||
const w = ctx.canvas.width; | ||
const h = ctx.canvas.height; | ||
const src = new Uint32Array(ctx.ctx.getImageData(0, 0, w, h).data.buffer); | ||
const dest = api.typedArray(fmt.type, w * h); | ||
const from = fmt.fromABGR; | ||
for (let i = dest.length; --i >= 0;) { | ||
dest[i] = from(src[i]); | ||
return PackedBuffer.fromCanvas(imageCanvas(img, width, height).canvas, fmt); | ||
} | ||
static fromCanvas(canvas, fmt = ABGR8888) { | ||
const ctx = canvasPixels(canvas); | ||
const w = canvas.width; | ||
const h = canvas.height; | ||
let dest; | ||
if (fmt === ABGR8888) { | ||
dest = ctx.pixels; | ||
} | ||
else { | ||
dest = api.typedArray(fmt.type, w * h); | ||
const src = ctx.pixels; | ||
const from = fmt.fromABGR; | ||
for (let i = dest.length; --i >= 0;) { | ||
dest[i] = from(src[i]); | ||
} | ||
} | ||
return new PackedBuffer(w, h, fmt, dest); | ||
} | ||
static fromCanvas(canvas) { | ||
return new PackedBuffer(canvas.width, canvas.height, ABGR8888, canvasPixels(canvas).pixels); | ||
} | ||
get stride() { | ||
@@ -415,6 +432,9 @@ return 1; | ||
copy() { | ||
const dest = new PackedBuffer(this.width, this.height, this.format); | ||
const dest = this.empty(); | ||
dest.pixels.set(this.pixels); | ||
return dest; | ||
} | ||
empty() { | ||
return new PackedBuffer(this.width, this.height, this.format); | ||
} | ||
getAt(x, y) { | ||
@@ -621,3 +641,9 @@ return x >= 0 && x < this.width && y >= 0 && y < this.height | ||
const floatBuffer = (w, h, fmt, pixels) => new FloatBuffer(w, h, fmt, pixels); | ||
function floatBuffer(...args) { | ||
return args[0] instanceof PackedBuffer | ||
? | ||
FloatBuffer.fromPacked(...args) | ||
: | ||
new FloatBuffer(...args); | ||
} | ||
class FloatBuffer { | ||
@@ -654,6 +680,9 @@ constructor(w, h, fmt, pixels) { | ||
copy() { | ||
const dest = new FloatBuffer(this.width, this.height, this.format); | ||
const dest = this.empty(); | ||
dest.pixels.set(this.pixels); | ||
return dest; | ||
} | ||
empty() { | ||
return new FloatBuffer(this.width, this.height, this.format); | ||
} | ||
getAt(x, y) { | ||
@@ -818,2 +847,228 @@ const { width, stride } = this; | ||
const convolveChannel = (src, opts) => convolve(initConvolve(src, opts)); | ||
const convolveImage = (src, opts) => { | ||
const state = initConvolve(src, opts); | ||
const dest = new FloatBuffer(state.dwidth, state.dheight, src.format); | ||
for (let channel of opts.channels || range(src.format.channels.length)) { | ||
dest.setChannel(channel, convolve(Object.assign(Object.assign({}, state), { channel }))); | ||
} | ||
return dest; | ||
}; | ||
const convolve = ({ channel, dest, dwidth, kernel, kh2, kw2, pad, rowStride, scale, src, srcStride, strideX, strideY, }) => { | ||
ensureChannel(src.format, channel); | ||
const maxY = src.height - kh2; | ||
const maxX = src.width - kw2; | ||
const padX = pad && strideX === 1; | ||
const padY = pad && strideY === 1; | ||
const dpix = dest.pixels; | ||
for (let sy = kh2, dy = padY ? kh2 : 0; sy < maxY; sy += strideY, dy++) { | ||
for (let x = kw2, i = sy * rowStride + x * srcStride + channel, j = dy * dwidth + (padX ? x : 0); x < maxX; x += strideX, i += strideX * srcStride, j++) { | ||
dpix[j] = kernel(i) * scale; | ||
} | ||
} | ||
return dest; | ||
}; | ||
const initKernel = (src, kernel, kw, kh) => (checks.isFunction(kernel.fn) | ||
? kernel.fn | ||
: defKernel(kernel.spec || | ||
kernel.pool, kw, kh))(src); | ||
const initConvolve = (src, opts) => { | ||
const { kernel, channel, stride: sampleStride, pad, scale } = Object.assign({ channel: 0, pad: true, scale: 1, stride: 1 }, opts); | ||
const size = kernel.size; | ||
const [kw, kh] = asIntVec(size); | ||
const [strideX, strideY] = asIntVec(sampleStride); | ||
api.assert(strideX >= 1 && strideY >= 1, `illegal stride: ${sampleStride}`); | ||
const { width, height, stride: srcStride, rowStride } = src; | ||
api.assert(kw >= 0 && kw <= width && kh >= 0 && kh <= height, `invalid kernel size: ${size}`); | ||
const dwidth = destSize(width, strideX, kw, pad); | ||
const dheight = destSize(height, strideY, kh, pad); | ||
const dest = new FloatBuffer(dwidth, dheight, FLOAT_GRAY); | ||
return { | ||
channel, | ||
dest, | ||
dheight, | ||
dwidth, | ||
kernel: initKernel(src, kernel, kw, kh), | ||
kh2: kh >> 1, | ||
kw2: kw >> 1, | ||
pad, | ||
rowStride, | ||
scale, | ||
src, | ||
srcStride, | ||
strideX, | ||
strideY, | ||
}; | ||
}; | ||
const destSize = (orig, res, k, pad) => pad ? Math.floor(orig / res) : Math.ceil((orig - k + 1) / res); | ||
const defKernel = (tpl, w, h) => { | ||
if (w * h > 1024 && !checks.isFunction(tpl)) | ||
return defLargeKernel(tpl, w, h); | ||
const isPool = checks.isFunction(tpl); | ||
const prefix = []; | ||
const prefixI = []; | ||
const body = []; | ||
const kvars = []; | ||
const h2 = h >> 1; | ||
const w2 = w >> 1; | ||
for (let y = 0, i = 0; y < h; y++) { | ||
const yy = y - h2; | ||
const row = []; | ||
for (let x = 0; x < w; x++, i++) { | ||
const kv = `k${y}_${x}`; | ||
kvars.push(kv); | ||
const xx = x - w2; | ||
const idx = (yy !== 0 ? `i${y}` : `i`) + (xx !== 0 ? `+x${x}` : ""); | ||
isPool | ||
? row.push(`pix[${idx}]`) | ||
: tpl[i] !== 0 && row.push(`${kv}*pix[${idx}]`); | ||
y === 0 && xx !== 0 && prefix.push(`const x${x} = ${xx} * stride;`); | ||
} | ||
row.length && body.push(...row); | ||
if (yy !== 0) { | ||
prefix.push(`const y${y} = ${yy} * rowStride;`); | ||
prefixI.push(`const i${y} = i + y${y};`); | ||
} | ||
} | ||
const decls = isPool | ||
? "" | ||
: `const [${kvars.join(", ")}] = [${tpl.join(", ")}];`; | ||
const inner = isPool ? tpl(body, w, h) : body.join(" + "); | ||
const fnBody = [ | ||
decls, | ||
"const { pixels: pix, stride, rowStride } = src;", | ||
...prefix, | ||
"return (i) => {", | ||
...prefixI, | ||
`return ${inner};`, | ||
"}", | ||
].join("\n"); | ||
return new Function("src", fnBody); | ||
}; | ||
const defLargeKernel = (kernel, w, h) => { | ||
const h2 = h >> 1; | ||
const w2 = w >> 1; | ||
return (src) => { | ||
const { pixels, rowStride, stride } = src; | ||
return (i) => { | ||
let sum = 0; | ||
for (let y = -h2, k = 0; y <= h2; y++) { | ||
for (let x = -w2, ii = i + y * rowStride; x <= w2; x++, ii += stride, k++) { | ||
sum += kernel[k] * pixels[ii]; | ||
} | ||
} | ||
return sum; | ||
}; | ||
}; | ||
}; | ||
const POOL_NEAREST = (body, w, h) => body[(h >> 1) * w + (w >> 1)]; | ||
const POOL_MEAN = (body, w, h) => `(${body.join("+")})*${1 / (w * h)}`; | ||
const POOL_MIN = (body) => `Math.min(${body.join(",")})`; | ||
const POOL_MAX = (body) => `Math.max(${body.join(",")})`; | ||
const POOL_THRESHOLD = (bias = 0) => (body, w, h) => { | ||
const center = POOL_NEAREST(body, w, h); | ||
const mean = `(${body.join("+")})/${w * h}`; | ||
return `(${center} - ${mean} + ${bias}) < 0 ? 0 : 1`; | ||
}; | ||
const SOBEL_X = { | ||
spec: [-1, -2, -1, 0, 0, 0, 1, 2, 1], | ||
size: 3, | ||
}; | ||
const SOBEL_Y = { | ||
spec: [-1, 0, 1, -2, 0, 2, -1, 0, 1], | ||
size: 3, | ||
}; | ||
const SHARPEN3 = { | ||
spec: [0, -1, 0, -1, 5, -1, 0, -1, 0], | ||
size: 3, | ||
}; | ||
const HIGHPASS3 = { | ||
spec: [-1, -1, -1, -1, 9, -1, -1, -1, -1], | ||
size: 3, | ||
}; | ||
const BOX_BLUR3 = { | ||
pool: POOL_MEAN, | ||
size: 3, | ||
}; | ||
const BOX_BLUR5 = { | ||
pool: POOL_MEAN, | ||
size: 5, | ||
}; | ||
const GAUSSIAN_BLUR3 = { | ||
spec: [1 / 16, 1 / 8, 1 / 16, 1 / 8, 1 / 4, 1 / 8, 1 / 16, 1 / 8, 1 / 16], | ||
size: 3, | ||
}; | ||
const GAUSSIAN_BLUR5 = { | ||
spec: [ | ||
1 / 256, 1 / 64, 3 / 128, 1 / 64, 1 / 256, | ||
1 / 64, 1 / 16, 3 / 32, 1 / 16, 1 / 64, | ||
3 / 128, 3 / 32, 9 / 64, 3 / 32, 3 / 128, | ||
1 / 64, 1 / 16, 3 / 32, 1 / 16, 1 / 64, | ||
1 / 256, 1 / 64, 3 / 128, 1 / 64, 1 / 256, | ||
], | ||
size: 5, | ||
}; | ||
const GAUSSIAN = (r) => { | ||
r |= 0; | ||
api.assert(r > 0, `invalid kernel radius: ${r}`); | ||
const sigma = -1 / (2 * (Math.hypot(r, r) / 3) ** 2); | ||
const res = []; | ||
let sum = 0; | ||
for (let y = -r; y <= r; y++) { | ||
for (let x = -r; x <= r; x++) { | ||
const g = Math.exp((x * x + y * y) * sigma); | ||
res.push(g); | ||
sum += g; | ||
} | ||
} | ||
return { spec: res.map((x) => x / sum), size: r * 2 + 1 }; | ||
}; | ||
const UNSHARP_MASK5 = { | ||
spec: [ | ||
-1 / 256, -1 / 64, -3 / 128, -1 / 64, -1 / 256, | ||
-1 / 64, -1 / 16, -3 / 32, -1 / 16, -1 / 64, | ||
-3 / 128, -3 / 32, 119 / 64, -3 / 32, -3 / 128, | ||
-1 / 64, -1 / 16, -3 / 32, -1 / 16, -1 / 64, | ||
-1 / 256, -1 / 64, -3 / 128, -1 / 64, -1 / 256, | ||
], | ||
size: 5, | ||
}; | ||
const from = (x) => x / 127.5 - 1; | ||
const to = (x, shift) => math.clamp(x * 127.5 + 128, 0, 255) << shift; | ||
const FLOAT_NORMAL = { | ||
__float: true, | ||
alpha: false, | ||
gray: false, | ||
channels: [exports.Lane.RED, exports.Lane.GREEN, exports.Lane.BLUE], | ||
shift: { 3: 0, 2: 8, 1: 16 }, | ||
size: 3, | ||
fromABGR: (src) => [ | ||
from(src & 0xff), | ||
from((src >> 8) & 0xff), | ||
from((src >> 16) & 0xff), | ||
], | ||
toABGR: (src) => to(src[0], 0) | to(src[1], 8) | to(src[2], 16) | 0xff000000, | ||
}; | ||
const normalMap = (src, opts) => { | ||
const { channel, step, scale, z } = Object.assign({ channel: 0, step: 0, scale: 1, z: 1 }, opts); | ||
ensureChannel(src.format, channel); | ||
const spec = [-1, ...new Array(step).fill(0), 1]; | ||
const [sx, sy] = asVec(scale); | ||
const dest = new FloatBuffer(src.width, src.height, FLOAT_NORMAL); | ||
dest.setChannel(0, convolveChannel(src, { | ||
kernel: { spec, size: [step + 2, 1] }, | ||
scale: sx, | ||
channel, | ||
})); | ||
dest.setChannel(1, convolveChannel(src, { | ||
kernel: { spec, size: [1, step + 2] }, | ||
scale: sy, | ||
channel, | ||
})); | ||
dest.setChannel(2, z); | ||
return dest; | ||
}; | ||
const ALPHA8 = defPackedFormat({ | ||
@@ -1015,8 +1270,14 @@ type: "u8", | ||
exports.BGR888 = BGR888; | ||
exports.BOX_BLUR3 = BOX_BLUR3; | ||
exports.BOX_BLUR5 = BOX_BLUR5; | ||
exports.FLOAT_GRAY = FLOAT_GRAY; | ||
exports.FLOAT_GRAY_ALPHA = FLOAT_GRAY_ALPHA; | ||
exports.FLOAT_HSVA = FLOAT_HSVA; | ||
exports.FLOAT_NORMAL = FLOAT_NORMAL; | ||
exports.FLOAT_RGB = FLOAT_RGB; | ||
exports.FLOAT_RGBA = FLOAT_RGBA; | ||
exports.FloatBuffer = FloatBuffer; | ||
exports.GAUSSIAN = GAUSSIAN; | ||
exports.GAUSSIAN_BLUR3 = GAUSSIAN_BLUR3; | ||
exports.GAUSSIAN_BLUR5 = GAUSSIAN_BLUR5; | ||
exports.GRAY16 = GRAY16; | ||
@@ -1026,5 +1287,17 @@ exports.GRAY8 = GRAY8; | ||
exports.GRAY_ALPHA8 = GRAY_ALPHA8; | ||
exports.HIGHPASS3 = HIGHPASS3; | ||
exports.POOL_MAX = POOL_MAX; | ||
exports.POOL_MEAN = POOL_MEAN; | ||
exports.POOL_MIN = POOL_MIN; | ||
exports.POOL_NEAREST = POOL_NEAREST; | ||
exports.POOL_THRESHOLD = POOL_THRESHOLD; | ||
exports.PackedBuffer = PackedBuffer; | ||
exports.RGB565 = RGB565; | ||
exports.RGB888 = RGB888; | ||
exports.SHARPEN3 = SHARPEN3; | ||
exports.SOBEL_X = SOBEL_X; | ||
exports.SOBEL_Y = SOBEL_Y; | ||
exports.UNSHARP_MASK5 = UNSHARP_MASK5; | ||
exports.asIntVec = asIntVec; | ||
exports.asVec = asVec; | ||
exports.buffer = buffer; | ||
@@ -1038,4 +1311,8 @@ exports.canvas2d = canvas2d; | ||
exports.compileToABGR = compileToABGR; | ||
exports.convolveChannel = convolveChannel; | ||
exports.convolveImage = convolveImage; | ||
exports.defBayer = defBayer; | ||
exports.defFloatFormat = defFloatFormat; | ||
exports.defKernel = defKernel; | ||
exports.defLargeKernel = defLargeKernel; | ||
exports.defPackedFormat = defPackedFormat; | ||
@@ -1049,5 +1326,7 @@ exports.ditherPixels = ditherPixels; | ||
exports.luminanceABGR = luminanceABGR; | ||
exports.normalMap = normalMap; | ||
exports.orderedDither = orderedDither; | ||
exports.packedBuffer = packedBuffer; | ||
exports.prepRegions = prepRegions; | ||
exports.range = range; | ||
exports.setChannelConvert = setChannelConvert; | ||
@@ -1054,0 +1333,0 @@ exports.setChannelSame = setChannelSame; |
@@ -1,1 +0,1 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@thi.ng/checks"),require("@thi.ng/api"),require("@thi.ng/math"),require("@thi.ng/porter-duff"),require("@thi.ng/binary")):"function"==typeof define&&define.amd?define(["exports","@thi.ng/checks","@thi.ng/api","@thi.ng/math","@thi.ng/porter-duff","@thi.ng/binary"],e):e(((t="undefined"!=typeof globalThis?globalThis:t||self).thi=t.thi||{},t.thi.ng=t.thi.ng||{},t.thi.ng.pixel={}),t.thi.ng.checks,t.thi.ng.api,t.thi.ng.math,t.thi.ng.porterDuff,t.thi.ng.binary)}(this,(function(t,e,s,i,n,r){"use strict";var a,h,l;t.Lane=void 0,(a=t.Lane||(t.Lane={}))[a.ALPHA=0]="ALPHA",a[a.BLUE=1]="BLUE",a[a.GREEN=2]="GREEN",a[a.RED=3]="RED",t.Wrap=void 0,(h=t.Wrap||(t.Wrap={}))[h.NONE=0]="NONE",h[h.U=1]="U",h[h.V=2]="V",h[h.UV=3]="UV",t.Filter=void 0,(l=t.Filter||(t.Filter={}))[l.NEAREST=0]="NEAREST",l[l.LINEAR=1]="LINEAR";const o=(t,e=t,s)=>{const i=document.createElement("canvas");return i.width=t,i.height=e,s&&s.appendChild(i),{canvas:i,ctx:i.getContext("2d")}};function c(t,s){let i,n;if(e.isNumber(t)){const e=o(t,s);i=e.canvas,n=e.ctx}else i=t,n=i.getContext("2d");const r=n.getImageData(0,0,i.width,i.height);return{canvas:i,ctx:n,img:r,pixels:new Uint32Array(r.data.buffer)}}const f=(t,s,i=s,n)=>{const r=e.isNumber(s)?o(s,i,n):o(t.width,t.height,n);return r.ctx.drawImage(t,0,0,r.canvas.width,r.canvas.height),r},p=(t,e,i,n=1)=>s.assert(t.length>=e*i*n,"pixel buffer too small"),u=(t,e)=>{const i=t.channels[e];return s.assert(null!=i,`invalid channel ID: ${e}`),i},m=t=>(29*(t>>>16&255)+150*(t>>>8&255)+76*(255&t))/255,d=(t,e,s,n,r,a,h=0,l=0)=>(s|=0,n|=0,(t|=0)<0&&(s+=t,h-=t,t=0),(e|=0)<0&&(n+=e,l-=e,e=0),[t,e,i.clamp(s,0,r-t),i.clamp(n,0,a-e),h,l]),A=(t,e,s={})=>{let i,n,r,a,h,l,o=t.width,c=e.width;return[i,n,h,l]=d(s.sx||0,s.sy||0,s.w||o,s.h||t.height,o,t.height),[r,a,h,l,i,n]=d(s.dx||0,s.dy||0,h,l,c,e.height,i,n),{sx:i,sy:n,dx:r,dy:a,rw:h,rh:l}},g=(t,e,s)=>{for(let i=t.length;--i>=0;)t[i]=s(t[i],e)},R=(t,e,s,i)=>{for(let n=t.length;--n>=0;)t[n]=i(t[n],s(e[n]))},w=(t,e,s,i,n)=>{const r=~n;for(let a=t.length;--a>=0;)t[a]=t[a]&r|s(i(e[a]))&n},x=(t,e,s)=>{const i=e.fromABGR,n=e.toABGR;for(let e=t.length;--e>=0;)t[e]=i(s(n(t[e])))},L=(t,e)=>e>0?`(${t} << ${e})`:e<0?`(${t} >>> ${-e})`:`${t}`,G=(t,e)=>L(t,-e),y=t=>`0x${t.toString(16)}`,B=t=>{const e=(1<<t)-1;return new Function("luma",`return (x) => ${G("luma(x)",8-t)} & ${e};`)(m)},z=t=>{let e;if(8!==t){const s=(1<<t)-1;e=`(((x & ${s}) * ${255/s}) | 0)`}else e="x";return new Function("x",`return 0xff000000 | (${e} * 0x010101);`)},E=t=>new Function("x","return ("+t.map((t=>{const e=t.abgrShift+(8-t.size);return`(${G("x",e)} & ${y(t.maskA)})`})).join(" | ")+") >>> 0;"),b=(t,e)=>{const s=t.map((t=>{if(8!==t.size){const e=t.mask0,s=255/e,i=G("x",t.shift);return L(`((${i} & ${e}) * ${s})`,24-8*t.lane)}return L(`(x & ${y(t.maskA)})`,t.abgrShift)})).join(" | ");return new Function("x",`return (${e?"":"0xff000000 | "}${s}) >>> 0;`)},v=(t,e,s,i,n,r)=>{if(1===s)return!r[e]&&(r[e]=[]),r[e][t]=i,r;const a=n<<2;return v(t,e,s>>=1,i,a,r),v(t+s,e+s,s,i+n,a,r),v(t+s,e,s,i+2*n,a,r),v(t,e+s,s,i+3*n,a,r),r},D=t=>({mat:v(0,0,t,0,1,[]),invSize:1/(t*t),mask:t-1}),F=({mat:t,mask:e,invSize:s},n,r,a,h,l,o)=>(o=n*(o/a)+t[l&e][h&e]*s-.5|0,n--,i.clamp(o,0,n)*((r-1)/n)),P=t=>{const e=t.channels,s=e.reduce(((t,e)=>(t[e]=3-e<<3,t)),{}),i=Object.assign(Object.assign({},t),{size:e.length,shift:s,__float:!0});if(t.convert)return Object.assign(i,t.convert),i;const n=(t,i)=>(255*t[i]+.5|0)<<s[e[i]],r=(t,i)=>(t>>>s[e[i]]&255)/255;switch(e.length){case 1:t.gray?C(i):N(i,r,n);break;case 2:t.gray?_(i,r):S(i,r,n);break;case 3:U(i,r,n);break;case 4:k(i,r,n)}return i},N=(t,e,s)=>{t.toABGR=e=>{let i=t.alpha?0:4278190080;return i|=s(e,0),i>>>0},t.fromABGR=(t,s=[])=>(s[0]=e(t,0),s)},C=t=>{t.toABGR=t=>(65793*(255*t[0]+.5|0)|4278190080)>>>0,t.fromABGR=(t,e=[])=>(e[0]=m(t)/255,e)},S=(t,e,s)=>{t.toABGR=e=>{let i=t.alpha?0:4278190080;return i|=s(e,0),i|=s(e,1),i>>>0},t.fromABGR=(t,s=[])=>(s[0]=e(t,0),s[1]=e(t,1),s)},_=(e,s)=>{const i=~~(e.channels[0]===t.Lane.ALPHA),n=1^i;e.toABGR=t=>{let e=65793*(255*t[i]+.5|0);return e|=(255*t[n]+.5|0)<<24,e>>>0},e.fromABGR=(t,e=[])=>(e[i]=m(t)/255,e[n]=s(t,n),e)},U=(t,e,s)=>{t.toABGR=e=>{let i=t.alpha?0:4278190080;return i|=s(e,0),i|=s(e,1),i|=s(e,2),i>>>0},t.fromABGR=(t,s=[])=>(s[0]=e(t,0),s[1]=e(t,1),s[2]=e(t,2),s)},k=(t,e,s)=>{t.toABGR=e=>{let i=t.alpha?0:4278190080;return i|=s(e,0),i|=s(e,1),i|=s(e,2),i|=s(e,3),i>>>0},t.fromABGR=(t,s=[])=>(s[0]=e(t,0),s[1]=e(t,1),s[2]=e(t,2),s[3]=e(t,3),s)},$=P({gray:!0,channels:[t.Lane.RED]}),I=t=>{s.assert(t.channels.length>0,"no channel specs given");const e=t.channels.reduce((([t,e],s,n)=>(e-=s.size,t.push(((t,e,s)=>{const n=1<<t.size,r=n-1,a=r<<s>>>0,h=~a>>>0,l=null!=t.lane?t.lane:e,o=t=>t>>>s&r,c=(t,e)=>t&h|(e&r)<<s;return{size:t.size,abgrShift:24-8*l-s,lane:l,shift:s,mask0:r,maskA:a,int:o,setInt:c,float:t=>o(t)/r,setFloat:(t,e)=>c(t,i.clamp01(e)*r),dither:(t,e,s,i,r)=>F(t,e,n,n,s,i,r)}})(s,n,e)),[t,e])),[[],t.size])[0];return{__packed:!0,type:t.type,size:t.size,alpha:t.alpha||0,channels:e,fromABGR:t.fromABGR||E(e),toABGR:t.toABGR||b(e,!!t.alpha)}},H=I({type:"u32",size:32,alpha:8,channels:[{size:8,lane:t.Lane.ALPHA},{size:8,lane:t.Lane.BLUE},{size:8,lane:t.Lane.GREEN},{size:8,lane:t.Lane.RED}],fromABGR:t=>t,toABGR:t=>t}),O=(t,e,s,i)=>new j(t,e,s,i),T=O;class j{constructor(t,e,i,n){this.width=t,this.height=e,this.format=i.__packed?i:I(i),this.pixels=n||s.typedArray(i.type,t*e)}static fromImage(t,e,i,n=i){const r=f(t,i,n),a=r.canvas.width,h=r.canvas.height,l=new Uint32Array(r.ctx.getImageData(0,0,a,h).data.buffer),o=s.typedArray(e.type,a*h),c=e.fromABGR;for(let t=o.length;--t>=0;)o[t]=c(l[t]);return new j(a,h,e,o)}static fromCanvas(t){return new j(t.width,t.height,H,c(t).pixels)}get stride(){return 1}as(t){return this.getRegion(0,0,this.width,this.height,t)}copy(){const t=new j(this.width,this.height,this.format);return t.pixels.set(this.pixels),t}getAt(t,e){return t>=0&&t<this.width&&e>=0&&e<this.height?this.pixels[(0|t)+(0|e)*this.width]:0}setAt(t,e,s){return t>=0&&t<this.width&&e>=0&&e<this.height&&(this.pixels[(0|t)+(0|e)*this.width]=s),this}getChannelAt(t,e,s,i=!1){const n=u(this.format,s),r=this.getAt(t,e);return i?n.float(r):n.int(r)}setChannelAt(t,e,s,i,n=!1){const r=u(this.format,s),a=this.getAt(t,e);return n?r.setFloat(a,i):r.setInt(a,i),this}blend(t,e,s){let i=this.width,n=e.width;const{sx:r,sy:a,dx:h,dy:l,rw:o,rh:c}=A(this,e,s);if(o<1||c<1)return e;const f=this.pixels,p=e.pixels,u=this.format.toABGR,m=e.format.toABGR,d=e.format.fromABGR;for(let e=(0|r)+(0|a)*i,s=(0|h)+(0|l)*n,A=0;A<c;A++,e+=i,s+=n)for(let i=0;i<o;i++)p[s+i]=d(t(u(f[e+i]),m(p[s+i])));return e}blit(t,e){let s=this.width,i=t.width;const{sx:n,sy:r,dx:a,dy:h,rw:l,rh:o}=A(this,t,e);if(l<1||o<1)return t;const c=this.pixels,f=t.pixels,p=this.format.toABGR,u=t.format.fromABGR,m=this.format!==t.format?(t,e)=>{for(let s=0;s<l;s++)f[e+s]=u(p(c[t+s]))}:(t,e)=>f.set(c.subarray(t,t+l),e);for(let t=(0|n)+(0|r)*s,e=(0|a)+(0|h)*i,l=0;l<o;l++,t+=s,e+=i)m(t,e);return t}blitCanvas(t,e=0,s=0){const i=t.getContext("2d"),n=new ImageData(this.width,this.height),r=new Uint32Array(n.data.buffer),a=this.pixels,h=this.format.toABGR;for(let t=r.length;--t>=0;)r[t]=h(a[t]);return i.putImageData(n,e,s),t}getRegion(t,e,s,i,n){const[r,a,h,l]=d(t,e,s,i,this.width,this.height);return this.blit(new j(h,l,n||this.format),{sx:r,sy:a,w:h,h:l})}getChannel(e){const i=u(this.format,e),n=new j(this.width,this.height,{type:s.uintTypeForBits(i.size),size:i.size,channels:[{size:i.size,lane:t.Lane.RED}],fromABGR:B(i.size),toABGR:z(i.size)}),r=this.pixels,a=n.pixels,h=i.int;for(let t=r.length;--t>=0;)a[t]=h(r[t]);return n}setChannel(t,s){const i=u(this.format,t),n=this.pixels,r=i.setInt;if(e.isNumber(s))g(n,s,r);else{const t=s.pixels,e=s.format.channels[0];p(t,this.width,this.height),i.size===e.size?R(n,t,e.int,r):w(n,t,this.format.fromABGR,s.format.toABGR,i.maskA)}return this}invert(){const{format:t,pixels:e}=this,s=Math.pow(2,t.size-t.alpha)-1;for(let t=e.length;--t>=0;)e[t]^=s;return this}premultiply(){return x(this.pixels,this.format,n.premultiplyInt),this}postmultiply(){return x(this.pixels,this.format,n.postmultiplyInt),this}isPremultiplied(){const t=this.pixels,e=this.format.toABGR;for(let s=t.length;--s>=0;)if(!n.isPremultipliedInt(e(t[s])))return!1;return!0}forEach(t){const e=this.pixels;for(let s=e.length;--s>=0;)e[s]=t(e[s]);return this}dither(t,s){const{pixels:i,format:n,width:r}=this,a=e.isNumber(s)?new Array(n.channels.length).fill(s):s,h=e.isNumber(t)?D(t):t;for(let t=0,e=i.length,s=n.channels.length,l=0,o=0;t<e;t++){let e=i[t];for(let t=0;t<s;t++){const s=n.channels[t],i=a[t];i>0&&(e=s.setInt(e,s.dither(h,i,l,o,s.int(e))))}i[t]=e,++l===r&&(l=0,o++)}return this}flipY(){const{pixels:t,width:e}=this,i=s.typedArray(this.format.type,e);for(let s=0,n=t.length-e;s<n;s+=e,n-=e)i.set(t.subarray(s,s+e)),t.copyWithin(s,n,n+e),t.set(i,n);return this}downsample(t){t|=0;const{width:e,height:s,pixels:i}=this,n=new j(e/t|0,s/t|0,this.format),{width:r,height:a,pixels:h}=n;for(let s=0,n=0;s<a;s++)for(let a=0,l=s*t*e;a<r;a++,n++,l+=t)h[n]=i[l];return n}}class Y{constructor(t,e,s,i){this.width=t,this.height=e,this.format=s.__float?s:P(s),this.stride=s.channels.length,this.rowStride=t*this.stride,this.pixels=i||new Float32Array(t*e*this.stride),this.__empty=Object.freeze(new Array(this.stride).fill(0))}static fromPacked(t,e){const s=new Y(t.width,t.height,e),{pixels:i,format:n,stride:r}=s,{pixels:a,format:h}=t;for(let t=a.length;--t>=0;)i.set(n.fromABGR(h.toABGR(a[t])),t*r);return s}as(t){const{width:e,height:s,stride:i,pixels:n,format:r}=this,a=new j(e,s,t),h=a.pixels;for(let e=0,s=0,a=n.length;e<a;e+=i,s++)h[s]=t.fromABGR(r.toABGR(n.subarray(e,e+i)));return a}copy(){const t=new Y(this.width,this.height,this.format);return t.pixels.set(this.pixels),t}getAt(t,e){const{width:s,stride:i}=this;if(t>=0&&t<s&&e>=0&&e<this.height){const s=(0|t)*i+(0|e)*this.rowStride;return this.pixels.subarray(s,s+i)}return this.__empty}setAt(t,e,s){return t>=0&&t<this.width&&e>=0&&e<this.height&&this.pixels.set(s,(0|t)*this.stride+(0|e)*this.rowStride),this}getChannelAt(t,e,s){u(this.format,s);const{width:i,stride:n}=this;if(t>=0&&t<i&&e>=0&&e<this.height)return this.pixels[(0|t)*n+(0|e)*this.rowStride+s]}setChannelAt(t,e,s,i){u(this.format,s);const{width:n,stride:r}=this;return t>=0&&t<n&&e>=0&&e<this.height&&(this.pixels[(0|t)*r+(0|e)*this.rowStride+s]=i),this}getChannel(t){u(this.format,t);const{pixels:e,stride:s}=this,n=new Float32Array(this.width*this.height);for(let r=t,a=0,h=e.length;r<h;r+=s,a++)n[a]=i.clamp01(e[r]);return new Y(this.width,this.height,$,n)}setChannel(t,s){u(this.format,t);const{pixels:i,stride:n}=this;if(e.isNumber(s))for(let e=t,r=i.length;e<r;e+=n)i[e]=s;else{const{pixels:e,stride:r}=s;p(e,this.width,this.height,r);for(let s=t,a=0,h=i.length;s<h;s+=n,a+=r)i[s]=e[a]}return this}blend(t,e,s){this.ensureFormat(e);const{sx:i,sy:n,dx:r,dy:a,rw:h,rh:l}=A(this,e,s);if(h<1||l<1)return e;const o=this.pixels,c=e.pixels,f=this.rowStride,p=e.rowStride,u=this.stride;for(let e=(0|i)*u+(0|n)*f,s=(0|r)*u+(0|a)*p,m=0;m<l;m++,e+=f,s+=p)for(let i=h,n=e,r=s;--i>=0;n+=u,r+=u){const e=c.subarray(r,r+u);t(e,o.subarray(n,n+u),e)}return e}blit(t,e){this.ensureFormat(t);const{sx:s,sy:i,dx:n,dy:r,rw:a,rh:h}=A(this,t,e);if(a<1||h<1)return t;const l=this.pixels,o=t.pixels,c=this.rowStride,f=t.rowStride,p=a*this.stride;for(let t=(0|s)*this.stride+(0|i)*c,e=(0|n)*this.stride+(0|r)*f,a=0;a<h;a++,t+=c,e+=f)o.set(l.subarray(t,t+p),e);return t}blitCanvas(t,e=0,s=0){const i=t.getContext("2d"),n=new ImageData(this.width,this.height),r=new Uint32Array(n.data.buffer),{stride:a,pixels:h,format:l}=this;for(let t=0,e=0,s=h.length;t<s;t+=a,e++)r[e]=l.toABGR(h.subarray(t,t+a));return i.putImageData(n,e,s),t}getRegion(t,e,s,i){const[n,r,a,h]=d(t,e,s,i,this.width,this.height);return this.blit(new Y(a,h,this.format),{sx:n,sy:r,w:a,h})}forEach(t){const{pixels:e,stride:s}=this;for(let i=0,n=e.length;i<n;i+s)t(e.subarray(i,i+s));return this}clamp(){const t=this.pixels;for(let e=t.length;--e>=0;)t[e]=i.clamp01(t[e]);return this}clampChannel(t){u(this.format,t);const{pixels:e,stride:s}=this;for(let n=t,r=e.length;n<r;n+=s)e[n]=i.clamp01(e[n])}flipY(){const{pixels:t,rowStride:e}=this,s=new Float32Array(e);for(let i=0,n=t.length-e;i<n;i+=e,n-=e)s.set(t.subarray(i,i+e)),t.copyWithin(i,n,n+e),t.set(s,n);return this}downsample(t){t|=0;const{width:e,height:s,stride:i,pixels:n}=this,r=new Y(e/t|0,s/t|0,this.format),{width:a,height:h,pixels:l}=r;t*=i;for(let s=0,r=0;s<h;s++)for(let h=0,o=s*t*e;h<a;h++,r+=i,o+=t)l.set(n.subarray(o,o+i),r);return r}ensureFormat(t){s.assert(t.format===this.format,"dest buffer format must be same as src")}}const q=I({type:"u8",size:8,alpha:8,channels:[{size:8,lane:0}]}),V=I({type:"u16",size:16,alpha:1,channels:[{size:1,lane:t.Lane.ALPHA},{size:5,lane:t.Lane.RED},{size:5,lane:t.Lane.GREEN},{size:5,lane:t.Lane.BLUE}]}),W=I({type:"u16",size:16,alpha:4,channels:[{size:4,lane:t.Lane.ALPHA},{size:4,lane:t.Lane.RED},{size:4,lane:t.Lane.GREEN},{size:4,lane:t.Lane.BLUE}]}),M=I({type:"u32",size:32,alpha:8,channels:[{size:8,lane:t.Lane.ALPHA},{size:8,lane:t.Lane.RED},{size:8,lane:t.Lane.GREEN},{size:8,lane:t.Lane.BLUE}],fromABGR:r.swapLane13,toABGR:r.swapLane13}),J=I({type:"u32",size:24,channels:[{size:8,lane:t.Lane.BLUE},{size:8,lane:t.Lane.GREEN},{size:8,lane:t.Lane.RED}],fromABGR:t=>16777215&t,toABGR:t=>4278190080|t}),K=I({type:"u16",size:16,channels:[{size:16,lane:t.Lane.RED}],fromABGR:t=>257*(m(t)+.5|0),toABGR:t=>(4278190080|65793*(t>>>8))>>>0}),Q=I({type:"u32",size:32,channels:[{size:8,lane:t.Lane.ALPHA},{size:16,lane:t.Lane.RED}],fromABGR:t=>257*(m(t)+.5|0)|257*(t>>>8&16711680),toABGR:t=>(4278190080&t|65793*(t>>>8&255))>>>0}),X=I({type:"u8",size:8,channels:[{size:8,lane:t.Lane.RED}],fromABGR:t=>m(t),toABGR:t=>(4278190080|65793*(255&t))>>>0}),Z=I({type:"u16",size:16,alpha:8,channels:[{size:8,lane:t.Lane.ALPHA},{size:8,lane:t.Lane.RED}],fromABGR:t=>m(t)|t>>>16&65280,toABGR:t=>((65280&t)<<16|65793*(255&t))>>>0}),tt=I({type:"u16",size:16,channels:[{size:5,lane:t.Lane.RED},{size:6,lane:t.Lane.GREEN},{size:5,lane:t.Lane.BLUE}]}),et=I({type:"u32",size:24,channels:[{size:8,lane:t.Lane.RED},{size:8,lane:t.Lane.GREEN},{size:8,lane:t.Lane.BLUE}]}),st=P({gray:!0,alpha:!0,channels:[t.Lane.RED,t.Lane.ALPHA]}),it=Math.abs,nt=Math.min,rt=P({alpha:!0,channels:[t.Lane.RED,t.Lane.GREEN,t.Lane.BLUE,t.Lane.ALPHA],convert:{fromABGR:(t,e=[])=>{const s=(t>>>24&255)/255,n=(t>>>16&255)/255,r=(t>>>8&255)/255,a=(255&t)/255;let h,l,o,c,f,p,u,m;r<n?(h=n,l=r,o=-1,c=2/3):(h=r,l=n,o=0,c=-1/3),a<h?(f=h,p=l,u=c,m=a):(f=a,p=l,u=o,m=h);const d=f-nt(p,m);return f=i.clamp01(f),e[0]=i.clamp01(it((m-p)/(6*d+i.EPS)+u)),e[1]=i.clamp01(d/(f+i.EPS)),e[2]=f,e[3]=s,e},toABGR:t=>{const e=6*t[0],s=t[1],n=255*t[2],r=255*t[3],a=((i.clamp01(it(e-3)-1)-1)*s+1)*n,h=((i.clamp01(2-it(e-2))-1)*s+1)*n;return(r<<24|((i.clamp01(2-it(e-4))-1)*s+1)*n<<16|h<<8|a)>>>0}}}),at=P({channels:[t.Lane.RED,t.Lane.GREEN,t.Lane.BLUE]}),ht=P({alpha:!0,channels:[t.Lane.RED,t.Lane.GREEN,t.Lane.BLUE,t.Lane.ALPHA]});t.ABGR8888=H,t.ALPHA8=q,t.ARGB1555=V,t.ARGB4444=W,t.ARGB8888=M,t.BGR888=J,t.FLOAT_GRAY=$,t.FLOAT_GRAY_ALPHA=st,t.FLOAT_HSVA=rt,t.FLOAT_RGB=at,t.FLOAT_RGBA=ht,t.FloatBuffer=Y,t.GRAY16=K,t.GRAY8=X,t.GRAY_ALPHA16=Q,t.GRAY_ALPHA8=Z,t.PackedBuffer=j,t.RGB565=tt,t.RGB888=et,t.buffer=T,t.canvas2d=o,t.canvasPixels=c,t.clampRegion=d,t.compileFromABGR=E,t.compileGrayFromABGR=B,t.compileGrayToABGR=z,t.compileToABGR=b,t.defBayer=D,t.defFloatFormat=P,t.defPackedFormat=I,t.ditherPixels=(t,e,s,i,n,r,a,h)=>{!t&&(t=e.slice()),a--;for(let l=0,o=0;l<i;l++)for(let i=0;i<s;i++)t[o]=F(n,r,a,h,i,l,e[o]),o++;return t},t.ensureChannel=u,t.ensureSize=p,t.floatBuffer=(t,e,s,i)=>new Y(t,e,s,i),t.imageCanvas=f,t.imagePromise=async t=>{const e=new Image;return e.src=t,await e.decode(),e},t.luminanceABGR=m,t.orderedDither=F,t.packedBuffer=O,t.prepRegions=A,t.setChannelConvert=w,t.setChannelSame=R,t.setChannelUni=g,t.transformABGR=x,Object.defineProperty(t,"__esModule",{value:!0})})); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@thi.ng/checks"),require("@thi.ng/api"),require("@thi.ng/math"),require("@thi.ng/porter-duff"),require("@thi.ng/binary")):"function"==typeof define&&define.amd?define(["exports","@thi.ng/checks","@thi.ng/api","@thi.ng/math","@thi.ng/porter-duff","@thi.ng/binary"],e):e(((t="undefined"!=typeof globalThis?globalThis:t||self).thi=t.thi||{},t.thi.ng=t.thi.ng||{},t.thi.ng.pixel={}),t.thi.ng.checks,t.thi.ng.api,t.thi.ng.math,t.thi.ng.porterDuff,t.thi.ng.binary)}(this,(function(t,e,s,n,i,r){"use strict";var a,h,l;t.Lane=void 0,(a=t.Lane||(t.Lane={}))[a.ALPHA=0]="ALPHA",a[a.BLUE=1]="BLUE",a[a.GREEN=2]="GREEN",a[a.RED=3]="RED",t.Wrap=void 0,(h=t.Wrap||(t.Wrap={}))[h.NONE=0]="NONE",h[h.U=1]="U",h[h.V=2]="V",h[h.UV=3]="UV",t.Filter=void 0,(l=t.Filter||(t.Filter={}))[l.NEAREST=0]="NEAREST",l[l.LINEAR=1]="LINEAR";const o=(t,e=t,s)=>{const n=document.createElement("canvas");return n.width=t,n.height=e,s&&s.appendChild(n),{canvas:n,ctx:n.getContext("2d")}};function c(t,s){let n,i;if(e.isNumber(t)){const e=o(t,s);n=e.canvas,i=e.ctx}else n=t,i=n.getContext("2d");const r=i.getImageData(0,0,n.width,n.height);return{canvas:n,ctx:i,img:r,pixels:new Uint32Array(r.data.buffer)}}const f=(t,s,n=s,i)=>{const r=e.isNumber(s)?o(s,n,i):o(t.width,t.height,i);return r.ctx.drawImage(t,0,0,r.canvas.width,r.canvas.height),r},p=(t,e,n,i=1)=>s.assert(t.length>=e*n*i,"pixel buffer too small"),u=(t,e)=>{const n=t.channels[e];return s.assert(null!=n,`invalid channel ID: ${e}`),n},d=t=>(29*(t>>>16&255)+150*(t>>>8&255)+76*(255&t))/255,m=(t,e,s,i,r,a,h=0,l=0)=>(s|=0,i|=0,(t|=0)<0&&(s+=t,h-=t,t=0),(e|=0)<0&&(i+=e,l-=e,e=0),[t,e,n.clamp(s,0,r-t),n.clamp(i,0,a-e),h,l]),A=(t,e,s={})=>{let n,i,r,a,h,l,o=t.width,c=e.width;return[n,i,h,l]=m(s.sx||0,s.sy||0,s.w||o,s.h||t.height,o,t.height),[r,a,h,l,n,i]=m(s.dx||0,s.dy||0,h,l,c,e.height,n,i),{sx:n,sy:i,dx:r,dy:a,rw:h,rh:l}},g=(t,e,s)=>{for(let n=t.length;--n>=0;)t[n]=s(t[n],e)},R=(t,e,s,n)=>{for(let i=t.length;--i>=0;)t[i]=n(t[i],s(e[i]))},w=(t,e,s,n,i)=>{const r=~i;for(let a=t.length;--a>=0;)t[a]=t[a]&r|s(n(e[a]))&i},L=(t,e,s)=>{const n=e.fromABGR,i=e.toABGR;for(let e=t.length;--e>=0;)t[e]=n(s(i(t[e])))},x=t=>e.isNumber(t)?[t,t]:t,B=t=>{const e=x(t);return[0|e[0],0|e[1]]},y=t=>new Uint8Array(t).map(((t,e)=>e)),G=(t,e)=>e>0?`(${t} << ${e})`:e<0?`(${t} >>> ${-e})`:`${t}`,z=(t,e)=>G(t,-e),E=t=>`0x${t.toString(16)}`,b=t=>{const e=(1<<t)-1;return new Function("luma",`return (x) => ${z("luma(x)",8-t)} & ${e};`)(d)},$=t=>{let e;if(8!==t){const s=(1<<t)-1;e=`(((x & ${s}) * ${255/s}) | 0)`}else e="x";return new Function("x",`return 0xff000000 | (${e} * 0x010101);`)},S=t=>new Function("x","return ("+t.map((t=>{const e=t.abgrShift+(8-t.size);return`(${z("x",e)} & ${E(t.maskA)})`})).join(" | ")+") >>> 0;"),_=(t,e)=>{const s=t.map((t=>{if(8!==t.size){const e=t.mask0,s=255/e,n=z("x",t.shift);return G(`((${n} & ${e}) * ${s})`,24-8*t.lane)}return G(`(x & ${E(t.maskA)})`,t.abgrShift)})).join(" | ");return new Function("x",`return (${e?"":"0xff000000 | "}${s}) >>> 0;`)},N=t=>{const e=t.channels,s=e.reduce(((t,e)=>(t[e]=3-e<<3,t)),{}),i=Object.assign(Object.assign({},t),{size:e.length,shift:s,__float:!0});if(t.convert)return Object.assign(i,t.convert),i;const r=(t,i)=>(255*n.clamp01(t[i])+.5|0)<<s[e[i]],a=(t,n)=>(t>>>s[e[n]]&255)/255;switch(e.length){case 1:t.gray?v(i):P(i,a,r);break;case 2:t.gray?O(i,a):k(i,a,r);break;case 3:F(i,a,r);break;case 4:U(i,a,r)}return i},P=(t,e,s)=>{t.toABGR=e=>{let n=t.alpha?0:4278190080;return n|=s(e,0),n>>>0},t.fromABGR=(t,s=[])=>(s[0]=e(t,0),s)},v=t=>{t.toABGR=t=>(65793*(255*n.clamp01(t[0])+.5|0)|4278190080)>>>0,t.fromABGR=(t,e=[])=>(e[0]=d(t)/255,e)},k=(t,e,s)=>{t.toABGR=e=>{let n=t.alpha?0:4278190080;return n|=s(e,0),n|=s(e,1),n>>>0},t.fromABGR=(t,s=[])=>(s[0]=e(t,0),s[1]=e(t,1),s)},O=(e,s)=>{const i=~~(e.channels[0]===t.Lane.ALPHA),r=1^i;e.toABGR=t=>{let e=65793*(255*n.clamp01(t[i])+.5|0);return e|=(255*t[r]+.5|0)<<24,e>>>0},e.fromABGR=(t,e=[])=>(e[i]=d(t)/255,e[r]=s(t,r),e)},F=(t,e,s)=>{t.toABGR=e=>{let n=t.alpha?0:4278190080;return n|=s(e,0),n|=s(e,1),n|=s(e,2),n>>>0},t.fromABGR=(t,s=[])=>(s[0]=e(t,0),s[1]=e(t,1),s[2]=e(t,2),s)},U=(t,e,s)=>{t.toABGR=e=>{let n=t.alpha?0:4278190080;return n|=s(e,0),n|=s(e,1),n|=s(e,2),n|=s(e,3),n>>>0},t.fromABGR=(t,s=[])=>(s[0]=e(t,0),s[1]=e(t,1),s[2]=e(t,2),s[3]=e(t,3),s)},C=N({gray:!0,channels:[t.Lane.RED]}),D=(t,e,s,n,i,r)=>{if(1===s)return!r[e]&&(r[e]=[]),r[e][t]=n,r;const a=i<<2;return D(t,e,s>>=1,n,a,r),D(t+s,e+s,s,n+i,a,r),D(t+s,e,s,n+2*i,a,r),D(t,e+s,s,n+3*i,a,r),r},I=t=>({mat:D(0,0,t,0,1,[]),invSize:1/(t*t),mask:t-1}),H=({mat:t,mask:e,invSize:s},i,r,a,h,l,o)=>(o=i*(o/a)+t[l&e][h&e]*s-.5|0,i--,n.clamp(o,0,i)*((r-1)/i)),j=t=>{s.assert(t.channels.length>0,"no channel specs given");const e=t.channels.reduce((([t,e],s,i)=>(e-=s.size,t.push(((t,e,s)=>{const i=1<<t.size,r=i-1,a=r<<s>>>0,h=~a>>>0,l=null!=t.lane?t.lane:e,o=t=>t>>>s&r,c=(t,e)=>t&h|(e&r)<<s;return{size:t.size,abgrShift:24-8*l-s,lane:l,shift:s,mask0:r,maskA:a,int:o,setInt:c,float:t=>o(t)/r,setFloat:(t,e)=>c(t,n.clamp01(e)*r),dither:(t,e,s,n,r)=>H(t,e,i,i,s,n,r)}})(s,i,e)),[t,e])),[[],t.size])[0];return{__packed:!0,type:t.type,size:t.size,alpha:t.alpha||0,channels:e,fromABGR:t.fromABGR||S(e),toABGR:t.toABGR||_(e,!!t.alpha)}},M=j({type:"u32",size:32,alpha:8,channels:[{size:8,lane:t.Lane.ALPHA},{size:8,lane:t.Lane.BLUE},{size:8,lane:t.Lane.GREEN},{size:8,lane:t.Lane.RED}],fromABGR:t=>t,toABGR:t=>t});function T(...t){return t[0]instanceof V?t[0].as(t[1]):new V(...t)}const Y=T;class V{constructor(t,e,n,i){this.width=t,this.height=e,this.format=n.__packed?n:j(n),this.pixels=i||s.typedArray(n.type,t*e)}static fromImage(t,e,s,n=s){return V.fromCanvas(f(t,s,n).canvas,e)}static fromCanvas(t,e=M){const n=c(t),i=t.width,r=t.height;let a;if(e===M)a=n.pixels;else{a=s.typedArray(e.type,i*r);const t=n.pixels,h=e.fromABGR;for(let e=a.length;--e>=0;)a[e]=h(t[e])}return new V(i,r,e,a)}get stride(){return 1}as(t){return this.getRegion(0,0,this.width,this.height,t)}copy(){const t=this.empty();return t.pixels.set(this.pixels),t}empty(){return new V(this.width,this.height,this.format)}getAt(t,e){return t>=0&&t<this.width&&e>=0&&e<this.height?this.pixels[(0|t)+(0|e)*this.width]:0}setAt(t,e,s){return t>=0&&t<this.width&&e>=0&&e<this.height&&(this.pixels[(0|t)+(0|e)*this.width]=s),this}getChannelAt(t,e,s,n=!1){const i=u(this.format,s),r=this.getAt(t,e);return n?i.float(r):i.int(r)}setChannelAt(t,e,s,n,i=!1){const r=u(this.format,s),a=this.getAt(t,e);return i?r.setFloat(a,n):r.setInt(a,n),this}blend(t,e,s){let n=this.width,i=e.width;const{sx:r,sy:a,dx:h,dy:l,rw:o,rh:c}=A(this,e,s);if(o<1||c<1)return e;const f=this.pixels,p=e.pixels,u=this.format.toABGR,d=e.format.toABGR,m=e.format.fromABGR;for(let e=(0|r)+(0|a)*n,s=(0|h)+(0|l)*i,A=0;A<c;A++,e+=n,s+=i)for(let n=0;n<o;n++)p[s+n]=m(t(u(f[e+n]),d(p[s+n])));return e}blit(t,e){let s=this.width,n=t.width;const{sx:i,sy:r,dx:a,dy:h,rw:l,rh:o}=A(this,t,e);if(l<1||o<1)return t;const c=this.pixels,f=t.pixels,p=this.format.toABGR,u=t.format.fromABGR,d=this.format!==t.format?(t,e)=>{for(let s=0;s<l;s++)f[e+s]=u(p(c[t+s]))}:(t,e)=>f.set(c.subarray(t,t+l),e);for(let t=(0|i)+(0|r)*s,e=(0|a)+(0|h)*n,l=0;l<o;l++,t+=s,e+=n)d(t,e);return t}blitCanvas(t,e=0,s=0){const n=t.getContext("2d"),i=new ImageData(this.width,this.height),r=new Uint32Array(i.data.buffer),a=this.pixels,h=this.format.toABGR;for(let t=r.length;--t>=0;)r[t]=h(a[t]);return n.putImageData(i,e,s),t}getRegion(t,e,s,n,i){const[r,a,h,l]=m(t,e,s,n,this.width,this.height);return this.blit(new V(h,l,i||this.format),{sx:r,sy:a,w:h,h:l})}getChannel(e){const n=u(this.format,e),i=new V(this.width,this.height,{type:s.uintTypeForBits(n.size),size:n.size,channels:[{size:n.size,lane:t.Lane.RED}],fromABGR:b(n.size),toABGR:$(n.size)}),r=this.pixels,a=i.pixels,h=n.int;for(let t=r.length;--t>=0;)a[t]=h(r[t]);return i}setChannel(t,s){const n=u(this.format,t),i=this.pixels,r=n.setInt;if(e.isNumber(s))g(i,s,r);else{const t=s.pixels,e=s.format.channels[0];p(t,this.width,this.height),n.size===e.size?R(i,t,e.int,r):w(i,t,this.format.fromABGR,s.format.toABGR,n.maskA)}return this}invert(){const{format:t,pixels:e}=this,s=Math.pow(2,t.size-t.alpha)-1;for(let t=e.length;--t>=0;)e[t]^=s;return this}premultiply(){return L(this.pixels,this.format,i.premultiplyInt),this}postmultiply(){return L(this.pixels,this.format,i.postmultiplyInt),this}isPremultiplied(){const t=this.pixels,e=this.format.toABGR;for(let s=t.length;--s>=0;)if(!i.isPremultipliedInt(e(t[s])))return!1;return!0}forEach(t){const e=this.pixels;for(let s=e.length;--s>=0;)e[s]=t(e[s]);return this}dither(t,s){const{pixels:n,format:i,width:r}=this,a=e.isNumber(s)?new Array(i.channels.length).fill(s):s,h=e.isNumber(t)?I(t):t;for(let t=0,e=n.length,s=i.channels.length,l=0,o=0;t<e;t++){let e=n[t];for(let t=0;t<s;t++){const s=i.channels[t],n=a[t];n>0&&(e=s.setInt(e,s.dither(h,n,l,o,s.int(e))))}n[t]=e,++l===r&&(l=0,o++)}return this}flipY(){const{pixels:t,width:e}=this,n=s.typedArray(this.format.type,e);for(let s=0,i=t.length-e;s<i;s+=e,i-=e)n.set(t.subarray(s,s+e)),t.copyWithin(s,i,i+e),t.set(n,i);return this}downsample(t){t|=0;const{width:e,height:s,pixels:n}=this,i=new V(e/t|0,s/t|0,this.format),{width:r,height:a,pixels:h}=i;for(let s=0,i=0;s<a;s++)for(let a=0,l=s*t*e;a<r;a++,i++,l+=t)h[i]=n[l];return i}}class X{constructor(t,e,s,n){this.width=t,this.height=e,this.format=s.__float?s:N(s),this.stride=s.channels.length,this.rowStride=t*this.stride,this.pixels=n||new Float32Array(t*e*this.stride),this.__empty=Object.freeze(new Array(this.stride).fill(0))}static fromPacked(t,e){const s=new X(t.width,t.height,e),{pixels:n,format:i,stride:r}=s,{pixels:a,format:h}=t;for(let t=a.length;--t>=0;)n.set(i.fromABGR(h.toABGR(a[t])),t*r);return s}as(t){const{width:e,height:s,stride:n,pixels:i,format:r}=this,a=new V(e,s,t),h=a.pixels;for(let e=0,s=0,a=i.length;e<a;e+=n,s++)h[s]=t.fromABGR(r.toABGR(i.subarray(e,e+n)));return a}copy(){const t=this.empty();return t.pixels.set(this.pixels),t}empty(){return new X(this.width,this.height,this.format)}getAt(t,e){const{width:s,stride:n}=this;if(t>=0&&t<s&&e>=0&&e<this.height){const s=(0|t)*n+(0|e)*this.rowStride;return this.pixels.subarray(s,s+n)}return this.__empty}setAt(t,e,s){return t>=0&&t<this.width&&e>=0&&e<this.height&&this.pixels.set(s,(0|t)*this.stride+(0|e)*this.rowStride),this}getChannelAt(t,e,s){u(this.format,s);const{width:n,stride:i}=this;if(t>=0&&t<n&&e>=0&&e<this.height)return this.pixels[(0|t)*i+(0|e)*this.rowStride+s]}setChannelAt(t,e,s,n){u(this.format,s);const{width:i,stride:r}=this;return t>=0&&t<i&&e>=0&&e<this.height&&(this.pixels[(0|t)*r+(0|e)*this.rowStride+s]=n),this}getChannel(t){u(this.format,t);const{pixels:e,stride:s}=this,i=new Float32Array(this.width*this.height);for(let r=t,a=0,h=e.length;r<h;r+=s,a++)i[a]=n.clamp01(e[r]);return new X(this.width,this.height,C,i)}setChannel(t,s){u(this.format,t);const{pixels:n,stride:i}=this;if(e.isNumber(s))for(let e=t,r=n.length;e<r;e+=i)n[e]=s;else{const{pixels:e,stride:r}=s;p(e,this.width,this.height,r);for(let s=t,a=0,h=n.length;s<h;s+=i,a+=r)n[s]=e[a]}return this}blend(t,e,s){this.ensureFormat(e);const{sx:n,sy:i,dx:r,dy:a,rw:h,rh:l}=A(this,e,s);if(h<1||l<1)return e;const o=this.pixels,c=e.pixels,f=this.rowStride,p=e.rowStride,u=this.stride;for(let e=(0|n)*u+(0|i)*f,s=(0|r)*u+(0|a)*p,d=0;d<l;d++,e+=f,s+=p)for(let n=h,i=e,r=s;--n>=0;i+=u,r+=u){const e=c.subarray(r,r+u);t(e,o.subarray(i,i+u),e)}return e}blit(t,e){this.ensureFormat(t);const{sx:s,sy:n,dx:i,dy:r,rw:a,rh:h}=A(this,t,e);if(a<1||h<1)return t;const l=this.pixels,o=t.pixels,c=this.rowStride,f=t.rowStride,p=a*this.stride;for(let t=(0|s)*this.stride+(0|n)*c,e=(0|i)*this.stride+(0|r)*f,a=0;a<h;a++,t+=c,e+=f)o.set(l.subarray(t,t+p),e);return t}blitCanvas(t,e=0,s=0){const n=t.getContext("2d"),i=new ImageData(this.width,this.height),r=new Uint32Array(i.data.buffer),{stride:a,pixels:h,format:l}=this;for(let t=0,e=0,s=h.length;t<s;t+=a,e++)r[e]=l.toABGR(h.subarray(t,t+a));return n.putImageData(i,e,s),t}getRegion(t,e,s,n){const[i,r,a,h]=m(t,e,s,n,this.width,this.height);return this.blit(new X(a,h,this.format),{sx:i,sy:r,w:a,h})}forEach(t){const{pixels:e,stride:s}=this;for(let n=0,i=e.length;n<i;n+s)t(e.subarray(n,n+s));return this}clamp(){const t=this.pixels;for(let e=t.length;--e>=0;)t[e]=n.clamp01(t[e]);return this}clampChannel(t){u(this.format,t);const{pixels:e,stride:s}=this;for(let i=t,r=e.length;i<r;i+=s)e[i]=n.clamp01(e[i])}flipY(){const{pixels:t,rowStride:e}=this,s=new Float32Array(e);for(let n=0,i=t.length-e;n<i;n+=e,i-=e)s.set(t.subarray(n,n+e)),t.copyWithin(n,i,i+e),t.set(s,i);return this}downsample(t){t|=0;const{width:e,height:s,stride:n,pixels:i}=this,r=new X(e/t|0,s/t|0,this.format),{width:a,height:h,pixels:l}=r;t*=n;for(let s=0,r=0;s<h;s++)for(let h=0,o=s*t*e;h<a;h++,r+=n,o+=t)l.set(i.subarray(o,o+n),r);return r}ensureFormat(t){s.assert(t.format===this.format,"dest buffer format must be same as src")}}const q=(t,e)=>W(J(t,e)),W=({channel:t,dest:e,dwidth:s,kernel:n,kh2:i,kw2:r,pad:a,rowStride:h,scale:l,src:o,srcStride:c,strideX:f,strideY:p})=>{u(o.format,t);const d=o.height-i,m=o.width-r,A=a&&1===f,g=a&&1===p,R=e.pixels;for(let e=i,a=g?i:0;e<d;e+=p,a++)for(let i=r,o=e*h+i*c+t,p=a*s+(A?i:0);i<m;i+=f,o+=f*c,p++)R[p]=n(o)*l;return e},K=(t,s,n,i)=>(e.isFunction(s.fn)?s.fn:Z(s.spec||s.pool,n,i))(t),J=(t,e)=>{const{kernel:n,channel:i,stride:r,pad:a,scale:h}=Object.assign({channel:0,pad:!0,scale:1,stride:1},e),l=n.size,[o,c]=B(l),[f,p]=B(r);s.assert(f>=1&&p>=1,`illegal stride: ${r}`);const{width:u,height:d,stride:m,rowStride:A}=t;s.assert(o>=0&&o<=u&&c>=0&&c<=d,`invalid kernel size: ${l}`);const g=Q(u,f,o,a),R=Q(d,p,c,a);return{channel:i,dest:new X(g,R,C),dheight:R,dwidth:g,kernel:K(t,n,o,c),kh2:c>>1,kw2:o>>1,pad:a,rowStride:A,scale:h,src:t,srcStride:m,strideX:f,strideY:p}},Q=(t,e,s,n)=>n?Math.floor(t/e):Math.ceil((t-s+1)/e),Z=(t,s,n)=>{if(s*n>1024&&!e.isFunction(t))return tt(t,s,n);const i=e.isFunction(t),r=[],a=[],h=[],l=[],o=n>>1,c=s>>1;for(let e=0,f=0;e<n;e++){const n=e-o,p=[];for(let a=0;a<s;a++,f++){const s=`k${e}_${a}`;l.push(s);const h=a-c,o=(0!==n?`i${e}`:"i")+(0!==h?`+x${a}`:"");i?p.push(`pix[${o}]`):0!==t[f]&&p.push(`${s}*pix[${o}]`),0===e&&0!==h&&r.push(`const x${a} = ${h} * stride;`)}p.length&&h.push(...p),0!==n&&(r.push(`const y${e} = ${n} * rowStride;`),a.push(`const i${e} = i + y${e};`))}const f=i?"":`const [${l.join(", ")}] = [${t.join(", ")}];`,p=i?t(h,s,n):h.join(" + "),u=[f,"const { pixels: pix, stride, rowStride } = src;",...r,"return (i) => {",...a,`return ${p};`,"}"].join("\n");return new Function("src",u)},tt=(t,e,s)=>{const n=s>>1,i=e>>1;return e=>{const{pixels:s,rowStride:r,stride:a}=e;return e=>{let h=0;for(let l=-n,o=0;l<=n;l++)for(let n=-i,c=e+l*r;n<=i;n++,c+=a,o++)h+=t[o]*s[c];return h}}},et=(t,e,s)=>t[(s>>1)*e+(e>>1)],st=(t,e,s)=>`(${t.join("+")})*${1/(e*s)}`,nt={pool:st,size:3},it={pool:st,size:5},rt=t=>t/127.5-1,at=(t,e)=>n.clamp(127.5*t+128,0,255)<<e,ht={__float:!0,alpha:!1,gray:!1,channels:[t.Lane.RED,t.Lane.GREEN,t.Lane.BLUE],shift:{3:0,2:8,1:16},size:3,fromABGR:t=>[rt(255&t),rt(t>>8&255),rt(t>>16&255)],toABGR:t=>at(t[0],0)|at(t[1],8)|at(t[2],16)|4278190080},lt=j({type:"u8",size:8,alpha:8,channels:[{size:8,lane:0}]}),ot=j({type:"u16",size:16,alpha:1,channels:[{size:1,lane:t.Lane.ALPHA},{size:5,lane:t.Lane.RED},{size:5,lane:t.Lane.GREEN},{size:5,lane:t.Lane.BLUE}]}),ct=j({type:"u16",size:16,alpha:4,channels:[{size:4,lane:t.Lane.ALPHA},{size:4,lane:t.Lane.RED},{size:4,lane:t.Lane.GREEN},{size:4,lane:t.Lane.BLUE}]}),ft=j({type:"u32",size:32,alpha:8,channels:[{size:8,lane:t.Lane.ALPHA},{size:8,lane:t.Lane.RED},{size:8,lane:t.Lane.GREEN},{size:8,lane:t.Lane.BLUE}],fromABGR:r.swapLane13,toABGR:r.swapLane13}),pt=j({type:"u32",size:24,channels:[{size:8,lane:t.Lane.BLUE},{size:8,lane:t.Lane.GREEN},{size:8,lane:t.Lane.RED}],fromABGR:t=>16777215&t,toABGR:t=>4278190080|t}),ut=j({type:"u16",size:16,channels:[{size:16,lane:t.Lane.RED}],fromABGR:t=>257*(d(t)+.5|0),toABGR:t=>(4278190080|65793*(t>>>8))>>>0}),dt=j({type:"u32",size:32,channels:[{size:8,lane:t.Lane.ALPHA},{size:16,lane:t.Lane.RED}],fromABGR:t=>257*(d(t)+.5|0)|257*(t>>>8&16711680),toABGR:t=>(4278190080&t|65793*(t>>>8&255))>>>0}),mt=j({type:"u8",size:8,channels:[{size:8,lane:t.Lane.RED}],fromABGR:t=>d(t),toABGR:t=>(4278190080|65793*(255&t))>>>0}),At=j({type:"u16",size:16,alpha:8,channels:[{size:8,lane:t.Lane.ALPHA},{size:8,lane:t.Lane.RED}],fromABGR:t=>d(t)|t>>>16&65280,toABGR:t=>((65280&t)<<16|65793*(255&t))>>>0}),gt=j({type:"u16",size:16,channels:[{size:5,lane:t.Lane.RED},{size:6,lane:t.Lane.GREEN},{size:5,lane:t.Lane.BLUE}]}),Rt=j({type:"u32",size:24,channels:[{size:8,lane:t.Lane.RED},{size:8,lane:t.Lane.GREEN},{size:8,lane:t.Lane.BLUE}]}),wt=N({gray:!0,alpha:!0,channels:[t.Lane.RED,t.Lane.ALPHA]}),Lt=Math.abs,xt=Math.min,Bt=N({alpha:!0,channels:[t.Lane.RED,t.Lane.GREEN,t.Lane.BLUE,t.Lane.ALPHA],convert:{fromABGR:(t,e=[])=>{const s=(t>>>24&255)/255,i=(t>>>16&255)/255,r=(t>>>8&255)/255,a=(255&t)/255;let h,l,o,c,f,p,u,d;r<i?(h=i,l=r,o=-1,c=2/3):(h=r,l=i,o=0,c=-1/3),a<h?(f=h,p=l,u=c,d=a):(f=a,p=l,u=o,d=h);const m=f-xt(p,d);return f=n.clamp01(f),e[0]=n.clamp01(Lt((d-p)/(6*m+n.EPS)+u)),e[1]=n.clamp01(m/(f+n.EPS)),e[2]=f,e[3]=s,e},toABGR:t=>{const e=6*t[0],s=t[1],i=255*t[2],r=255*t[3],a=((n.clamp01(Lt(e-3)-1)-1)*s+1)*i,h=((n.clamp01(2-Lt(e-2))-1)*s+1)*i;return(r<<24|((n.clamp01(2-Lt(e-4))-1)*s+1)*i<<16|h<<8|a)>>>0}}}),yt=N({channels:[t.Lane.RED,t.Lane.GREEN,t.Lane.BLUE]}),Gt=N({alpha:!0,channels:[t.Lane.RED,t.Lane.GREEN,t.Lane.BLUE,t.Lane.ALPHA]});t.ABGR8888=M,t.ALPHA8=lt,t.ARGB1555=ot,t.ARGB4444=ct,t.ARGB8888=ft,t.BGR888=pt,t.BOX_BLUR3=nt,t.BOX_BLUR5=it,t.FLOAT_GRAY=C,t.FLOAT_GRAY_ALPHA=wt,t.FLOAT_HSVA=Bt,t.FLOAT_NORMAL=ht,t.FLOAT_RGB=yt,t.FLOAT_RGBA=Gt,t.FloatBuffer=X,t.GAUSSIAN=t=>{t|=0,s.assert(t>0,`invalid kernel radius: ${t}`);const e=-1/(2*(Math.hypot(t,t)/3)**2),n=[];let i=0;for(let s=-t;s<=t;s++)for(let r=-t;r<=t;r++){const t=Math.exp((r*r+s*s)*e);n.push(t),i+=t}return{spec:n.map((t=>t/i)),size:2*t+1}},t.GAUSSIAN_BLUR3={spec:[1/16,1/8,1/16,1/8,1/4,1/8,1/16,1/8,1/16],size:3},t.GAUSSIAN_BLUR5={spec:[1/256,1/64,3/128,1/64,1/256,1/64,1/16,3/32,1/16,1/64,3/128,3/32,9/64,3/32,3/128,1/64,1/16,3/32,1/16,1/64,1/256,1/64,3/128,1/64,1/256],size:5},t.GRAY16=ut,t.GRAY8=mt,t.GRAY_ALPHA16=dt,t.GRAY_ALPHA8=At,t.HIGHPASS3={spec:[-1,-1,-1,-1,9,-1,-1,-1,-1],size:3},t.POOL_MAX=t=>`Math.max(${t.join(",")})`,t.POOL_MEAN=st,t.POOL_MIN=t=>`Math.min(${t.join(",")})`,t.POOL_NEAREST=et,t.POOL_THRESHOLD=(t=0)=>(e,s,n)=>`(${et(e,s,n)} - ${`(${e.join("+")})/${s*n}`} + ${t}) < 0 ? 0 : 1`,t.PackedBuffer=V,t.RGB565=gt,t.RGB888=Rt,t.SHARPEN3={spec:[0,-1,0,-1,5,-1,0,-1,0],size:3},t.SOBEL_X={spec:[-1,-2,-1,0,0,0,1,2,1],size:3},t.SOBEL_Y={spec:[-1,0,1,-2,0,2,-1,0,1],size:3},t.UNSHARP_MASK5={spec:[-1/256,-1/64,-3/128,-1/64,-1/256,-1/64,-1/16,-3/32,-1/16,-1/64,-3/128,-3/32,119/64,-3/32,-3/128,-1/64,-1/16,-3/32,-1/16,-1/64,-1/256,-1/64,-3/128,-1/64,-1/256],size:5},t.asIntVec=B,t.asVec=x,t.buffer=Y,t.canvas2d=o,t.canvasPixels=c,t.clampRegion=m,t.compileFromABGR=S,t.compileGrayFromABGR=b,t.compileGrayToABGR=$,t.compileToABGR=_,t.convolveChannel=q,t.convolveImage=(t,e)=>{const s=J(t,e),n=new X(s.dwidth,s.dheight,t.format);for(let i of e.channels||y(t.format.channels.length))n.setChannel(i,W(Object.assign(Object.assign({},s),{channel:i})));return n},t.defBayer=I,t.defFloatFormat=N,t.defKernel=Z,t.defLargeKernel=tt,t.defPackedFormat=j,t.ditherPixels=(t,e,s,n,i,r,a,h)=>{!t&&(t=e.slice()),a--;for(let l=0,o=0;l<n;l++)for(let n=0;n<s;n++)t[o]=H(i,r,a,h,n,l,e[o]),o++;return t},t.ensureChannel=u,t.ensureSize=p,t.floatBuffer=function(...t){return t[0]instanceof V?X.fromPacked(...t):new X(...t)},t.imageCanvas=f,t.imagePromise=async t=>{const e=new Image;return e.src=t,await e.decode(),e},t.luminanceABGR=d,t.normalMap=(t,e)=>{const{channel:s,step:n,scale:i,z:r}=Object.assign({channel:0,step:0,scale:1,z:1},e);u(t.format,s);const a=[-1,...new Array(n).fill(0),1],[h,l]=x(i),o=new X(t.width,t.height,ht);return o.setChannel(0,q(t,{kernel:{spec:a,size:[n+2,1]},scale:h,channel:s})),o.setChannel(1,q(t,{kernel:{spec:a,size:[1,n+2]},scale:l,channel:s})),o.setChannel(2,r),o},t.orderedDither=H,t.packedBuffer=T,t.prepRegions=A,t.range=y,t.setChannelConvert=w,t.setChannelSame=R,t.setChannelUni=g,t.transformABGR=L,Object.defineProperty(t,"__esModule",{value:!0})})); |
{ | ||
"name": "@thi.ng/pixel", | ||
"version": "0.6.1", | ||
"description": "Typed array backed, integer and floating point pixel buffers w/ customizable formats, blitting, dithering, conversions", | ||
"version": "0.7.1", | ||
"description": "Typedarray integer & float pixel buffers w/ customizable formats, blitting, dithering, convolution", | ||
"module": "./index.js", | ||
@@ -42,3 +42,3 @@ "main": "./lib/index.js", | ||
"@istanbuljs/nyc-config-typescript": "^1.0.1", | ||
"@microsoft/api-extractor": "^7.12.1", | ||
"@microsoft/api-extractor": "^7.13.1", | ||
"@types/mocha": "^8.2.0", | ||
@@ -49,11 +49,11 @@ "@types/node": "^14.14.14", | ||
"ts-node": "^9.1.1", | ||
"typedoc": "^0.20.26", | ||
"typescript": "^4.1.5" | ||
"typedoc": "^0.20.28", | ||
"typescript": "^4.2.2" | ||
}, | ||
"dependencies": { | ||
"@thi.ng/api": "^7.0.0", | ||
"@thi.ng/binary": "^2.1.0", | ||
"@thi.ng/checks": "^2.9.0", | ||
"@thi.ng/math": "^3.2.0", | ||
"@thi.ng/porter-duff": "^0.1.38" | ||
"@thi.ng/api": "^7.1.1", | ||
"@thi.ng/binary": "^2.2.1", | ||
"@thi.ng/checks": "^2.9.2", | ||
"@thi.ng/math": "^3.2.2", | ||
"@thi.ng/porter-duff": "^0.1.40" | ||
}, | ||
@@ -77,2 +77,3 @@ "files": [ | ||
"blit", | ||
"blur", | ||
"canvas", | ||
@@ -82,2 +83,3 @@ "channel", | ||
"conversion", | ||
"convolution", | ||
"datastructure", | ||
@@ -87,2 +89,3 @@ "dither", | ||
"format", | ||
"gaussian", | ||
"grayscale", | ||
@@ -92,5 +95,7 @@ "image", | ||
"multiformat", | ||
"normal", | ||
"pixel", | ||
"resize", | ||
"rgb565", | ||
"sharpen", | ||
"typedarray", | ||
@@ -109,3 +114,3 @@ "typescript" | ||
}, | ||
"gitHead": "2cb9a54255d6a5d7f21c9875002b86c42dd038de" | ||
"gitHead": "a59cd470a1728cb5f40e96e010fb98ead30e3142" | ||
} |
@@ -1,2 +0,2 @@ | ||
import { Fn, UIntArray } from "@thi.ng/api"; | ||
import { Fn, ICopy, IEmpty, UIntArray } from "@thi.ng/api"; | ||
import { BayerMatrix, BayerSize, BlendFnInt, BlitOpts, IPixelBuffer, PackedFormat, PackedFormatSpec } from "./api"; | ||
@@ -11,10 +11,20 @@ /** | ||
*/ | ||
export declare const packedBuffer: (w: number, h: number, fmt: PackedFormat | PackedFormatSpec, pixels?: Uint8Array | Uint8ClampedArray | Uint16Array | Uint32Array | undefined) => PackedBuffer; | ||
export declare function packedBuffer(w: number, h: number, fmt: PackedFormat | PackedFormatSpec, pixels?: UIntArray): PackedBuffer; | ||
export declare function packedBuffer(src: PackedBuffer, fmt: PackedFormat | PackedFormatSpec): PackedBuffer; | ||
/** | ||
* @deprecated use {@link packedBuffer} instead. | ||
*/ | ||
export declare const buffer: (w: number, h: number, fmt: PackedFormat | PackedFormatSpec, pixels?: Uint8Array | Uint8ClampedArray | Uint16Array | Uint32Array | undefined) => PackedBuffer; | ||
export declare class PackedBuffer implements IPixelBuffer<UIntArray, number> { | ||
static fromImage(img: HTMLImageElement, fmt: PackedFormat, width?: number, height?: number | undefined): PackedBuffer; | ||
static fromCanvas(canvas: HTMLCanvasElement): PackedBuffer; | ||
export declare const buffer: typeof packedBuffer; | ||
export declare class PackedBuffer implements IPixelBuffer<UIntArray, number>, ICopy<PackedBuffer>, IEmpty<PackedBuffer> { | ||
/** | ||
* Creates a new pixel buffer from given HTML image element with optional | ||
* support for format conversion (default: {@link ABGR8888} & resizing. | ||
* | ||
* @param img | ||
* @param fmt | ||
* @param width | ||
* @param height | ||
*/ | ||
static fromImage(img: HTMLImageElement, fmt?: PackedFormat, width?: number, height?: number | undefined): PackedBuffer; | ||
static fromCanvas(canvas: HTMLCanvasElement, fmt?: PackedFormat): PackedBuffer; | ||
readonly width: number; | ||
@@ -28,2 +38,3 @@ readonly height: number; | ||
copy(): PackedBuffer; | ||
empty(): PackedBuffer; | ||
getAt(x: number, y: number): number; | ||
@@ -30,0 +41,0 @@ setAt(x: number, y: number, col: number): this; |
@@ -1,2 +0,2 @@ | ||
import { typedArray, uintTypeForBits } from "@thi.ng/api"; | ||
import { typedArray, uintTypeForBits, } from "@thi.ng/api"; | ||
import { isNumber } from "@thi.ng/checks"; | ||
@@ -11,12 +11,9 @@ import { isPremultipliedInt, postmultiplyInt, premultiplyInt, } from "@thi.ng/porter-duff"; | ||
import { clampRegion, ensureChannel, ensureSize, prepRegions, setChannelConvert, setChannelSame, setChannelUni, transformABGR, } from "./utils"; | ||
export function packedBuffer(...args) { | ||
return args[0] instanceof PackedBuffer | ||
? args[0].as(args[1]) | ||
: // @ts-ignore | ||
new PackedBuffer(...args); | ||
} | ||
/** | ||
* Syntax sugar for {@link PackedBuffer} ctor. | ||
* | ||
* @param w - | ||
* @param h - | ||
* @param fmt - | ||
* @param pixels - | ||
*/ | ||
export const packedBuffer = (w, h, fmt, pixels) => new PackedBuffer(w, h, fmt, pixels); | ||
/** | ||
* @deprecated use {@link packedBuffer} instead. | ||
@@ -34,17 +31,32 @@ */ | ||
} | ||
/** | ||
* Creates a new pixel buffer from given HTML image element with optional | ||
* support for format conversion (default: {@link ABGR8888} & resizing. | ||
* | ||
* @param img | ||
* @param fmt | ||
* @param width | ||
* @param height | ||
*/ | ||
static fromImage(img, fmt, width, height = width) { | ||
const ctx = imageCanvas(img, width, height); | ||
const w = ctx.canvas.width; | ||
const h = ctx.canvas.height; | ||
const src = new Uint32Array(ctx.ctx.getImageData(0, 0, w, h).data.buffer); | ||
const dest = typedArray(fmt.type, w * h); | ||
const from = fmt.fromABGR; | ||
for (let i = dest.length; --i >= 0;) { | ||
dest[i] = from(src[i]); | ||
return PackedBuffer.fromCanvas(imageCanvas(img, width, height).canvas, fmt); | ||
} | ||
static fromCanvas(canvas, fmt = ABGR8888) { | ||
const ctx = canvasPixels(canvas); | ||
const w = canvas.width; | ||
const h = canvas.height; | ||
let dest; | ||
if (fmt === ABGR8888) { | ||
dest = ctx.pixels; | ||
} | ||
else { | ||
dest = typedArray(fmt.type, w * h); | ||
const src = ctx.pixels; | ||
const from = fmt.fromABGR; | ||
for (let i = dest.length; --i >= 0;) { | ||
dest[i] = from(src[i]); | ||
} | ||
} | ||
return new PackedBuffer(w, h, fmt, dest); | ||
} | ||
static fromCanvas(canvas) { | ||
return new PackedBuffer(canvas.width, canvas.height, ABGR8888, canvasPixels(canvas).pixels); | ||
} | ||
get stride() { | ||
@@ -57,6 +69,9 @@ return 1; | ||
copy() { | ||
const dest = new PackedBuffer(this.width, this.height, this.format); | ||
const dest = this.empty(); | ||
dest.pixels.set(this.pixels); | ||
return dest; | ||
} | ||
empty() { | ||
return new PackedBuffer(this.width, this.height, this.format); | ||
} | ||
getAt(x, y) { | ||
@@ -63,0 +78,0 @@ return x >= 0 && x < this.width && y >= 0 && y < this.height |
@@ -16,2 +16,4 @@ <!-- This file is generated - DO NOT EDIT! --> | ||
- [Floating point pixel formats](#floating-point-pixel-formats) | ||
- [Strided convolution & pooling](#strided-convolution--pooling) | ||
- [Normal map generation](#normal-map-generation) | ||
- [Status](#status) | ||
@@ -29,3 +31,3 @@ - [Support packages](#support-packages) | ||
Typed array backed, integer and floating point pixel buffers w/ customizable formats, blitting, dithering, conversions. | ||
Typedarray integer & float pixel buffers w/ customizable formats, blitting, dithering, convolution. | ||
@@ -43,6 +45,9 @@  | ||
- Single-channel manipulation / extraction / replacement / conversion | ||
- Convolution w/ arbitrary shaped/sized kernels, pooling, striding (resizing) | ||
- Convolution kernel & pooling kernels presets | ||
- Customizable normal map generation (i.e. X/Y gradients plus static Z component) | ||
- Inversion | ||
- XY pixel accessors | ||
- 10 packed integer and 4 floating point preset formats (see table | ||
below) | ||
- Image downsampling (nearest neighbor, mean/min/max pooling) | ||
- XY full pixel & channel-only accessors | ||
- 12 packed integer and 6 floating point preset formats (see table below) | ||
- Ordered dithering w/ customizable Bayer matrix size and target color | ||
@@ -108,10 +113,83 @@ steps (int formats only) | ||
| `FLOAT_GRAY_ALPHA` | 2 | Grayscale and alpha channel | | ||
| `FLOAT_NORMAL` | 3 | Normal map (signed values) | | ||
| `FLOAT_RGB` | 3 | Red, Green, Blue | | ||
| `FLOAT_RGBA` | 4 | Red, Green, Blue, Alpha | | ||
- All color channels are unclamped (but can be clamped via | ||
`buf.clamp()`). For conversion to packed int formats assumed to | ||
contain normalized data (i.e. [0..1] interval) | ||
- All color channels are unclamped (but can be clamped via `buf.clamp()`). For | ||
conversion to packed int formats assumed to contain normalized data (i.e. | ||
[0..1] interval, with exception of `FLOAT_NORMAL` which uses [-1..1] range) | ||
- Conversion between float formats is currently unsupported | ||
### Strided convolution & pooling | ||
Floating point buffers can be processed using arbitrary convolution kernels. The | ||
following convolution kernel presets are provided for convenience: | ||
| Kernel | Size | | ||
|------------------|-------------| | ||
| `BOX_BLUR3` | 3x3 | | ||
| `BOX_BLUR5` | 5x5 | | ||
| `GAUSSIAN_BLUR3` | 3x3 | | ||
| `GAUSSIAN_BLUR5` | 5x5 | | ||
| `GAUSSIAN(n)` | 2n+1 x 2n+1 | | ||
| `HIGHPASS3` | 3x3 | | ||
| `SHARPEN3` | 3x3 | | ||
| `SOBEL_X` | 3x3 | | ||
| `SOBEL_Y` | 3x3 | | ||
| `UNSHARP_MASK5` | 5x5 | | ||
Furthermore, convolution supports striding (i.e. only processing every nth pixel | ||
column/row) and pixel pooling (e.g. for ML applications). Available pooling | ||
kernel presets (kernel sizes are configured independently): | ||
| Kernel | Description | | ||
|------------------------|--------------------| | ||
| `POOL_MEAN` | Moving average | | ||
| `POOL_MAX` | Local maximum | | ||
| `POOL_MIN` | Local minimum | | ||
| `POOL_NEAREST` | Nearest neighbor | | ||
| `POOL_THRESHOLD(bias)` | Adaptive threshold | | ||
Convolution can be applied to single, multiple or all channels of a | ||
`FloatBuffer`. See | ||
[`convolveChannel()`](https://docs.thi.ng/umbrella/pixel/modules.html#convolvechannel) | ||
and | ||
[`convolveImage()`](https://docs.thi.ng/umbrella/pixel/modules.html#convolveimage) | ||
TODO add image & code example | ||
### Normal map generation | ||
Normal maps can be created via `normalMap()`. This function uses an adjustable | ||
convolution kernel size to control gradient smoothness & details. Result X/Y | ||
gradients can also be scaled (uniform or anisotropic) and the Z component can be | ||
customized to (default: 1.0). The resulting image is in `FLOAT_NORMAL` format, | ||
using signed channel values. This channel format is auto-translating these into | ||
unsigned values when the image is converted into an integer format. | ||
| Step | Scale = 1 | Scale = 2 | Scale = 4 | Scale = 8 | | ||
|------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| | ||
| 0 |  |  |  |  | | ||
| 1 |  |  |  |  | | ||
| 2 |  |  |  |  | | ||
| 3 |  |  |  |  | | ||
```ts | ||
import { floatBuffer, normalMap, FLOAT_GRAY, RGB888 } from "@thi.ng/pixel"; | ||
import { asPPM, read } from "@thi.ng/pixel-io-netpbm"; | ||
// read source image into a single channel floating point buffer | ||
const src = floatBuffer(read(readFileSync("noise.pgm")), FLOAT_GRAY); | ||
// create normal map (w/ default options) | ||
const nmap = normalMap(src, { step: 0, scale: 1 }); | ||
// pixel lookup (vectors are stored _un_normalized) | ||
nmap.getAt(10, 10); | ||
// Float32Array(3) [ -0.019607841968536377, -0.04313725233078003, 1 ] | ||
// save as 24bit PBM, conversion to RGB int format first | ||
writeFileSync("noise-normal.ppm", asPPM(nmap.as(RGB888))); | ||
``` | ||
### Status | ||
@@ -145,3 +223,3 @@ | ||
Package sizes (gzipped, pre-treeshake): ESM: 5.35 KB / CJS: 5.57 KB / UMD: 5.52 KB | ||
Package sizes (gzipped, pre-treeshake): ESM: 7.18 KB / CJS: 7.45 KB / UMD: 7.29 KB | ||
@@ -148,0 +226,0 @@ ## Dependencies |
@@ -32,2 +32,8 @@ import { Fn, Fn2, FnN, TypedArray, UIntArray } from "@thi.ng/api"; | ||
export declare const transformABGR: (pix: UIntArray, format: PackedFormat, fn: Fn<number, number>) => void; | ||
/** @internal */ | ||
export declare const asVec: (x: number | [number, number]) => number[]; | ||
/** @internal */ | ||
export declare const asIntVec: (x: number | [number, number]) => number[]; | ||
/** @internal */ | ||
export declare const range: (n: number) => Uint8Array; | ||
//# sourceMappingURL=utils.d.ts.map |
10
utils.js
import { assert } from "@thi.ng/api"; | ||
import { isNumber } from "@thi.ng/checks"; | ||
import { clamp } from "@thi.ng/math"; | ||
@@ -60,1 +61,10 @@ /** @internal */ | ||
}; | ||
/** @internal */ | ||
export const asVec = (x) => isNumber(x) ? [x, x] : x; | ||
/** @internal */ | ||
export const asIntVec = (x) => { | ||
const v = asVec(x); | ||
return [v[0] | 0, v[1] | 0]; | ||
}; | ||
/** @internal */ | ||
export const range = (n) => new Uint8Array(n).map((_, i) => i); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
309270
68
3673
333
15
Updated@thi.ng/api@^7.1.1
Updated@thi.ng/binary@^2.2.1
Updated@thi.ng/checks@^2.9.2
Updated@thi.ng/math@^3.2.2
Updated@thi.ng/porter-duff@^0.1.40