diff --git a/assets/pixel/resize-bicubic.jpg b/assets/pixel/resize-bicubic.jpg index feef0fb00a..567c1c8842 100644 Binary files a/assets/pixel/resize-bicubic.jpg and b/assets/pixel/resize-bicubic.jpg differ diff --git a/assets/pixel/resize-bilinear.jpg b/assets/pixel/resize-bilinear.jpg index 229ff722dc..bf83cfbe93 100644 Binary files a/assets/pixel/resize-bilinear.jpg and b/assets/pixel/resize-bilinear.jpg differ diff --git a/assets/pixel/resize-nearest.png b/assets/pixel/resize-nearest.png index 6bd0ddc79f..173951531a 100644 Binary files a/assets/pixel/resize-nearest.png and b/assets/pixel/resize-nearest.png differ diff --git a/packages/pixel/README.md b/packages/pixel/README.md index 865991f80d..fcdb49abf7 100644 --- a/packages/pixel/README.md +++ b/packages/pixel/README.md @@ -150,7 +150,7 @@ sampler(-1.1, 0.5).toString(16) // 'ff79643a' // resize image to 1024x256 using bicubic sampling -const img = resize(src, 1024, 256, "cubic"); +const img = src.resize(1024, 256, "cubic"); ``` | Filter | | @@ -259,7 +259,7 @@ yarn add @thi.ng/pixel ``` -Package sizes (gzipped, pre-treeshake): ESM: 8.17 KB / CJS: 8.45 KB / UMD: 8.20 KB +Package sizes (gzipped, pre-treeshake): ESM: 8.19 KB / CJS: 8.47 KB / UMD: 8.22 KB ## Dependencies diff --git a/packages/pixel/src/api.ts b/packages/pixel/src/api.ts index ec45a3e086..65ed60b379 100644 --- a/packages/pixel/src/api.ts +++ b/packages/pixel/src/api.ts @@ -4,6 +4,7 @@ import type { Fn2, Fn3, FnN, + FnU2, IObjectOf, NumericArray, TypedArray, @@ -439,3 +440,7 @@ export interface NormalMapOpts { */ z: number; } + +export type IntSampler = FnU2; + +export type FloatSampler = FnU2; diff --git a/packages/pixel/src/index.ts b/packages/pixel/src/index.ts index 5c7acda926..7f23051821 100644 --- a/packages/pixel/src/index.ts +++ b/packages/pixel/src/index.ts @@ -6,7 +6,6 @@ export * from "./dither"; export * from "./float"; export * from "./normal-map"; export * from "./packed"; -export * from "./resize"; export * from "./sample"; export * from "./utils"; diff --git a/packages/pixel/src/packed.ts b/packages/pixel/src/packed.ts index f5434adbed..5bf24c0dfb 100644 --- a/packages/pixel/src/packed.ts +++ b/packages/pixel/src/packed.ts @@ -1,4 +1,5 @@ import { + assert, Fn, ICopy, IEmpty, @@ -6,7 +7,7 @@ import { UIntArray, uintTypeForBits, } from "@thi.ng/api"; -import { isNumber } from "@thi.ng/checks"; +import { isNumber, isString } from "@thi.ng/checks"; import { isPremultipliedInt, postmultiplyInt, @@ -17,6 +18,8 @@ import { BayerSize, BlendFnInt, BlitOpts, + Filter, + IntSampler, IPixelBuffer, Lane, PackedChannel, @@ -28,6 +31,7 @@ import { compileGrayFromABGR, compileGrayToABGR } from "./codegen"; import { defBayer } from "./dither"; import { ABGR8888 } from "./format/abgr8888"; import { defPackedFormat } from "./format/packed-format"; +import { defSampler } from "./sample"; import { clampRegion, ensureChannel, @@ -425,27 +429,32 @@ export class PackedBuffer } /** - * Returns new buffer of downscaled version (by given integer factor) using - * simple nearest neighbor sampling. + * Returns scaled version of this buffer using given sampler or filter + * (default: `"linear"`) for interpolation. Syntax sugar for + * {@link PackedBuffer.resize}. * - * @param res + * @param scale */ - downsample(res: number) { - res |= 0; - const { width, height, pixels: sbuf } = this; - const dest = new PackedBuffer( - (width / res) | 0, - (height / res) | 0, - this.format - ); - const { width: dwidth, height: dheight, pixels: dbuf } = dest; - for (let y = 0, i = 0; y < dheight; y++) { - for ( - let x = 0, j = y * res * width; - x < dwidth; - x++, i++, j += res - ) { - dbuf[i] = sbuf[j]; + scale(scale: number, sampler: IntSampler | Filter = "linear") { + assert(scale > 0, `scale must be > 0`); + return this.resize(this.width * scale, this.height * scale, sampler); + } + + resize(w: number, h: number, sampler: IntSampler | Filter = "linear") { + w |= 0; + h |= 0; + assert(w > 0 && h > 0, `target width & height must be > 0`); + const dest = packedBuffer(w, h, this.format); + const dpix = dest.pixels; + 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; diff --git a/packages/pixel/src/resize.ts b/packages/pixel/src/resize.ts deleted file mode 100644 index 64df311f74..0000000000 --- a/packages/pixel/src/resize.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Filter } from "./api"; -import { packedBuffer, PackedBuffer } from "./packed"; -import { defSampler } from "./sample"; - -export const resize = ( - src: PackedBuffer, - w: number, - h: number, - filter: Filter = "linear" -) => { - const dest = packedBuffer(w, h, src.format); - const sample = defSampler(src, filter, "repeat"); - const scaleX = w > 0 ? src.width / w : 0; - const scaleY = h > 0 ? src.height / h : 0; - for (let y = 0, i = 0; y < h; y++) { - const yy = y * scaleY; - for (let x = 0; x < w; x++, i++) { - dest.pixels[i] = sample(x * scaleX, yy); - } - } - return dest; -}; diff --git a/packages/pixel/src/sample.ts b/packages/pixel/src/sample.ts index 0eaeca9bd0..4a7e01f57a 100644 --- a/packages/pixel/src/sample.ts +++ b/packages/pixel/src/sample.ts @@ -1,19 +1,15 @@ -import { - assert, - FloatArray, - Fn, - FnU2, - IObjectOf, - NumericArray, -} from "@thi.ng/api"; -import { clamp, fmod, fract, mixBilinear, mixBicubic } from "@thi.ng/math"; -import type { Filter, IPixelBuffer, Wrap } from "./api"; +import { assert, Fn, IObjectOf, NumericArray } from "@thi.ng/api"; +import { clamp, fmod, fract, mixBicubic, mixBilinear } from "@thi.ng/math"; +import type { + Filter, + FloatSampler, + IntSampler, + IPixelBuffer, + Wrap, +} from "./api"; import type { FloatBuffer } from "./float"; import type { PackedBuffer } from "./packed"; -type IntSampler = FnU2; -type FloatSampler = FnU2; - export function defSampler( src: PackedBuffer, filter?: Filter, @@ -188,7 +184,9 @@ const mixBicubicChan = ( u: number, v: number, i: number, - s = 4 + s = 4, + min = 0, + max = 255 ) => clamp( mixBicubic( @@ -211,14 +209,14 @@ const mixBicubicChan = ( u, v ), - 0, - 255 + min, + max ); const bicubicABGR = (src: PackedBuffer, sample: IntSampler): IntSampler => { const { fromABGR, toABGR } = src.format; - const buf32 = new Uint32Array(16); - const buf8 = new Uint8Array(buf32.buffer); + const u32 = new Uint32Array(16); + const u8 = new Uint8Array(u32.buffer); return (x, y) => { x -= 0.5; y -= 0.5; @@ -230,28 +228,28 @@ const bicubicABGR = (src: PackedBuffer, sample: IntSampler): IntSampler => { const y3 = y + 2; const u = fract(x); const v = fract(y); - buf32[0] = toABGR(sample(x1, y1)); - buf32[1] = toABGR(sample(x, y1)); - buf32[2] = toABGR(sample(x2, y1)); - buf32[3] = toABGR(sample(x3, y1)); - buf32[4] = toABGR(sample(x1, y)); - buf32[5] = toABGR(sample(x, y)); - buf32[6] = toABGR(sample(x2, y)); - buf32[7] = toABGR(sample(x3, y)); - buf32[8] = toABGR(sample(x1, y2)); - buf32[9] = toABGR(sample(x, y2)); - buf32[10] = toABGR(sample(x2, y2)); - buf32[11] = toABGR(sample(x3, y2)); - buf32[12] = toABGR(sample(x1, y3)); - buf32[13] = toABGR(sample(x, y3)); - buf32[14] = toABGR(sample(x2, y3)); - buf32[15] = toABGR(sample(x3, y3)); + 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( - (mixBicubicChan(buf8, u, v, 3) << 24) | - (mixBicubicChan(buf8, u, v, 2) << 16) | - (mixBicubicChan(buf8, u, v, 1) << 8) | - mixBicubicChan(buf8, u, v, 0) + mixBicubicChan(u8, u, v, 0) | + (mixBicubicChan(u8, u, v, 1) << 8) | + (mixBicubicChan(u8, u, v, 2) << 16) | + (mixBicubicChan(u8, u, v, 3) << 24) ) >>> 0 ); }; diff --git a/packages/pixel/tpl.readme.md b/packages/pixel/tpl.readme.md index 010b563013..5d2b655a51 100644 --- a/packages/pixel/tpl.readme.md +++ b/packages/pixel/tpl.readme.md @@ -131,7 +131,7 @@ sampler(-1.1, 0.5).toString(16) // 'ff79643a' // resize image to 1024x256 using bicubic sampling -const img = resize(src, 1024, 256, "cubic"); +const img = src.resize(1024, 256, "cubic"); ``` | Filter | |