UNPKG

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
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