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,275 lines 65.5 kB
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