diff --git a/packages/text-canvas/src/api.ts b/packages/text-canvas/src/api.ts index 624a33b509..0206c2051b 100644 --- a/packages/text-canvas/src/api.ts +++ b/packages/text-canvas/src/api.ts @@ -1,4 +1,4 @@ -import type { Fn, NumOrString } from "@thi.ng/api"; +import type { Fn, FnN, NumOrString } from "@thi.ng/api"; export enum Align { LEFT, @@ -40,9 +40,11 @@ export interface ImageOpts { */ chars: NumOrString[]; /** - * Format to apply to all chars. + * Format to apply to each pixel. If a function is given, it will be called + * for each pixel value, normalized to [0..1] interval (and after gamma + * correction). The function MUST return a valid format ID. */ - format: number; + format: number | FnN; /** * Gamma correction value / exponent. All source pixel values will * be raised by this exponent. diff --git a/packages/text-canvas/src/canvas.ts b/packages/text-canvas/src/canvas.ts index a0b8b5e4fa..20395aa028 100644 --- a/packages/text-canvas/src/canvas.ts +++ b/packages/text-canvas/src/canvas.ts @@ -21,8 +21,8 @@ export class Canvas { ) { this.width = width; this.height = height; - this.buf = new Uint32Array(width * height).fill(0x20); this.format = this.defaultFormat = format; + this.buf = new Uint32Array(width * height).fill(charCode(0x20, format)); this.styles = [style]; this.clipRects = [ { x1: 0, y1: 0, x2: width, y2: height, w: width, h: height }, diff --git a/packages/text-canvas/src/image.ts b/packages/text-canvas/src/image.ts index bea6f0d463..7d2d1ec827 100644 --- a/packages/text-canvas/src/image.ts +++ b/packages/text-canvas/src/image.ts @@ -1,4 +1,5 @@ import { peek } from "@thi.ng/arrays"; +import { isNumber } from "@thi.ng/checks"; import { ImageOpts, SHADES_BLOCK } from "./api"; import { Canvas } from "./canvas"; import { charCode, intersectRect } from "./utils"; @@ -115,6 +116,7 @@ export const image = ( bits: 8, ...opts, }; + const fmt = isNumber(format) ? () => format : format; const max = (1 << bits) - 1; const mask = invert ? max : 0; const norm = 1 / max; @@ -123,14 +125,51 @@ export const image = ( let sidx = sx + yy * w; let didx = x1 + dy * width; for (let xx = sx, dx = x1; dx < x2; xx++, dx++) { - buf[didx++] = charCode( - chars[ - (Math.pow((pixels[sidx++] ^ mask) * norm, gamma) * num + - 0.5) | - 0 - ], - format - ); + const col = Math.pow((pixels[sidx++] ^ mask) * norm, gamma); + buf[didx++] = charCode(chars[(col * num + 0.5) | 0], fmt(col)); + } + } +}; + +/** + * Optimized version of {@link image}, which only uses a single char for all + * pixels and applies pixel values directly as formatting data (for each pixel). + * + * @param canvas + * @param x + * @param y + * @param w + * @param h + * @param pixels + * @param char + */ +export const imageRaw = ( + canvas: Canvas, + x: number, + y: number, + w: number, + h: number, + pixels: ArrayLike, + char = "█" +) => { + x |= 0; + y |= 0; + w |= 0; + h |= 0; + const { buf, width } = canvas; + const { x1, y1, x2, y2, w: iw, h: ih } = intersectRect( + { x1: x, y1: y, x2: x + w, y2: y + h, w, h }, + peek(canvas.clipRects) + ); + if (!iw || !ih) return; + const sx = Math.max(0, x1 - x); + const sy = Math.max(0, y1 - y); + const code = char.charCodeAt(0); + for (let yy = sy, dy = y1; dy < y2; yy++, dy++) { + let sidx = sx + yy * w; + let didx = x1 + dy * width; + for (let xx = sx, dx = x1; dx < x2; xx++, dx++) { + buf[didx++] = code | ((pixels[sidx++] & 0xffff) << 16); } } };