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,275 lines • 65.5 kB
JavaScript
import { Channel } from '../color/channel.js';
import { ColorUtils } from '../color/color-utils.js';
import { Line } from '../common/line.js';
import { MathUtils } from '../common/math-utils.js';
import { Point } from '../common/point.js';
import { Rectangle } from '../common/rectangle.js';
import { ImageUtils } from '../image/image-utils.js';
import { BlendMode } from './blend-mode.js';
import { CircleQuadrant } from './circle-quadrant.js';
export class Draw {
static calculateCircumference(image, center, radius) {
if (radius < 0 ||
center.x + radius < 0 ||
center.x - radius >= image.width ||
center.y + radius < 0 ||
center.y - radius >= image.height) {
return [];
}
if (radius === 0) {
return [center];
}
const points = [
new Point(center.x - radius, center.y),
new Point(center.x + radius, center.y),
new Point(center.x, center.y - radius),
new Point(center.x, center.y + radius),
];
if (radius === 1) {
return points;
}
for (let f = 1 - radius, ddFx = 0, ddFy = -(radius << 1), x = 0, y = radius; x < y;) {
if (f >= 0) {
ddFy += 2;
f += ddFy;
--y;
}
++x;
ddFx += 2;
f += ddFx + 1;
if (x !== y + 1) {
const x1 = center.x - y;
const x2 = center.x + y;
const y1 = center.y - x;
const y2 = center.y + x;
const x3 = center.x - x;
const x4 = center.x + x;
const y3 = center.y - y;
const y4 = center.y + y;
points.push(new Point(x1, y1));
points.push(new Point(x1, y2));
points.push(new Point(x2, y1));
points.push(new Point(x2, y2));
if (x !== y) {
points.push(new Point(x3, y3));
points.push(new Point(x4, y4));
points.push(new Point(x4, y3));
points.push(new Point(x3, y4));
}
}
}
return points;
}
static drawAntialiasCircle(opt) {
var _a, _b;
const drawPixel4 = (x, y, dx, dy, alpha) => {
if ((quadrants & CircleQuadrant.bottomRight) !== 0) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x + dx, y + dy),
color: opt.color,
alpha: alpha,
maskChannel: maskChannel,
mask: opt.mask,
});
}
if ((quadrants & CircleQuadrant.bottomLeft) !== 0) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x - dx, y + dy),
color: opt.color,
alpha: alpha,
maskChannel: maskChannel,
mask: opt.mask,
});
}
if ((quadrants & CircleQuadrant.topRight) !== 0) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x + dx, y - dy),
color: opt.color,
alpha: alpha,
maskChannel: maskChannel,
mask: opt.mask,
});
}
if ((quadrants & CircleQuadrant.topLeft) !== 0) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x - dx, y - dy),
color: opt.color,
alpha: alpha,
maskChannel: maskChannel,
mask: opt.mask,
});
}
};
const quadrants = (_a = opt.quadrants) !== null && _a !== void 0 ? _a : CircleQuadrant.all;
const maskChannel = (_b = opt.maskChannel) !== null && _b !== void 0 ? _b : Channel.luminance;
const radiusSqr = opt.radius * opt.radius;
const quarter = Math.round(opt.radius / Math.SQRT2);
for (let i = 0; i <= quarter; ++i) {
const j = Math.sqrt(radiusSqr - i * i);
const frc = MathUtils.fract(j);
const frc2 = frc * (i === quarter ? 0.25 : 1);
const flr = Math.floor(j);
drawPixel4(opt.x, opt.y, i, flr, 1 - frc);
drawPixel4(opt.x, opt.y, i, flr + 1, frc2);
drawPixel4(opt.x, opt.y, flr, i, 1 - frc);
drawPixel4(opt.x, opt.y, flr + 1, i, frc2);
}
return opt.image;
}
static drawLineWu(opt) {
const line = opt.line.clone();
const steep = Math.abs(line.dy) > Math.abs(line.dx);
if (steep) {
line.swapXY1();
line.swapXY2();
}
if (line.x1 > line.x2) {
line.flipX();
line.flipY();
}
const gradient = line.dx === 1 ? 1 : line.dy / line.dx;
let xend = Math.floor(line.x1 + 0.5);
let yend = line.y1 + gradient * (xend - line.x1);
let xgap = 1 - (line.x1 + 0.5 - Math.floor(line.x1 + 0.5));
const xpxl1 = xend;
const ypxl1 = Math.floor(yend);
if (steep) {
Draw.drawPixel({
image: opt.image,
pos: new Point(ypxl1, xpxl1),
color: opt.color,
alpha: (1 - (yend - Math.floor(yend))) * xgap,
maskChannel: opt.maskChannel,
mask: opt.mask,
});
Draw.drawPixel({
image: opt.image,
pos: new Point(ypxl1 + 1, xpxl1),
color: opt.color,
alpha: (yend - Math.floor(yend)) * xgap,
maskChannel: opt.maskChannel,
mask: opt.mask,
});
}
else {
Draw.drawPixel({
image: opt.image,
pos: new Point(xpxl1, ypxl1),
color: opt.color,
alpha: (1 - (yend - Math.floor(yend))) * xgap,
maskChannel: opt.maskChannel,
mask: opt.mask,
});
Draw.drawPixel({
image: opt.image,
pos: new Point(xpxl1, ypxl1 + 1),
color: opt.color,
alpha: (yend - Math.floor(yend)) * xgap,
maskChannel: opt.maskChannel,
mask: opt.mask,
});
}
let intery = yend + gradient;
xend = Math.floor(line.x2 + 0.5);
yend = line.y2 + gradient * (xend - line.x2);
xgap = line.x2 + 0.5 - Math.floor(line.x2 + 0.5);
const xpxl2 = xend;
const ypxl2 = Math.floor(yend);
if (steep) {
Draw.drawPixel({
image: opt.image,
pos: new Point(ypxl2, xpxl2),
color: opt.color,
alpha: (1 - (yend - Math.floor(yend))) * xgap,
mask: opt.mask,
maskChannel: opt.maskChannel,
});
Draw.drawPixel({
image: opt.image,
pos: new Point(ypxl2 + 1, xpxl2),
color: opt.color,
alpha: (yend - Math.floor(yend)) * xgap,
maskChannel: opt.maskChannel,
mask: opt.mask,
});
for (let x = xpxl1 + 1; x <= xpxl2 - 1; x++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(Math.floor(intery), x),
color: opt.color,
alpha: 1 - (intery - Math.floor(intery)),
mask: opt.mask,
maskChannel: opt.maskChannel,
});
Draw.drawPixel({
image: opt.image,
pos: new Point(Math.floor(intery) + 1, x),
color: opt.color,
alpha: intery - Math.floor(intery),
maskChannel: opt.maskChannel,
mask: opt.mask,
});
intery += gradient;
}
}
else {
Draw.drawPixel({
image: opt.image,
pos: new Point(xpxl2, ypxl2),
color: opt.color,
alpha: (1 - (yend - Math.floor(yend))) * xgap,
maskChannel: opt.maskChannel,
mask: opt.mask,
});
Draw.drawPixel({
image: opt.image,
pos: new Point(xpxl2, ypxl2 + 1),
color: opt.color,
alpha: (yend - Math.floor(yend)) * xgap,
maskChannel: opt.maskChannel,
mask: opt.mask,
});
for (let x = xpxl1 + 1; x <= xpxl2 - 1; x++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x, Math.floor(intery)),
color: opt.color,
alpha: 1 - (intery - Math.floor(intery)),
maskChannel: opt.maskChannel,
mask: opt.mask,
});
Draw.drawPixel({
image: opt.image,
pos: new Point(x, Math.floor(intery) + 1),
color: opt.color,
alpha: intery - Math.floor(intery),
maskChannel: opt.maskChannel,
mask: opt.mask,
});
intery += gradient;
}
}
return opt.image;
}
static setAlpha(c, a) {
c.a = a;
return c;
}
static colorDistance(c1, c2, compareAlpha) {
const d1 = c1[0] - c2[0];
const d2 = c1[1] - c2[1];
const d3 = c1[2] - c2[2];
if (compareAlpha) {
const dA = c1[3] - c2[3];
return Math.sqrt(Math.max(d1 * d1, (d1 - dA) * (d1 - dA)) +
Math.max(d2 * d2, (d2 - dA) * (d2 - dA)) +
Math.max(d3 * d3, (d3 - dA) * (d3 - dA)));
}
else {
return Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);
}
}
static testPixelLabColorDistance(src, x, y, refColor, threshold) {
const pixel = src.getPixel(x, y);
const compareAlpha = refColor.length > 3;
const pixelColor = ColorUtils.rgbToLab(pixel.r, pixel.g, pixel.b);
if (compareAlpha) {
pixelColor.push(pixel.a);
}
return Draw.colorDistance(pixelColor, refColor, compareAlpha) > threshold;
}
static fill4Core(src, x, y, array, mark, visited) {
let _x = x;
let _y = y;
if (visited[_y * src.width + _x] === 1) {
return;
}
let lastRowLength = 0;
do {
let rowLength = 0;
let sx = _x;
if (lastRowLength !== 0 && array(_y, _x)) {
do {
if (--lastRowLength === 0) {
return;
}
} while (array(_y, ++_x));
sx = _x;
}
else {
for (; _x !== 0 && !array(_y, _x - 1); rowLength++, lastRowLength++) {
mark(_y, --_x);
if (_y !== 0 && !array(_y - 1, _x)) {
Draw.fill4(src, _x, _y - 1, array, mark, visited);
}
}
}
for (; sx < src.width && !array(_y, sx); rowLength++, sx++) {
mark(_y, sx);
}
if (rowLength < lastRowLength) {
for (const end = _x + lastRowLength; ++sx < end;) {
if (!array(_y, sx)) {
Draw.fill4Core(src, sx, _y, array, mark, visited);
}
}
}
else if (rowLength > lastRowLength && _y !== 0) {
for (let ux = _x + lastRowLength; ++ux < sx;) {
if (!array(_y - 1, ux)) {
Draw.fill4(src, ux, _y - 1, array, mark, visited);
}
}
}
lastRowLength = rowLength;
} while (lastRowLength !== 0 && ++_y < src.height);
}
static fill4(src, x, y, array, mark, visited) {
let _x = x;
let _y = y;
if (visited[_y * src.width + _x] === 1) {
return;
}
while (true) {
const ox = _x;
const oy = _y;
while (_y !== 0 && !array(_y - 1, _x)) {
_y--;
}
while (_x !== 0 && !array(_y, _x - 1)) {
_x--;
}
if (_x === ox && _y === oy) {
break;
}
}
Draw.fill4Core(src, _x, _y, array, mark, visited);
}
static imgDirectComposite(src, dst, dstX, dstY, dstW, dstH, xCache, yCache, maskChannel, mask) {
let p = undefined;
if (mask !== undefined) {
for (let y = 0; y < dstH; ++y) {
for (let x = 0; x < dstW; ++x) {
const sx = xCache[x];
const sy = yCache[y];
p = src.getPixel(sx, sy, p);
const m = mask.getPixel(sx, sy).getChannelNormalized(maskChannel);
if (m === 1) {
dst.setPixel(dstX + x, dstY + y, p);
}
else {
const dp = dst.getPixel(dstX + x, dstY + y);
dp.r = MathUtils.mix(dp.r, p.r, m);
dp.g = MathUtils.mix(dp.g, p.g, m);
dp.b = MathUtils.mix(dp.b, p.b, m);
dp.a = MathUtils.mix(dp.a, p.a, m);
}
}
}
}
else {
for (let y = 0; y < dstH; ++y) {
for (let x = 0; x < dstW; ++x) {
p = src.getPixel(xCache[x], yCache[y], p);
dst.setPixel(dstX + x, dstY + y, p);
}
}
}
}
static imgComposite(src, dst, dstX, dstY, dstW, dstH, xCache, yCache, blend, linearBlend, maskChannel, mask) {
let p = undefined;
for (let y = 0; y < dstH; ++y) {
for (let x = 0; x < dstW; ++x) {
p = src.getPixel(xCache[x], yCache[y], p);
Draw.drawPixel({
image: dst,
pos: new Point(dstX + x, dstY + y),
color: p,
blend: blend,
linearBlend: linearBlend,
maskChannel: maskChannel,
mask: mask,
});
}
}
}
static drawCircle(opt) {
var _a, _b;
const antialias = (_a = opt.antialias) !== null && _a !== void 0 ? _a : false;
const maskChannel = (_b = opt.maskChannel) !== null && _b !== void 0 ? _b : Channel.luminance;
if (antialias) {
return Draw.drawAntialiasCircle({
image: opt.image,
x: opt.center.x,
y: opt.center.y,
radius: opt.radius,
color: opt.color,
mask: opt.mask,
maskChannel: maskChannel,
});
}
const points = Draw.calculateCircumference(opt.image, opt.center, opt.radius);
for (const pt of points) {
Draw.drawPixel({
image: opt.image,
pos: new Point(pt.x, pt.y),
color: opt.color,
mask: opt.mask,
maskChannel: maskChannel,
});
}
return opt.image;
}
static fillCircle(opt) {
var _a, _b;
const antialias = (_a = opt.antialias) !== null && _a !== void 0 ? _a : false;
const maskChannel = (_b = opt.maskChannel) !== null && _b !== void 0 ? _b : Channel.luminance;
const radiusSqr = opt.radius * opt.radius;
const x1 = Math.max(0, opt.center.x - opt.radius);
const y1 = Math.max(0, opt.center.y - opt.radius);
const x2 = Math.min(opt.image.width - 1, opt.center.x + opt.radius);
const y2 = Math.min(opt.image.height - 1, opt.center.y + opt.radius);
const range = opt.image.getRange(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
let it = undefined;
while (((it = range.next()), !it.done)) {
const p = it.value;
if (antialias) {
const a = ImageUtils.circleTest(p, opt.center, radiusSqr, antialias);
if (a > 0) {
const alpha = opt.color.aNormalized * a;
Draw.drawPixel({
image: opt.image,
pos: new Point(p.x, p.y),
color: opt.color,
alpha: alpha,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
else {
const dx = p.x - opt.center.x;
const dy = p.y - opt.center.y;
const d2 = dx * dx + dy * dy;
if (d2 < radiusSqr) {
Draw.drawPixel({
image: opt.image,
pos: new Point(p.x, p.y),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
}
return opt.image;
}
static drawLine(opt) {
var _a, _b, _c;
const line = opt.line.clone();
const antialias = (_a = opt.antialias) !== null && _a !== void 0 ? _a : false;
const thickness = (_b = opt.thickness) !== null && _b !== void 0 ? _b : 1;
const maskChannel = (_c = opt.maskChannel) !== null && _c !== void 0 ? _c : Channel.luminance;
if (!ImageUtils.clipLine(new Rectangle(0, 0, opt.image.width - 1, opt.image.height - 1), line)) {
return opt.image;
}
const radius = Math.floor(thickness / 2);
if (line.dx === 0 && line.dy === 0) {
if (opt.thickness === 1) {
Draw.drawPixel({
image: opt.image,
pos: new Point(line.x1, line.y1),
color: opt.color,
maskChannel: opt.maskChannel,
mask: opt.mask,
});
}
else {
Draw.fillCircle({
image: opt.image,
center: new Point(line.x1, line.y1),
radius: radius,
color: opt.color,
maskChannel: opt.maskChannel,
mask: opt.mask,
});
}
return opt.image;
}
if (line.dx === 0) {
if (line.dy < 0) {
for (let y = line.y2; y <= line.y1; ++y) {
if (thickness <= 1) {
Draw.drawPixel({
image: opt.image,
pos: new Point(line.x1, y),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
else {
for (let i = 0; i < thickness; i++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(line.x1 - radius + i, y),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
}
}
else {
for (let y = line.y1; y <= line.y2; ++y) {
if (thickness <= 1) {
Draw.drawPixel({
image: opt.image,
pos: new Point(line.x1, y),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
else {
for (let i = 0; i < thickness; i++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(line.x1 - radius + i, y),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
}
}
return opt.image;
}
else if (line.dy === 0) {
if (line.dx < 0) {
for (let x = line.x2; x <= line.x1; ++x) {
if (thickness <= 1) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x, line.y1),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
else {
for (let i = 0; i < thickness; i++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x, line.y1 - radius + i),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
}
}
else {
for (let x = line.x1; x <= line.x2; ++x) {
if (thickness <= 1) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x, line.y1),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
else {
for (let i = 0; i < thickness; i++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x, line.y1 - radius + i),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
}
}
return opt.image;
}
const xor = (n) => {
return (~n + 0x10000) & 0xffff;
};
if (!antialias) {
const dx = Math.abs(line.dx);
const dy = Math.abs(line.dy);
if (dy <= dx) {
const ac = Math.cos(Math.atan2(dy, dx));
let wid = 0;
if (ac !== 0) {
wid = Math.trunc(thickness / ac);
}
else {
wid = 1;
}
if (wid === 0) {
wid = 1;
}
let d = 2 * dy - dx;
const incr1 = 2 * dy;
const incr2 = 2 * (dy - dx);
let x = 0;
let y = 0;
let ydirflag = 0;
let xend = 0;
if (line.x1 > line.x2) {
x = line.x2;
y = line.y2;
ydirflag = -1;
xend = line.x1;
}
else {
x = line.x1;
y = line.y1;
ydirflag = 1;
xend = line.x2;
}
let wstart = Math.trunc(y - wid / 2);
for (let w = wstart; w < wstart + wid; w++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x, w),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
if ((line.y2 - line.y1) * ydirflag > 0) {
while (x < xend) {
x++;
if (d < 0) {
d += incr1;
}
else {
y++;
d += incr2;
}
wstart = Math.trunc(y - wid / 2);
for (let w = wstart; w < wstart + wid; w++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x, w),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
}
else {
while (x < xend) {
x++;
if (d < 0) {
d += incr1;
}
else {
y--;
d += incr2;
}
wstart = Math.trunc(y - wid / 2);
for (let w = wstart; w < wstart + wid; w++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x, w),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
}
}
else {
const as = Math.sin(Math.atan2(dy, dx));
let wid = 0;
if (as !== 0) {
wid = Math.trunc(thickness / as);
}
else {
wid = 1;
}
if (wid === 0) {
wid = 1;
}
let d = 2 * dx - dy;
const incr1 = 2 * dx;
const incr2 = 2 * (dx - dy);
let x = 0;
let y = 0;
let yend = 0;
let xdirflag = 0;
if (line.y1 > line.y2) {
y = line.y2;
x = line.x2;
yend = line.y1;
xdirflag = -1;
}
else {
y = line.y1;
x = line.x1;
yend = line.y2;
xdirflag = 1;
}
let wstart = Math.trunc(x - wid / 2);
for (let w = wstart; w < wstart + wid; w++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(w, y),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
if ((line.x2 - line.x1) * xdirflag > 0) {
while (y < yend) {
y++;
if (d < 0) {
d += incr1;
}
else {
x++;
d += incr2;
}
wstart = Math.trunc(x - wid / 2);
for (let w = wstart; w < wstart + wid; w++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(w, y),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
}
else {
while (y < yend) {
y++;
if (d < 0) {
d += incr1;
}
else {
x--;
d += incr2;
}
wstart = Math.trunc(x - wid / 2);
for (let w = wstart; w < wstart + wid; w++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(w, y),
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
}
}
}
return opt.image;
}
if (thickness === 1) {
return Draw.drawLineWu({
image: opt.image,
line: new Line(line.x1, line.y1, line.x2, line.y2),
color: opt.color,
});
}
const ag = Math.abs(line.dy) < Math.abs(line.dx)
? Math.cos(Math.atan2(line.dy, line.dx))
: Math.sin(Math.atan2(line.dy, line.dx));
let wid = 0;
if (ag !== 0.0) {
wid = Math.trunc(Math.abs(thickness / ag));
}
else {
wid = 1;
}
if (wid === 0) {
wid = 1;
}
if (Math.abs(line.dx) > Math.abs(line.dy)) {
if (line.dx < 0) {
line.flipX();
line.flipY();
}
let y = line.y1;
const inc = Math.trunc((line.dy * 65536) / line.dx);
let frac = 0;
for (let x = line.x1; x <= line.x2; x++) {
const wstart = y - Math.trunc(wid / 2);
for (let w = wstart; w < wstart + wid; w++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(x, w),
color: opt.color,
alpha: ((frac >>> 8) & 0xff) / 255,
maskChannel: maskChannel,
mask: opt.mask,
});
Draw.drawPixel({
image: opt.image,
pos: new Point(x, w + 1),
color: opt.color,
alpha: ((xor(frac) >>> 8) & 0xff) / 255,
maskChannel: maskChannel,
mask: opt.mask,
});
}
frac += inc;
if (frac >= 65536) {
frac -= 65536;
y++;
}
else if (frac < 0) {
frac += 65536;
y--;
}
}
}
else {
if (line.dy < 0) {
line.flipX();
line.flipY();
}
let x = line.x1;
const inc = Math.trunc((line.dx * 65536) / line.dy);
let frac = 0;
for (let y = line.y1; y <= line.y2; y++) {
const wstart = x - Math.trunc(wid / 2);
for (let w = wstart; w < wstart + wid; w++) {
Draw.drawPixel({
image: opt.image,
pos: new Point(w, y),
color: opt.color,
alpha: ((frac >>> 8) & 0xff) / 255,
maskChannel: maskChannel,
mask: opt.mask,
});
Draw.drawPixel({
image: opt.image,
pos: new Point(w + 1, y),
color: opt.color,
alpha: ((xor(frac) >>> 8) & 0xff) / 255,
maskChannel: maskChannel,
mask: opt.mask,
});
}
frac += inc;
if (frac >= 65536) {
frac -= 65536;
x++;
}
else if (frac < 0) {
frac += 65536;
x--;
}
}
}
return opt.image;
}
static drawPixel(opt) {
var _a, _b, _c, _d, _e, _f;
const blend = (_a = opt.blend) !== null && _a !== void 0 ? _a : BlendMode.alpha;
const linearBlend = (_b = opt.linearBlend) !== null && _b !== void 0 ? _b : false;
const maskChannel = (_c = opt.maskChannel) !== null && _c !== void 0 ? _c : Channel.luminance;
if (!opt.image.isBoundsSafe(opt.pos.x, opt.pos.y)) {
return opt.image;
}
if (blend === BlendMode.direct || opt.image.hasPalette) {
if (opt.image.isBoundsSafe(opt.pos.x, opt.pos.y)) {
opt.image.getPixel(opt.pos.x, opt.pos.y).set(opt.color);
return opt.image;
}
}
const maskPixel = (_d = opt.mask) === null || _d === void 0 ? void 0 : _d.getPixelSafe(opt.pos.x, opt.pos.y);
const msk = (_e = maskPixel === null || maskPixel === void 0 ? void 0 : maskPixel.getChannelNormalized(maskChannel)) !== null && _e !== void 0 ? _e : 1;
let overlayR = opt.filter !== undefined
? opt.color.rNormalized * opt.filter.rNormalized
: opt.color.rNormalized;
let overlayG = opt.filter !== undefined
? opt.color.gNormalized * opt.filter.gNormalized
: opt.color.gNormalized;
let overlayB = opt.filter !== undefined
? opt.color.bNormalized * opt.filter.bNormalized
: opt.color.bNormalized;
const a = (opt.color.length < 4 ? 1 : opt.color.aNormalized) *
(opt.filter === undefined || opt.filter.length < 4
? 1
: opt.filter.aNormalized);
const overlayA = ((_f = opt.alpha) !== null && _f !== void 0 ? _f : a) * msk;
if (overlayA === 0) {
return opt.image;
}
const dst = opt.image.getPixel(opt.pos.x, opt.pos.y);
const baseR = dst.rNormalized;
const baseG = dst.gNormalized;
const baseB = dst.bNormalized;
const baseA = dst.aNormalized;
switch (blend) {
case BlendMode.direct:
return opt.image;
case BlendMode.alpha:
break;
case BlendMode.lighten:
overlayR = Math.max(baseR, overlayR);
overlayG = Math.max(baseG, overlayG);
overlayB = Math.max(baseB, overlayB);
break;
case BlendMode.screen:
overlayR = 1 - (1 - overlayR) * (1 - baseR);
overlayG = 1 - (1 - overlayG) * (1 - baseG);
overlayB = 1 - (1 - overlayB) * (1 - baseB);
break;
case BlendMode.dodge:
{
const baseOverlayAlphaProduct = overlayA * baseA;
const rightHandProductR = overlayR * (1 - baseA) + baseR * (1 - overlayA);
const rightHandProductG = overlayG * (1 - baseA) + baseG * (1 - overlayA);
const rightHandProductB = overlayB * (1 - baseA) + baseB * (1 - overlayA);
const firstBlendColorR = baseOverlayAlphaProduct + rightHandProductR;
const firstBlendColorG = baseOverlayAlphaProduct + rightHandProductG;
const firstBlendColorB = baseOverlayAlphaProduct + rightHandProductB;
const oR = MathUtils.clamp((overlayR / MathUtils.clamp(overlayA, 0.01, 1)) *
MathUtils.step(0, overlayA), 0, 0.99);
const oG = MathUtils.clamp((overlayG / MathUtils.clamp(overlayA, 0.01, 1)) *
MathUtils.step(0, overlayA), 0, 0.99);
const oB = MathUtils.clamp((overlayB / MathUtils.clamp(overlayA, 0.01, 1)) *
MathUtils.step(0, overlayA), 0, 0.99);
const secondBlendColorR = (baseR * overlayA) / (1 - oR) + rightHandProductR;
const secondBlendColorG = (baseG * overlayA) / (1 - oG) + rightHandProductG;
const secondBlendColorB = (baseB * overlayA) / (1 - oB) + rightHandProductB;
const colorChoiceR = MathUtils.step(overlayR * baseA + baseR * overlayA, baseOverlayAlphaProduct);
const colorChoiceG = MathUtils.step(overlayG * baseA + baseG * overlayA, baseOverlayAlphaProduct);
const colorChoiceB = MathUtils.step(overlayB * baseA + baseB * overlayA, baseOverlayAlphaProduct);
overlayR = MathUtils.mix(firstBlendColorR, secondBlendColorR, colorChoiceR);
overlayG = MathUtils.mix(firstBlendColorG, secondBlendColorG, colorChoiceG);
overlayB = MathUtils.mix(firstBlendColorB, secondBlendColorB, colorChoiceB);
}
break;
case BlendMode.addition:
overlayR = baseR + overlayR;
overlayG = baseG + overlayG;
overlayB = baseB + overlayB;
break;
case BlendMode.darken:
overlayR = Math.min(baseR, overlayR);
overlayG = Math.min(baseG, overlayG);
overlayB = Math.min(baseB, overlayB);
break;
case BlendMode.multiply:
overlayR *= baseR;
overlayG *= baseG;
overlayB *= baseB;
break;
case BlendMode.burn:
overlayR = overlayR !== 0 ? 1 - (1 - baseR) / overlayR : 0;
overlayG = overlayG !== 0 ? 1 - (1 - baseG) / overlayG : 0;
overlayB = overlayB !== 0 ? 1 - (1 - baseB) / overlayB : 0;
break;
case BlendMode.overlay:
if (2 * baseR < baseA) {
overlayR =
2 * overlayR * baseR +
overlayR * (1 - baseA) +
baseR * (1 - overlayA);
}
else {
overlayR =
overlayA * baseA -
2 * (baseA - baseR) * (overlayA - overlayR) +
overlayR * (1 - baseA) +
baseR * (1 - overlayA);
}
if (2 * baseG < baseA) {
overlayG =
2 * overlayG * baseG +
overlayG * (1 - baseA) +
baseG * (1 - overlayA);
}
else {
overlayG =
overlayA * baseA -
2 * (baseA - baseG) * (overlayA - overlayG) +
overlayG * (1 - baseA) +
baseG * (1 - overlayA);
}
if (2 * baseB < baseA) {
overlayB =
2 * overlayB * baseB +
overlayB * (1 - baseA) +
baseB * (1 - overlayA);
}
else {
overlayB =
overlayA * baseA -
2 * (baseA - baseB) * (overlayA - overlayB) +
overlayB * (1 - baseA) +
baseB * (1 - overlayA);
}
break;
case BlendMode.softLight:
overlayR =
baseA === 0
? 0
: baseR *
(overlayA * (baseR / baseA) +
2 * overlayR * (1 - baseR / baseA)) +
overlayR * (1 - baseA) +
baseR * (1 - overlayA);
overlayG =
baseA === 0
? 0
: baseG *
(overlayA * (baseG / baseA) +
2 * overlayG * (1 - baseG / baseA)) +
overlayG * (1 - baseA) +
baseG * (1 - overlayA);
overlayB =
baseA === 0
? 0
: baseB *
(overlayA * (baseB / baseA) +
2 * overlayB * (1 - baseB / baseA)) +
overlayB * (1 - baseA) +
baseB * (1 - overlayA);
break;
case BlendMode.hardLight:
if (2 * overlayR < overlayA) {
overlayR =
2 * overlayR * baseR +
overlayR * (1 - baseA) +
baseR * (1 - overlayA);
}
else {
overlayR =
overlayA * baseA -
2 * (baseA - baseR) * (overlayA - overlayR) +
overlayR * (1 - baseA) +
baseR * (1 - overlayA);
}
if (2 * overlayG < overlayA) {
overlayG =
2 * overlayG * baseG +
overlayG * (1 - baseA) +
baseG * (1 - overlayA);
}
else {
overlayG =
overlayA * baseA -
2 * (baseA - baseG) * (overlayA - overlayG) +
overlayG * (1 - baseA) +
baseG * (1 - overlayA);
}
if (2 * overlayB < overlayA) {
overlayB =
2 * overlayB * baseB +
overlayB * (1 - baseA) +
baseB * (1 - overlayA);
}
else {
overlayB =
overlayA * baseA -
2 * (baseA - baseB) * (overlayA - overlayB) +
overlayB * (1 - baseA) +
baseB * (1 - overlayA);
}
break;
case BlendMode.difference:
overlayR = Math.abs(overlayR - baseR);
overlayG = Math.abs(overlayG - baseG);
overlayB = Math.abs(overlayB - baseB);
break;
case BlendMode.subtract:
overlayR = baseR - overlayR;
overlayG = baseG - overlayG;
overlayB = baseB - overlayB;
break;
case BlendMode.divide:
overlayR = overlayR !== 0 ? baseR / overlayR : 0;
overlayG = overlayG !== 0 ? baseG / overlayG : 0;
overlayB = overlayB !== 0 ? baseB / overlayB : 0;
break;
}
const invA = 1 - overlayA;
if (linearBlend) {
const lbr = Math.pow(baseR, 2.2);
const lbg = Math.pow(baseG, 2.2);
const lbb = Math.pow(baseB, 2.2);
const lor = Math.pow(overlayR, 2.2);
const log = Math.pow(overlayG, 2.2);
const lob = Math.pow(overlayB, 2.2);
const r = Math.pow(lor * overlayA + lbr * baseA * invA, 1 / 2.2);
const g = Math.pow(log * overlayA + lbg * baseA * invA, 1 / 2.2);
const b = Math.pow(lob * overlayA + lbb * baseA * invA, 1 / 2.2);
const a = overlayA + baseA * invA;
dst.rNormalized = r;
dst.gNormalized = g;
dst.bNormalized = b;
dst.aNormalized = a;
}
else {
const r = overlayR * overlayA + baseR * baseA * invA;
const g = overlayG * overlayA + baseG * baseA * invA;
const b = overlayB * overlayA + baseB * baseA * invA;
const a = overlayA + baseA * invA;
dst.rNormalized = r;
dst.gNormalized = g;
dst.bNormalized = b;
dst.aNormalized = a;
}
return opt.image;
}
static drawPolygon(opt) {
var _a, _b, _c;
const antialias = (_a = opt.antialias) !== null && _a !== void 0 ? _a : false;
const thickness = (_b = opt.thickness) !== null && _b !== void 0 ? _b : 1;
const maskChannel = (_c = opt.maskChannel) !== null && _c !== void 0 ? _c : Channel.luminance;
if (opt.color.a === 0) {
return opt.image;
}
const vertices = opt.vertices;
const numVertices = vertices.length;
if (numVertices === 0) {
return opt.image;
}
if (numVertices === 1) {
return Draw.drawPixel({
image: opt.image,
pos: vertices[0],
color: opt.color,
maskChannel: maskChannel,
mask: opt.mask,
});
}
if (numVertices === 2) {
return Draw.drawLine({
image: opt.image,
line: new Line(vertices[0].x, vertices[0].y, vertices[1].x, vertices[1].y),
color: opt.color,
antialias: antialias,
thickness: thickness,
maskChannel: maskChannel,
mask: opt.mask,
});
}
for (let i = 0; i < numVertices - 1; ++i) {
Draw.drawLine({
image: opt.image,
line: new Line(vertices[i].x, vertices[i].y, vertices[i + 1].x, vertices[i + 1].y),
color: opt.color,
antialias: antialias,
thickness: thickness,
maskChannel: maskChannel,
mask: opt.mask,
});
}
Draw.drawLine({
image: opt.image,
line: new Line(vertices[numVertices - 1].x, vertices[numVertices - 1].y, vertices[0].x, vertices[0].y),
color: opt.color,
antialias: antialias,
thickness: thickness,
maskChannel: maskChannel,
mask: opt.mask,
});
return opt.image;
}
static drawRect(opt) {
var _a, _b, _c;
const rect = opt.rect;
const thickness = (_a = opt.thickness) !== null && _a !== void 0 ? _a : 1;
const radius = (_b = opt.radius) !== null && _b !== void 0 ? _b : 0;
const maskChannel = (_c = opt.maskChannel) !== null && _c !== void 0 ? _c : Channel.luminance;
const x0 = rect.left;
const y0 = rect.top;
const x1 = rect.right;
const y1 = rect.bottom;
if (radius > 0) {
const rad = Math.round(radius);
Draw.drawLine({
image: opt.image,
line: new Line(x0 + rad, y0, x1 - rad, y0),
color: opt.color,
});
Draw.drawLine({
image: opt.image,
line: new Line(x1, y0 + rad, x1, y1 - rad),
color: opt.color,
});
Draw.drawLine({
image: opt.image,
line: new Line(x0 + rad, y1, x1 - rad, y1),
color: opt.color,
});
Draw.drawLine({
image: opt.image,
line: new Line(x0, y0 + rad, x0, y1 - rad),
color: opt.color,
});
const c1x = x0 + rad;
const c1y = y0 + rad;
const c2x = x1 - rad;
const c2y = y0 + rad;
const c3x = x1 - rad;
const c3y = y1 - rad;
const c4x = x0 + rad;
const c4y = y1 - rad;
Draw.drawAntialiasCircle({
image: opt.image,
x: c1x,
y: c1y,
radius: rad,
color: opt.color,
maskChannel: maskChannel,
quadrants: CircleQuadrant.topLeft,
mask: opt.mask,
});
Draw.drawAntialiasCircle({
image: opt.image,
x: c2x,
y: c2y,
radius: rad,
color: opt.color,
maskChannel: maskChannel,
quadrants: CircleQuadrant.topRight,
mask: opt.mask,
});
Draw.drawAntialiasCircle({
image: opt.image,
x: c3x,
y: c3y,
radius: rad,
color: opt.color,
maskChannel: maskChannel,
quadrants: CircleQuadrant.bottomRight,
mask: opt.mask,
});
Draw.drawAntialiasCircle({
image: opt.image,
x: c4x,
y: c4y,
radius: rad,
color: opt.color,
maskChannel: maskChannel,
quadrants: CircleQuadrant.bottomLeft,
mask: opt.mask,
});
return opt.image;
}
const ht = thickness / 2;
Draw