image-in-browser
Version:
Package for encoding / decoding images, transforming images, applying filters, drawing primitives on images on the client side (no need for server Node.js)
1,119 lines • 88.3 kB
JavaScript
import { Channel } from '../color/channel.js';
import { ColorRgba8 } from '../color/color-rgba8.js';
import { Interpolation } from '../common/interpolation.js';
import { MathUtils } from '../common/math-utils.js';
import { NeuralQuantizer } from '../image/neural-quantizer.js';
import { OctreeQuantizer } from '../image/octree-quantizer.js';
import { RandomUtils } from '../common/random-utils.js';
import { Draw } from '../draw/draw.js';
import { MemoryImage } from '../image/image.js';
import { DitherKernel, DitherKernels } from './dither-kernel.js';
import { NoiseType } from './noise-type.js';
import { PixelateMode } from './pixelate-mode.js';
import { QuantizeMethod } from './quantize-method.js';
import { SeparableKernel } from './separable-kernel.js';
import { ColorUtils } from '../color/color-utils.js';
import { SolarizeMode } from './solarize-mode.js';
import { BinaryQuantizer } from '../image/binary-quantizer.js';
import { ContrastMode } from './contrast-mode.js';
export class Filter {
static adjustColor(opt) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const maskChannel = (_a = opt.maskChannel) !== null && _a !== void 0 ? _a : Channel.luminance;
const contrast = opt.contrast !== undefined
? MathUtils.clamp(opt.contrast, 0, 2)
: undefined;
const brightness = opt.brightness !== undefined ? opt.brightness : undefined;
const gamma = opt.gamma !== undefined ? MathUtils.clamp(opt.gamma, 0, 1000) : undefined;
let exposure = opt.exposure !== undefined
? MathUtils.clamp(opt.exposure, 0, 1000)
: undefined;
const amount = MathUtils.clamp((_b = opt.amount) !== null && _b !== void 0 ? _b : 1, 0, 1000);
if (amount === 0) {
return opt.image;
}
const avgLumR = 0.5;
const avgLumG = 0.5;
const avgLumB = 0.5;
const useBlacksWhitesMids = opt.blacks !== undefined ||
opt.whites !== undefined ||
opt.mids !== undefined;
let br = 0;
let bg = 0;
let bb = 0;
let wr = 0;
let wg = 0;
let wb = 0;
let mr = 0;
let mg = 0;
let mb = 0;
if (useBlacksWhitesMids) {
br = (_d = (_c = opt.blacks) === null || _c === void 0 ? void 0 : _c.rNormalized) !== null && _d !== void 0 ? _d : 0;
bg = (_f = (_e = opt.blacks) === null || _e === void 0 ? void 0 : _e.gNormalized) !== null && _f !== void 0 ? _f : 0;
bb = (_h = (_g = opt.blacks) === null || _g === void 0 ? void 0 : _g.bNormalized) !== null && _h !== void 0 ? _h : 0;
wr = (_k = (_j = opt.whites) === null || _j === void 0 ? void 0 : _j.rNormalized) !== null && _k !== void 0 ? _k : 0;
wg = (_m = (_l = opt.whites) === null || _l === void 0 ? void 0 : _l.gNormalized) !== null && _m !== void 0 ? _m : 0;
wb = (_p = (_o = opt.whites) === null || _o === void 0 ? void 0 : _o.bNormalized) !== null && _p !== void 0 ? _p : 0;
mr = (_r = (_q = opt.mids) === null || _q === void 0 ? void 0 : _q.rNormalized) !== null && _r !== void 0 ? _r : 0;
mg = (_t = (_s = opt.mids) === null || _s === void 0 ? void 0 : _s.gNormalized) !== null && _t !== void 0 ? _t : 0;
mb = (_v = (_u = opt.mids) === null || _u === void 0 ? void 0 : _u.bNormalized) !== null && _v !== void 0 ? _v : 0;
mr = 1 / (1 + 2 * (mr - 0.5));
mg = 1 / (1 + 2 * (mg - 0.5));
mb = 1 / (1 + 2 * (mb - 0.5));
}
const invContrast = contrast !== undefined ? 1 - contrast : 0;
if (exposure !== undefined) {
exposure = Math.pow(2, exposure);
}
const hsv = [0, 0, 0];
for (const frame of image.frames) {
for (const p of frame) {
const or = p.rNormalized;
const og = p.gNormalized;
const ob = p.bNormalized;
let r = or;
let g = og;
let b = ob;
if (useBlacksWhitesMids) {
r = Math.pow((r + br) * wr, mr);
g = Math.pow((g + bg) * wg, mg);
b = Math.pow((b + bb) * wb, mb);
}
if (brightness !== undefined && brightness !== 1.0) {
const tb = MathUtils.clamp(brightness, 0, 1000);
r *= tb;
g *= tb;
b *= tb;
}
if (opt.saturation !== undefined || opt.hue !== undefined) {
ColorUtils.rgbToHsv(r, g, b, hsv);
hsv[0] += (_w = opt.hue) !== null && _w !== void 0 ? _w : 0;
hsv[1] *= (_x = opt.saturation) !== null && _x !== void 0 ? _x : 1;
ColorUtils.hsvToRgb(hsv[0], hsv[1], hsv[2], hsv);
r = hsv[0];
g = hsv[1];
b = hsv[2];
}
if (contrast !== undefined) {
r = avgLumR * invContrast + r * contrast;
g = avgLumG * invContrast + g * contrast;
b = avgLumB * invContrast + b * contrast;
}
if (gamma !== undefined) {
r = Math.pow(r, gamma);
g = Math.pow(g, gamma);
b = Math.pow(b, gamma);
}
if (exposure !== undefined) {
r *= exposure;
g *= exposure;
b *= exposure;
}
const msk = (_z = (_y = opt.mask) === null || _y === void 0 ? void 0 : _y.getPixel(p.x, p.y).getChannelNormalized(maskChannel)) !== null && _z !== void 0 ? _z : 1;
const blend = msk * amount;
r = MathUtils.mix(or, r, blend);
g = MathUtils.mix(og, g, blend);
b = MathUtils.mix(ob, b, blend);
p.rNormalized = MathUtils.clamp(r, 0, 1);
p.gNormalized = MathUtils.clamp(g, 0, 1);
p.bNormalized = MathUtils.clamp(b, 0, 1);
}
}
return image;
}
static billboard(opt) {
var _a, _b, _c, _d;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const grid = (_a = opt.grid) !== null && _a !== void 0 ? _a : 10;
const amount = (_b = opt.amount) !== null && _b !== void 0 ? _b : 1;
const maskChannel = (_c = opt.maskChannel) !== null && _c !== void 0 ? _c : Channel.luminance;
const rs = 0.2025;
for (const frame of image.frames) {
const w = frame.width;
const h = frame.height;
const aspect = w / h;
const stepX = 0.0015625;
const stepY = 0.0015625 * aspect;
const orig = frame.clone({
skipAnimation: true,
});
for (const p of frame) {
const uvX = p.x / (w - 1);
const uvY = p.y / (h - 1);
const offX = Math.floor(uvX / (grid * stepX));
const offY = Math.floor(uvY / (grid * stepY));
const x2 = Math.floor(offX * grid * stepX * (w - 1));
const y2 = Math.floor(offY * grid * stepY * (h - 1));
if (x2 >= w || y2 >= h) {
continue;
}
const op = orig.getPixel(x2, y2);
const prcX = MathUtils.fract(uvX / (grid * stepX));
const prcY = MathUtils.fract(uvY / (grid * stepY));
const pwX = Math.pow(Math.abs(prcX - 0.5), 2);
const pwY = Math.pow(Math.abs(prcY - 0.5), 2);
let r = op.r / p.maxChannelValue;
let g = op.g / p.maxChannelValue;
let b = op.b / p.maxChannelValue;
const gr = MathUtils.smoothStep(rs - 0.1, rs + 0.1, pwX + pwY);
const y = (r + g + b) / 3.0;
const ls = 0.3;
const lb = Math.ceil(y / ls);
const lf = ls * lb + 0.3;
r = MathUtils.mix(lf * r, 0.1, gr);
g = MathUtils.mix(lf * g, 0.1, gr);
b = MathUtils.mix(lf * b, 0.1, gr);
const msk = (_d = opt.mask) === null || _d === void 0 ? void 0 : _d.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
const mx = (msk !== null && msk !== void 0 ? msk : 1) * amount;
p.r = MathUtils.mix(p.r, r * p.maxChannelValue, mx);
p.g = MathUtils.mix(p.g, g * p.maxChannelValue, mx);
p.b = MathUtils.mix(p.b, b * p.maxChannelValue, mx);
}
}
return image;
}
static bleachBypass(opt) {
var _a, _b, _c, _d;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const amount = (_a = opt.amount) !== null && _a !== void 0 ? _a : 1;
const maskChannel = (_b = opt.maskChannel) !== null && _b !== void 0 ? _b : Channel.luminance;
const luminanceR = 0.2125;
const luminanceG = 0.7154;
const luminanceB = 0.0721;
for (const frame of image.frames) {
for (const p of frame) {
const r = p.rNormalized;
const g = p.gNormalized;
const b = p.bNormalized;
const lr = r * luminanceR;
const lg = g * luminanceG;
const lb = b * luminanceB;
const l = lr + lg + lb;
const mixAmount = MathUtils.clamp((l - 0.45) * 10, 0, 1);
const branch1R = 2 * r * l;
const branch1G = 2 * g * l;
const branch1B = 2 * b * l;
const branch2R = 1 - 2 * (1 - r) * (1 - l);
const branch2G = 1 - 2 * (1 - g) * (1 - l);
const branch2B = 1 - 2 * (1 - b) * (1 - l);
const msk = (_d = (_c = opt.mask) === null || _c === void 0 ? void 0 : _c.getPixel(p.x, p.y).getChannelNormalized(maskChannel)) !== null && _d !== void 0 ? _d : 1;
const mx = msk * amount;
if (mx !== 1) {
const nr = MathUtils.mix(branch1R, branch2R, mixAmount) * p.maxChannelValue;
const ng = MathUtils.mix(branch1G, branch2G, mixAmount) * p.maxChannelValue;
const nb = MathUtils.mix(branch1B, branch2B, mixAmount) * p.maxChannelValue;
p.r = MathUtils.mix(p.r, nr, amount);
p.g = MathUtils.mix(p.g, ng, amount);
p.b = MathUtils.mix(p.b, nb, amount);
}
else {
p.rNormalized = MathUtils.mix(branch1R, branch2R, mixAmount);
p.gNormalized = MathUtils.mix(branch1G, branch2G, mixAmount);
p.bNormalized = MathUtils.mix(branch1B, branch2B, mixAmount);
}
}
}
return image;
}
static bulgeDistortion(opt) {
var _a, _b, _c, _d, _e, _f, _g;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const scale = (_a = opt.scale) !== null && _a !== void 0 ? _a : 0.5;
const interpolation = (_b = opt.interpolation) !== null && _b !== void 0 ? _b : Interpolation.nearest;
const maskChannel = (_c = opt.maskChannel) !== null && _c !== void 0 ? _c : Channel.luminance;
for (const frame of image.frames) {
const orig = frame.clone({
skipAnimation: true,
});
const w = frame.width;
const h = frame.height;
const cx = (_d = opt.centerX) !== null && _d !== void 0 ? _d : Math.trunc(w / 2);
const cy = (_e = opt.centerY) !== null && _e !== void 0 ? _e : Math.trunc(h / 2);
const rad = (_f = opt.radius) !== null && _f !== void 0 ? _f : Math.trunc(Math.min(w, h) / 2);
const radSqr = rad * rad;
for (const p of frame) {
let x = p.x;
let y = p.y;
const deltaX = cx - x;
const deltaY = cy - y;
const dist = deltaX * deltaX + deltaY * deltaY;
x -= cx;
y -= cy;
if (dist < radSqr) {
const percent = 1 - ((radSqr - dist) / radSqr) * scale;
const percentSqr = percent * percent;
x *= percentSqr;
y *= percentSqr;
}
x += cx;
y += cy;
const p2 = orig.getPixelInterpolate(x, y, interpolation);
const msk = (_g = opt.mask) === null || _g === void 0 ? void 0 : _g.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
if (msk === undefined) {
p.set(p2);
}
else {
p.r = MathUtils.mix(p.r, p2.r, msk);
p.g = MathUtils.mix(p.g, p2.g, msk);
p.b = MathUtils.mix(p.b, p2.b, msk);
p.a = MathUtils.mix(p.a, p2.a, msk);
}
}
}
return image;
}
static bumpToNormal(opt) {
var _a;
const strength = (_a = opt.strength) !== null && _a !== void 0 ? _a : 2;
const dest = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: MemoryImage.from(opt.image);
const mx = opt.image.maxChannelValue;
for (const frame of opt.image.frames) {
for (let y = 0; y < frame.height; ++y) {
for (let x = 0; x < frame.width; ++x) {
const height = frame.getPixel(x, y).r / mx;
let du = (height -
frame.getPixel(x < frame.width - 1 ? x + 1 : x, y).r / mx) *
strength;
let dv = (height -
frame.getPixel(x, y < frame.height - 1 ? y + 1 : y).r / mx) *
strength;
const z = Math.abs(du) + Math.abs(dv);
if (z > 1) {
du /= z;
dv /= z;
}
const dw = Math.sqrt(1 - du * du - dv * dv);
const nX = du * 0.5 + 0.5;
const nY = dv * 0.5 + 0.5;
const nZ = dw;
dest.frames[frame.frameIndex].setPixelRgb(x, y, nX * mx, nY * mx, nZ * mx);
}
}
}
return dest;
}
static chromaticAberration(opt) {
var _a, _b, _c;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const shift = (_a = opt.shift) !== null && _a !== void 0 ? _a : 5;
const maskChannel = (_b = opt.maskChannel) !== null && _b !== void 0 ? _b : Channel.luminance;
for (const frame of image.frames) {
const orig = frame.clone({
skipAnimation: true,
});
const w = frame.width - 1;
for (const p of frame) {
const shiftLeft = MathUtils.clamp(p.x - shift, 0, w);
const shiftRight = MathUtils.clamp(p.x + shift, 0, w);
const lc = orig.getPixel(shiftLeft, p.y);
const rc = orig.getPixel(shiftRight, p.y);
const msk = (_c = opt.mask) === null || _c === void 0 ? void 0 : _c.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
if (msk === undefined) {
p.r = rc.r;
p.b = lc.b;
}
else {
p.r = MathUtils.mix(p.r, rc.r, msk);
p.b = MathUtils.mix(p.b, lc.b, msk);
}
}
}
return image;
}
static colorHalftone(opt) {
var _a, _b, _c, _d, _e, _f, _g;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const amount = (_a = opt.amount) !== null && _a !== void 0 ? _a : 1;
let angle = (_b = opt.angle) !== null && _b !== void 0 ? _b : 1;
const size = (_c = opt.size) !== null && _c !== void 0 ? _c : 5;
const maskChannel = (_d = opt.maskChannel) !== null && _d !== void 0 ? _d : Channel.luminance;
angle *= 0.0174533;
const pattern = (x, y, cx, cy, angle) => {
const scale = 3.14159 / size;
const s = Math.sin(angle);
const c = Math.cos(angle);
const tx = x - cx;
const ty = y - cy;
const px = (c * tx - s * ty) * scale;
const py = (s * tx + c * ty) * scale;
return Math.sin(px) * Math.sin(py) * 4.0;
};
for (const frame of image.frames) {
const w = frame.width;
const h = frame.height;
const cx = (_e = opt.centerX) !== null && _e !== void 0 ? _e : Math.trunc(w / 2);
const cy = (_f = opt.centerY) !== null && _f !== void 0 ? _f : Math.trunc(h / 2);
for (const p of frame) {
const x = p.x;
const y = p.y;
let cmyC = 1 - p.rNormalized;
let cmyM = 1 - p.gNormalized;
let cmyY = 1 - p.bNormalized;
let cmyK = Math.min(cmyC, Math.min(cmyM, cmyY));
cmyC = (cmyC - cmyK) / (1 - cmyK);
cmyM = (cmyM - cmyK) / (1 - cmyK);
cmyY = (cmyY - cmyK) / (1 - cmyK);
cmyC = MathUtils.clamp(cmyC * 10 - 3 + pattern(x, y, cx, cy, angle + 0.26179), 0, 1);
cmyM = MathUtils.clamp(cmyM * 10 - 3 + pattern(x, y, cx, cy, angle + 1.30899), 0, 1);
cmyY = MathUtils.clamp(cmyY * 10 - 3 + pattern(x, y, cx, cy, angle), 0, 1);
cmyK = MathUtils.clamp(cmyK * 10 - 5 + pattern(x, y, cx, cy, angle + 0.78539), 0, 1);
const r = (1 - cmyC - cmyK) * p.maxChannelValue;
const g = (1 - cmyM - cmyK) * p.maxChannelValue;
const b = (1 - cmyY - cmyK) * p.maxChannelValue;
const msk = (_g = opt.mask) === null || _g === void 0 ? void 0 : _g.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
const mx = (msk !== null && msk !== void 0 ? msk : 1) * amount;
if (mx !== 1) {
p.r = MathUtils.mix(p.r, r, mx);
p.g = MathUtils.mix(p.g, g, mx);
p.b = MathUtils.mix(p.b, b, mx);
}
else {
p.r = r;
p.g = g;
p.b = b;
}
}
}
return image;
}
static colorOffset(opt) {
var _a, _b, _c, _d, _e, _f;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const red = (_a = opt.red) !== null && _a !== void 0 ? _a : 0;
const green = (_b = opt.green) !== null && _b !== void 0 ? _b : 0;
const blue = (_c = opt.blue) !== null && _c !== void 0 ? _c : 0;
const alpha = (_d = opt.alpha) !== null && _d !== void 0 ? _d : 0;
const maskChannel = (_e = opt.maskChannel) !== null && _e !== void 0 ? _e : Channel.luminance;
for (const frame of image.frames) {
for (const p of frame) {
const msk = (_f = opt.mask) === null || _f === void 0 ? void 0 : _f.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
if (msk === undefined) {
p.r += red;
p.g += green;
p.b += blue;
p.a += alpha;
}
else {
p.r = MathUtils.mix(p.r, p.r + red, msk);
p.g = MathUtils.mix(p.g, p.g + green, msk);
p.b = MathUtils.mix(p.b, p.b + blue, msk);
p.a = MathUtils.mix(p.a, p.a + alpha, msk);
}
}
}
return image;
}
static contrast(opt) {
var _a, _b, _c;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const maskChannel = (_a = opt.maskChannel) !== null && _a !== void 0 ? _a : Channel.luminance;
const mode = (_b = opt.mode) !== null && _b !== void 0 ? _b : ContrastMode.proportional;
if (opt.contrast === 100) {
return opt.image;
}
if (Filter._contrastCache === undefined ||
opt.contrast !== Filter._contrastCache.lastContrast) {
Filter._contrastCache = {
lastContrast: opt.contrast,
contrast: new Uint8Array(256),
};
if (mode === ContrastMode.proportional) {
const c = (opt.contrast * opt.contrast) / 10000;
for (let i = 0; i < 256; ++i) {
Filter._contrastCache.contrast[i] = MathUtils.clampInt255(((i / 255 - 0.5) * c + 0.5) * 255);
}
}
else {
const c = opt.contrast / 100 - 0.12;
for (let i = 0; i < 256; ++i) {
Filter._contrastCache.contrast[i] = MathUtils.clampInt255(((Math.tan((i / 128 - 1) * c) + 1) / 2) * 255);
}
}
}
for (const frame of image.frames) {
for (const p of frame) {
const msk = (_c = opt.mask) === null || _c === void 0 ? void 0 : _c.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
const r = Math.trunc(p.r);
const g = Math.trunc(p.g);
const b = Math.trunc(p.b);
if (msk === undefined) {
p.r = Filter._contrastCache.contrast[r];
p.g = Filter._contrastCache.contrast[g];
p.b = Filter._contrastCache.contrast[b];
}
else {
p.r = MathUtils.mix(p.r, Filter._contrastCache.contrast[r], msk);
p.g = MathUtils.mix(p.g, Filter._contrastCache.contrast[g], msk);
p.b = MathUtils.mix(p.b, Filter._contrastCache.contrast[b], msk);
}
}
}
return image;
}
static convolution(opt) {
var _a, _b, _c, _d, _e;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const div = (_a = opt.div) !== null && _a !== void 0 ? _a : 1;
const offset = (_b = opt.offset) !== null && _b !== void 0 ? _b : 0;
const amount = (_c = opt.amount) !== null && _c !== void 0 ? _c : 1;
const maskChannel = (_d = opt.maskChannel) !== null && _d !== void 0 ? _d : Channel.luminance;
const tmp = MemoryImage.from(image);
for (const frame of image.frames) {
const tmpFrame = tmp.frames[frame.frameIndex];
for (const c of tmpFrame) {
let r = 0;
let g = 0;
let b = 0;
for (let j = 0, fi = 0; j < 3; ++j) {
const yv = Math.min(Math.max(c.y - 1 + j, 0), image.height - 1);
for (let i = 0; i < 3; ++i, ++fi) {
const xv = Math.min(Math.max(c.x - 1 + i, 0), image.width - 1);
const c2 = tmpFrame.getPixel(xv, yv);
r += c2.r * opt.filter[fi];
g += c2.g * opt.filter[fi];
b += c2.b * opt.filter[fi];
}
}
r = MathUtils.clampInt255(r / div + offset);
g = MathUtils.clampInt255(g / div + offset);
b = MathUtils.clampInt255(b / div + offset);
const p = frame.getPixel(c.x, c.y);
const msk = (_e = opt.mask) === null || _e === void 0 ? void 0 : _e.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
const mx = (msk !== null && msk !== void 0 ? msk : 1) * amount;
p.r = MathUtils.mix(p.r, r, mx);
p.g = MathUtils.mix(p.g, g, mx);
p.b = MathUtils.mix(p.b, b, mx);
}
}
return image;
}
static copyImageChannels(opt) {
var _a, _b, _c;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const scaled = (_a = opt.scaled) !== null && _a !== void 0 ? _a : false;
const maskChannel = (_b = opt.maskChannel) !== null && _b !== void 0 ? _b : Channel.luminance;
const dx = opt.from.width / image.width;
const dy = opt.from.height / image.height;
const fromPixel = opt.from.getPixel(0, 0);
for (const frame of image.frames) {
for (const p of frame) {
if (scaled) {
fromPixel.setPosition(Math.floor(p.x * dx), Math.floor(p.y * dy));
}
else {
fromPixel.setPosition(p.x, p.y);
}
const r = opt.red !== undefined
? fromPixel.getChannelNormalized(opt.red)
: p.rNormalized;
const g = opt.green !== undefined
? fromPixel.getChannelNormalized(opt.green)
: p.gNormalized;
const b = opt.blue !== undefined
? fromPixel.getChannelNormalized(opt.blue)
: p.bNormalized;
const a = opt.alpha !== undefined
? fromPixel.getChannelNormalized(opt.alpha)
: p.aNormalized;
const msk = (_c = opt.mask) === null || _c === void 0 ? void 0 : _c.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
if (msk === undefined) {
p.rNormalized = r;
p.gNormalized = g;
p.bNormalized = b;
p.aNormalized = a;
}
else {
p.rNormalized = MathUtils.mix(p.r, r, msk);
p.gNormalized = MathUtils.mix(p.g, g, msk);
p.bNormalized = MathUtils.mix(p.b, b, msk);
p.aNormalized = MathUtils.mix(p.a, a, msk);
}
}
}
return image;
}
static ditherImage(opt) {
var _a, _b, _c;
const quantizer = (_a = opt.quantizer) !== null && _a !== void 0 ? _a : new NeuralQuantizer(opt.image);
const kernel = (_b = opt.kernel) !== null && _b !== void 0 ? _b : DitherKernel.floydSteinberg;
const serpentine = (_c = opt.serpentine) !== null && _c !== void 0 ? _c : false;
if (kernel === DitherKernel.none) {
return quantizer.getIndexImage(opt.image);
}
const ds = DitherKernels[kernel];
const height = opt.image.height;
const width = opt.image.width;
let direction = serpentine ? -1 : 1;
const palette = quantizer.palette;
const indexedImage = new MemoryImage({
width: width,
height: height,
numChannels: 1,
palette: palette,
});
const imageCopy = opt.image.clone();
for (let y = 0; y < height; y++) {
if (serpentine) {
direction *= -1;
}
const x0 = direction === 1 ? 0 : width - 1;
const x1 = direction === 1 ? width : 0;
for (let x = x0; x !== x1; x += direction) {
const pc = imageCopy.getPixel(x, y);
const r1 = Math.trunc(pc.getChannel(0));
const g1 = Math.trunc(pc.getChannel(1));
const b1 = Math.trunc(pc.getChannel(2));
const idx = quantizer.getColorIndexRgb(r1, g1, b1);
indexedImage.setPixelIndex(x, y, idx);
const r2 = palette.get(idx, 0);
const g2 = palette.get(idx, 1);
const b2 = palette.get(idx, 2);
const er = r1 - r2;
const eg = g1 - g2;
const eb = b1 - b2;
if (er === 0 && eg === 0 && eb === 0) {
continue;
}
const i0 = direction === 1 ? 0 : ds.length - 1;
const i1 = direction === 1 ? ds.length : 0;
for (let i = i0; i !== i1; i += direction) {
const x1 = Math.trunc(ds[i][1]);
const y1 = Math.trunc(ds[i][2]);
if (x1 + x >= 0 && x1 + x < width && y1 + y >= 0 && y1 + y < height) {
const d = ds[i][0];
const nx = x + x1;
const ny = y + y1;
const p2 = imageCopy.getPixel(nx, ny);
p2.r += er * d;
p2.g += eg * d;
p2.b += eb * d;
}
}
}
}
return indexedImage;
}
static dotScreen(opt) {
var _a, _b, _c, _d, _e, _f, _g;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const size = (_a = opt.size) !== null && _a !== void 0 ? _a : 5.75;
const amount = (_b = opt.amount) !== null && _b !== void 0 ? _b : 1;
const maskChannel = (_c = opt.maskChannel) !== null && _c !== void 0 ? _c : Channel.luminance;
let angle = (_d = opt.angle) !== null && _d !== void 0 ? _d : 180;
angle *= 0.0174533;
const s = Math.sin(angle);
const c = Math.cos(angle);
for (const frame of image.frames) {
const w = frame.width - 1;
const h = frame.height - 1;
const cntX = ((_e = opt.centerX) !== null && _e !== void 0 ? _e : Math.trunc(w / 2)) / w;
const cntY = ((_f = opt.centerY) !== null && _f !== void 0 ? _f : Math.trunc(h / 2)) / h;
const pattern = (cntX, cntY, tx, ty) => {
const texX = (tx - cntX) * w;
const texY = (ty - cntY) * h;
const pointX = (c * texX - s * texY) * size;
const pointY = (s * texX + c * texY) * size;
return Math.sin(pointX) * Math.sin(pointY) * 4;
};
for (const p of frame) {
const average = p.luminanceNormalized;
const pat = pattern(cntX, cntY, p.x / w, p.y / h);
const c = (average * 10 - 5 + pat) * p.maxChannelValue;
const msk = (_g = opt.mask) === null || _g === void 0 ? void 0 : _g.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
const mx = (msk !== null && msk !== void 0 ? msk : 1) * amount;
p.r = MathUtils.mix(p.r, c, mx);
p.g = MathUtils.mix(p.g, c, mx);
p.b = MathUtils.mix(p.b, c, mx);
}
}
return image;
}
static dropShadow(opt) {
var _a;
const blur = opt.blur >= 0 ? opt.blur : 0;
const shadowColor = (_a = opt.shadowColor) !== null && _a !== void 0 ? _a : new ColorRgba8(0, 0, 0, 128);
const shadowWidth = opt.image.width + blur * 2;
const shadowHeight = opt.image.height + blur * 2;
let shadowOffsetX = -blur;
let shadowOffsetY = -blur;
let newImageWidth = shadowWidth;
let newImageHeight = shadowHeight;
let imageOffsetX = 0;
let imageOffsetY = 0;
if (shadowOffsetX + opt.hShadow < 0) {
imageOffsetX = -(shadowOffsetX + opt.hShadow);
shadowOffsetX = -shadowOffsetX;
newImageWidth = imageOffsetX;
}
if (shadowOffsetY + opt.vShadow < 0) {
imageOffsetY = -(shadowOffsetY + opt.vShadow);
shadowOffsetY = -shadowOffsetY;
newImageHeight += imageOffsetY;
}
if (shadowWidth + shadowOffsetX + opt.hShadow > newImageWidth) {
newImageWidth = shadowWidth + shadowOffsetX + opt.hShadow;
}
if (shadowHeight + shadowOffsetY + opt.vShadow > newImageHeight) {
newImageHeight = shadowHeight + shadowOffsetY + opt.vShadow;
}
const dst = new MemoryImage({
width: newImageWidth,
height: newImageHeight,
numChannels: 4,
});
dst.clear(new ColorRgba8(255, 255, 255, 0));
Draw.compositeImage({
dst: dst,
src: opt.image,
dstX: shadowOffsetX,
dstY: shadowOffsetY,
});
Filter.remapColors({
image: dst,
red: Channel.alpha,
green: Channel.alpha,
blue: Channel.alpha,
});
Filter.scaleRgba({
image: dst,
scale: shadowColor,
});
Filter.gaussianBlur({
image: dst,
radius: blur,
});
Draw.compositeImage({
dst: dst,
src: opt.image,
dstX: imageOffsetX,
dstY: imageOffsetY,
});
return dst;
}
static edgeGlow(opt) {
var _a, _b, _c;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const amount = (_a = opt.amount) !== null && _a !== void 0 ? _a : 1;
const maskChannel = (_b = opt.maskChannel) !== null && _b !== void 0 ? _b : Channel.luminance;
if (amount === 0) {
return opt.image;
}
for (const frame of image.frames) {
const orig = MemoryImage.from(frame, true);
const width = frame.width;
const height = frame.height;
for (const p of frame) {
const ny = MathUtils.clamp(p.y - 1, 0, height - 1);
const py = MathUtils.clamp(p.y + 1, 0, height - 1);
const nx = MathUtils.clamp(p.x - 1, 0, width - 1);
const px = MathUtils.clamp(p.x + 1, 0, width - 1);
const t1 = orig.getPixel(nx, ny);
const t2 = orig.getPixel(p.x, ny);
const t3 = orig.getPixel(px, ny);
const t4 = orig.getPixel(nx, p.y);
const t5 = p;
const t6 = orig.getPixel(px, p.y);
const t7 = orig.getPixel(nx, py);
const t8 = orig.getPixel(p.x, py);
const t9 = orig.getPixel(px, py);
const xxR = t1.rNormalized +
2 * t2.rNormalized +
t3.rNormalized -
t7.rNormalized -
2 * t8.rNormalized -
t9.rNormalized;
const xxG = t1.gNormalized +
2 * t2.gNormalized +
t3.gNormalized -
t7.gNormalized -
2 * t8.gNormalized -
t9.gNormalized;
const xxB = t1.bNormalized +
2 * t2.bNormalized +
t3.bNormalized -
t7.bNormalized -
2 * t8.bNormalized -
t9.bNormalized;
const yyR = t1.rNormalized -
t3.rNormalized +
2 * t4.rNormalized -
2 * t6.rNormalized +
t7.rNormalized -
t9.rNormalized;
const yyG = t1.gNormalized -
t3.gNormalized +
2 * t4.gNormalized -
2 * t6.gNormalized +
t7.gNormalized -
t9.gNormalized;
const yyB = t1.bNormalized -
t3.bNormalized +
2 * t4.bNormalized -
2 * t6.bNormalized +
t7.bNormalized -
t9.bNormalized;
const rrR = Math.sqrt(xxR * xxR + yyR * yyR);
const rrG = Math.sqrt(xxG * xxG + yyG * yyG);
const rrB = Math.sqrt(xxB * xxB + yyB * yyB);
const r = rrR * 2 * t5.rNormalized * p.maxChannelValue;
const g = rrG * 2 * t5.gNormalized * p.maxChannelValue;
const b = rrB * 2 * t5.bNormalized * p.maxChannelValue;
const msk = (_c = opt.mask) === null || _c === void 0 ? void 0 : _c.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
const mx = (msk !== null && msk !== void 0 ? msk : 1) * amount;
p.r = MathUtils.mix(p.r, r, mx);
p.g = MathUtils.mix(p.g, g, mx);
p.b = MathUtils.mix(p.b, b, mx);
}
}
return image;
}
static emboss(opt) {
var _a, _b;
const amount = (_a = opt.amount) !== null && _a !== void 0 ? _a : 1;
const maskChannel = (_b = opt.maskChannel) !== null && _b !== void 0 ? _b : Channel.luminance;
const filter = [1.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.5];
return Filter.convolution({
image: opt.image,
filter: filter,
div: 1,
offset: 127,
amount: amount,
mask: opt.mask,
maskChannel: maskChannel,
});
}
static gamma(opt) {
var _a, _b;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const maskChannel = (_a = opt.maskChannel) !== null && _a !== void 0 ? _a : Channel.luminance;
for (const frame of image.frames) {
for (const p of frame) {
const msk = (_b = opt.mask) === null || _b === void 0 ? void 0 : _b.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
if (msk === undefined) {
p.rNormalized = Math.pow(p.rNormalized, opt.gamma);
p.gNormalized = Math.pow(p.gNormalized, opt.gamma);
p.bNormalized = Math.pow(p.bNormalized, opt.gamma);
}
else {
p.rNormalized = MathUtils.mix(p.rNormalized, Math.pow(p.rNormalized, opt.gamma), msk);
p.gNormalized = MathUtils.mix(p.gNormalized, Math.pow(p.gNormalized, opt.gamma), msk);
p.bNormalized = MathUtils.mix(p.bNormalized, Math.pow(p.bNormalized, opt.gamma), msk);
}
}
}
return image;
}
static gaussianBlur(opt) {
var _a;
const maskChannel = (_a = opt.maskChannel) !== null && _a !== void 0 ? _a : Channel.luminance;
if (opt.radius <= 0) {
return opt.image;
}
let kernel = undefined;
if (Filter._gaussianKernelCache.has(opt.radius)) {
kernel = Filter._gaussianKernelCache.get(opt.radius);
}
else {
const sigma = (opt.radius * 2) / 3;
const s = 2 * sigma * sigma;
kernel = new SeparableKernel(opt.radius);
let sum = 0;
for (let x = -opt.radius; x <= opt.radius; ++x) {
const c = Math.exp(-(x * x) / s);
sum += c;
kernel.setCoefficient(x + opt.radius, c);
}
kernel.scaleCoefficients(1 / sum);
Filter._gaussianKernelCache.set(opt.radius, kernel);
}
return Filter.separableConvolution({
image: opt.image,
kernel: kernel,
mask: opt.mask,
maskChannel: maskChannel,
});
}
static grayscale(opt) {
var _a, _b, _c;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const amount = (_a = opt.amount) !== null && _a !== void 0 ? _a : 1;
const maskChannel = (_b = opt.maskChannel) !== null && _b !== void 0 ? _b : Channel.luminance;
for (const frame of image.frames) {
if (frame.hasPalette) {
const p = frame.palette;
const numColors = p.numColors;
for (let i = 0; i < numColors; ++i) {
const l = ColorUtils.getLuminanceRgb(p.getRed(i), p.getGreen(i), p.getBlue(i));
if (amount !== 1) {
const r = MathUtils.mix(p.getRed(i), l, amount);
const g = MathUtils.mix(p.getGreen(i), l, amount);
const b = MathUtils.mix(p.getBlue(i), l, amount);
p.setRed(i, r);
p.setGreen(i, g);
p.setBlue(i, b);
}
else {
p.setRed(i, l);
p.setGreen(i, l);
p.setBlue(i, l);
}
}
}
else {
for (const p of frame) {
const l = ColorUtils.getLuminanceRgb(p.r, p.g, p.b);
const msk = (_c = opt.mask) === null || _c === void 0 ? void 0 : _c.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
const mx = (msk !== null && msk !== void 0 ? msk : 1) * amount;
if (mx !== 1) {
p.r = MathUtils.mix(p.r, l, mx);
p.g = MathUtils.mix(p.g, l, mx);
p.b = MathUtils.mix(p.b, l, mx);
}
else {
p.r = l;
p.g = l;
p.b = l;
}
}
}
}
return image;
}
static hdrToLdr(opt) {
const knee = (x, f) => {
return Math.log(x * f + 1.0) / f;
};
const gamma = (h, m) => {
let x = Math.max(0, h * m);
if (x > 1.0) {
x = 1 + knee(x - 1, 0.184874);
}
return Math.pow(x, 0.4545) * 84.66;
};
const image = new MemoryImage({
width: opt.image.width,
height: opt.image.height,
numChannels: opt.image.numChannels,
});
const m = opt.exposure !== undefined
? Math.pow(2, MathUtils.clamp(opt.exposure + 2.47393, -20, 20))
: 1;
const nc = opt.image.numChannels;
for (let y = 0; y < opt.image.height; ++y) {
for (let x = 0; x < opt.image.width; ++x) {
const hp = opt.image.getPixel(x, y);
let r = hp.rNormalized;
let g = nc === 1 ? r : hp.gNormalized;
let b = nc === 1 ? r : hp.bNormalized;
if (!isFinite(r) || isNaN(r)) {
r = 0;
}
if (!isFinite(g) || isNaN(g)) {
g = 0;
}
if (!isFinite(b) || isNaN(b)) {
b = 0;
}
let ri = 0;
let gi = 0;
let bi = 0;
if (opt.exposure !== undefined) {
ri = gamma(r, m);
gi = gamma(g, m);
bi = gamma(b, m);
}
else {
ri = MathUtils.clamp(r, 0, 1) * 255;
gi = MathUtils.clamp(g, 0, 1) * 255;
bi = MathUtils.clamp(b, 0, 1) * 255;
}
const mi = Math.max(ri, Math.max(gi, bi));
if (mi > 255) {
ri = 255 * (ri / mi);
gi = 255 * (gi / mi);
bi = 255 * (bi / mi);
}
if (opt.image.numChannels > 3) {
let a = hp.a;
if (!isFinite(a) || isNaN(a)) {
a = 1;
}
image.setPixelRgba(x, y, MathUtils.clampInt255(ri), MathUtils.clampInt255(gi), MathUtils.clampInt255(bi), MathUtils.clampInt255(a * 255));
}
else {
image.setPixelRgb(x, y, MathUtils.clampInt255(ri), MathUtils.clampInt255(gi), MathUtils.clampInt255(bi));
}
}
}
return image;
}
static hexagonPixelate(opt) {
var _a, _b, _c, _d, _e, _f;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const size = (_a = opt.size) !== null && _a !== void 0 ? _a : 5;
const amount = (_b = opt.amount) !== null && _b !== void 0 ? _b : 1;
const maskChannel = (_c = opt.maskChannel) !== null && _c !== void 0 ? _c : Channel.luminance;
for (const frame of image.frames) {
const w = frame.width - 1;
const h = frame.height - 1;
const cntX = ((_d = opt.centerX) !== null && _d !== void 0 ? _d : Math.trunc(frame.width / 2)) / w;
const cntY = ((_e = opt.centerY) !== null && _e !== void 0 ? _e : Math.trunc(frame.height / 2)) / h;
const orig = frame.clone({
skipAnimation: true,
});
for (const p of frame) {
let texX = (p.x - cntX) / size;
let texY = (p.y - cntY) / size;
texY /= 0.866025404;
texX -= texY * 0.5;
let ax = 0;
let ay = 0;
if (texX + texY - Math.floor(texX) - Math.floor(texY) < 1) {
ax = Math.floor(texX);
ay = Math.floor(texY);
}
else {
ax = Math.ceil(texX);
ay = Math.ceil(texY);
}
const bx = Math.ceil(texX);
const by = Math.floor(texY);
const cx = Math.floor(texX);
const cy = Math.ceil(texY);
const tex2X = texX;
const tex2Y = texY;
const tex2Z = 1 - texX - texY;
const a2x = ax;
const a2y = ay;
const a2z = 1 - ax - ay;
const b2x = bx;
const b2y = by;
const b2z = 1 - bx - by;
const c2x = cx;
const c2y = cy;
const c2z = 1 - cx - cy;
const aLen = MathUtils.length3(tex2X - a2x, tex2Y - a2y, tex2Z - a2z);
const bLen = MathUtils.length3(tex2X - b2x, tex2Y - b2y, tex2Z - b2z);
const cLen = MathUtils.length3(tex2X - c2x, tex2Y - c2y, tex2Z - c2z);
let choiceX = 0;
let choiceY = 0;
if (aLen < bLen) {
if (aLen < cLen) {
choiceX = ax;
choiceY = ay;
}
else {
choiceX = cx;
choiceY = cy;
}
}
else {
if (bLen < cLen) {
choiceX = bx;
choiceY = by;
}
else {
choiceX = cx;
choiceY = cy;
}
}
choiceX += choiceY * 0.5;
choiceY *= 0.866025404;
choiceX *= size / w;
choiceY *= size / h;
const nx = choiceX + cntX / w;
const ny = choiceY + cntY / h;
const x = MathUtils.clamp(nx * w, 0, w);
const y = MathUtils.clamp(ny * h, 0, h);
const newColor = orig.getPixel(Math.floor(x), Math.floor(y));
const msk = (_f = opt.mask) === null || _f === void 0 ? void 0 : _f.getPixel(p.x, p.y).getChannelNormalized(maskChannel);
const mx = (msk !== null && msk !== void 0 ? msk : 1) * amount;
p.r = MathUtils.mix(p.r, newColor.r, mx);
p.g = MathUtils.mix(p.g, newColor.g, mx);
p.b = MathUtils.mix(p.b, newColor.b, mx);
}
}
return image;
}
static invert(opt) {
var _a, _b;
const image = opt.image.hasPalette
? opt.image.convert({
numChannels: opt.image.numChannels,
})
: opt.image;
const maskChannel = (_a = opt.maskChannel) !== null && _a !== void 0 ? _a : Channel.luminance;
const max = image.maxChannelValue;
for (const frame of image.frames) {
if (image.hasPalette) {
const p = frame.palette;
const numColors = p.numColors;
for (let i = 0; i < numColors; ++i) {
const r = max - p.getRed(i);
const g = max - p.getGreen(i);
const b