UNPKG

bresenham-zingl

Version:

Bresenham rasterisation algorithms by Alois Zingl

571 lines (555 loc) 14.6 kB
import assert from "../assert"; import { quadBezierSegmentAA, quadBezierSegment } from "./quadratic"; import { line, lineAA } from "../line"; import { setPixelAlphaFn, setPixelFn } from "../types"; /** * Plot limited cubic Bezier segment * @param {number} x0 * @param {number} y0 * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @param {number} x3 * @param {number} y3 * @param {setPixel} setPixel */ export function cubicBezierSegment( x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, setPixel: setPixelFn ) { var f: number, fx: number, fy: number, leg = 1; let sx = x0 < x3 ? 1 : -1; let sy = y0 < y3 ? 1 : -1; /* step direction */ let xc = -Math.abs(x0 + x1 - x2 - x3); let xa = xc - 4 * sx * (x1 - x2); let xb = sx * (x0 - x1 - x2 + x3); let yc = -Math.abs(y0 + y1 - y2 - y3); let ya = yc - 4 * sy * (y1 - y2); let yb = sy * (y0 - y1 - y2 + y3); let ab: number, ac: number, bc: number, cb: number, xx: number, xy: number, yy: number, dx: number, dy: number, ex: number, pxy: number, EP = 0.01; /* check for curve restrains */ /* slope P0-P1 == P2-P3 and (P0-P3 == P1-P2 or no slope change) */ assert( (x1 - x0) * (x2 - x3) < EP && ((x3 - x0) * (x1 - x2) < EP || xb * xb < xa * xc + EP), "slope change" ); assert( (y1 - y0) * (y2 - y3) < EP && ((y3 - y0) * (y1 - y2) < EP || yb * yb < ya * yc + EP), "slope change" ); if (xa == 0 && ya == 0) { /* quadratic Bezier */ sx = Math.floor((3 * x1 - x0 + 1) / 2); sy = Math.floor((3 * y1 - y0 + 1) / 2); /* new midpoint */ return quadBezierSegment(x0, y0, sx, sy, x3, y3, setPixel); } x1 = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0) + 1; /* line lengths */ x2 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3) + 1; do { /* loop over both ends */ ab = xa * yb - xb * ya; ac = xa * yc - xc * ya; bc = xb * yc - xc * yb; ex = ab * (ab + ac - 3 * bc) + ac * ac; /* P0 part of self-intersection loop? */ f = ex > 0 ? 1 : Math.sqrt(1 + 1024 / x1); /* calculate resolution */ ab *= f; ac *= f; bc *= f; ex *= f * f; /* increase resolution */ xy = (9 * (ab + ac + bc)) / 8; cb = 8 * (xa - ya); /* init differences of 1st degree */ dx = (27 * (8 * ab * (yb * yb - ya * yc) + ex * (ya + 2 * yb + yc))) / 64 - ya * ya * (xy - ya); dy = (27 * (8 * ab * (xb * xb - xa * xc) - ex * (xa + 2 * xb + xc))) / 64 - xa * xa * (xy + xa); /* init differences of 2nd degree */ xx = (3 * (3 * ab * (3 * yb * yb - ya * ya - 2 * ya * yc) - ya * (3 * ac * (ya + yb) + ya * cb))) / 4; yy = (3 * (3 * ab * (3 * xb * xb - xa * xa - 2 * xa * xc) - xa * (3 * ac * (xa + xb) + xa * cb))) / 4; xy = xa * ya * (6 * ab + 6 * ac - 3 * bc + cb); ac = ya * ya; cb = xa * xa; xy = (3 * (xy + 9 * f * (cb * yb * yc - xb * xc * ac) - 18 * xb * yb * ab)) / 8; if (ex < 0) { /* negate values if inside self-intersection loop */ dx = -dx; dy = -dy; xx = -xx; yy = -yy; xy = -xy; ac = -ac; cb = -cb; } /* init differences of 3rd degree */ ab = 6 * ya * ac; ac = -6 * xa * ac; bc = 6 * ya * cb; cb = -6 * xa * cb; dx += xy; ex = dx + dy; dy += xy; /* error of 1st step */ outer: for (pxy = xy, fx = fy = f; x0 != x3 && y0 != y3; ) { setPixel(x0, y0); /* plot curve */ do { /* move sub-steps of one pixel */ if (dx > pxy || dy < pxy) { break outer; } /* confuMath.sing values */ y1 = 2 * ex - dy; /* save value for test of y step */ if (2 * ex >= dx) { /* x sub-step */ fx--; ex += dx += xx; dy += xy += ac; yy += bc; xx += ab; } if (y1 <= 0) { /* y sub-step */ fy--; ex += dy += yy; dx += xy += bc; xx += ac; yy += cb; } } while (fx > 0 && fy > 0); /* pixel complete? */ if (2 * fx <= f) { x0 += sx; fx += f; } /* x step */ if (2 * fy <= f) { y0 += sy; fy += f; } /* y step */ if (pxy == xy && dx < 0 && dy > 0) pxy = EP; /* pixel ahead valid */ } //exit: xx = x0; x0 = x3; x3 = xx; sx = -sx; xb = -xb; /* swap legs */ yy = y0; y0 = y3; y3 = yy; sy = -sy; yb = -yb; x1 = x2; } while (leg--); /* try other end */ line( x0, y0, x3, y3, setPixel ); /* remaining part in case of cusp or crunode */ } /** * Plot limited anti-aliased cubic Bezier segment * @param {Number} x0 * @param {Number} y0 * @param {Number} x1 * @param {Number} y1 * @param {Number} x2 * @param {Number} y2 * @param {Number} x3 * @param {Number} y3 * @param {setPixelAA} setPixelAA */ export function cubicBezierSegmentAA( x0, y0, x1, y1, x2, y2, x3, y3, setPixelAA ) { let f, fx, fy, leg = 1; let sx = x0 < x3 ? 1 : -1, sy = y0 < y3 ? 1 : -1; /* step direction */ let xc = -Math.abs(x0 + x1 - x2 - x3), xa = xc - 4 * sx * (x1 - x2), xb = sx * (x0 - x1 - x2 + x3); let yc = -Math.abs(y0 + y1 - y2 - y3), ya = yc - 4 * sy * (y1 - y2), yb = sy * (y0 - y1 - y2 + y3); let ab, ac, bc, ba, xx, xy, yy, dx, dy, ex, px, py, ed, ip; const EP = 0.01; /* check for curve restrains */ /* slope P0-P1 == P2-P3 and (P0-P3 == P1-P2 or no slope change) */ assert( (x1 - x0) * (x2 - x3) < EP && ((x3 - x0) * (x1 - x2) < EP || xb * xb < xa * xc + EP) ); assert( (y1 - y0) * (y2 - y3) < EP && ((y3 - y0) * (y1 - y2) < EP || yb * yb < ya * yc + EP) ); if (xa === 0 && ya === 0) { /* quadratic Bezier */ sx = Math.floor((3 * x1 - x0 + 1) / 2); sy = Math.floor((3 * y1 - y0 + 1) / 2); /* new midpoint */ return quadBezierSegmentAA(x0, y0, sx, sy, x3, y3, setPixelAA); } x1 = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0) + 1; /* line lengths */ x2 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3) + 1; do { /* loop over both ends */ ab = xa * yb - xb * ya; ac = xa * yc - xc * ya; bc = xb * yc - xc * yb; ip = 4 * ab * bc - ac * ac; /* self intersection loop at all? */ /* P0 part of self-intersection loop? */ ex = ab * (ab + ac - 3 * bc) + ac * ac; f = ex > 0 ? 1 : Math.sqrt(1 + 1024 / x1); /* calculate resolution */ ab *= f; ac *= f; bc *= f; ex *= f * f; /* increase resolution */ xy = (9 * (ab + ac + bc)) / 8; ba = 8 * (xa - ya); /* init differences of 1st degree */ dx = (27 * (8 * ab * (yb * yb - ya * yc) + ex * (ya + 2 * yb + yc))) / 64 - ya * ya * (xy - ya); dy = (27 * (8 * ab * (xb * xb - xa * xc) - ex * (xa + 2 * xb + xc))) / 64 - xa * xa * (xy + xa); /* init differences of 2nd degree */ xx = (3 * (3 * ab * (3 * yb * yb - ya * ya - 2 * ya * yc) - ya * (3 * ac * (ya + yb) + ya * ba))) / 4; yy = (3 * (3 * ab * (3 * xb * xb - xa * xa - 2 * xa * xc) - xa * (3 * ac * (xa + xb) + xa * ba))) / 4; xy = xa * ya * (6 * ab + 6 * ac - 3 * bc + ba); ac = ya * ya; ba = xa * xa; xy = (3 * (xy + 9 * f * (ba * yb * yc - xb * xc * ac) - 18 * xb * yb * ab)) / 8; if (ex < 0) { /* negate values if inside self-intersection loop */ dx = -dx; dy = -dy; xx = -xx; yy = -yy; xy = -xy; ac = -ac; ba = -ba; } /* init differences of 3rd degree */ ab = 6 * ya * ac; ac = -6 * xa * ac; bc = 6 * ya * ba; ba = -6 * xa * ba; dx += xy; ex = dx + dy; dy += xy; /* error of 1st step */ let exit = false; outer: for (fx = fy = f; x0 !== x3 && y0 !== y3; ) { y1 = Math.min(Math.abs(xy - dx), Math.abs(dy - xy)); /* approximate error distance */ ed = Math.max(Math.abs(xy - dx), Math.abs(dy - xy)); ed = f * (ed + (2 * ed * y1 * y1) / (4 * ed * ed + y1 * y1)); y1 = (255 * Math.abs(ex - (f - fx + 1) * dx - (f - fy + 1) * dy + f * xy)) / ed; if (y1 < 256) setPixelAA(x0, y0, y1); /* plot curve */ /* pixel intensity x move */ px = Math.abs(ex - (f - fx + 1) * dx + (fy - 1) * dy); /* pixel intensity y move */ py = Math.abs(ex + (fx - 1) * dx - (f - fy + 1) * dy); y2 = y0; do { /* move sub-steps of one pixel */ if (ip >= -EP) if (dx + xx > xy || dy + yy < xy) { /* intersection possible? -> check.. */ /* two x or y steps */ exit = true; break outer; } y1 = 2 * ex + dx; /* save value for test of y step */ if (2 * ex + dy > 0) { /* x sub-step */ fx--; ex += dx += xx; dy += xy += ac; yy += bc; xx += ab; } else if (y1 > 0) { /* tiny nearly cusp */ exit = true; break outer; } if (y1 <= 0) { /* y sub-step */ fy--; ex += dy += yy; dx += xy += bc; xx += ac; yy += ba; } } while (fx > 0 && fy > 0); /* pixel complete? */ if (2 * fy <= f) { /* x+ anti-aliasing pixel */ if (py < ed) setPixelAA(x0 + sx, y0, (255 * py) / ed); /* plot curve */ y0 += sy; fy += f; /* y step */ } if (2 * fx <= f) { /* y+ anti-aliasing pixel */ if (px < ed) setPixelAA(x0, y2 + sy, (255 * px) / ed); /* plot curve */ x0 += sx; fx += f; /* x step */ } } if (exit) { if (2 * ex < dy && 2 * fy <= f + 2) { /* round x+ approximation pixel */ if (py < ed) setPixelAA(x0 + sx, y0, (255 * py) / ed); /* plot curve */ y0 += sy; } if (2 * ex > dx && 2 * fx <= f + 2) { /* round y+ approximation pixel */ if (px < ed) setPixelAA(x0, y2 + sy, (255 * px) / ed); /* plot curve */ x0 += sx; } xx = x0; x0 = x3; x3 = xx; sx = -sx; xb = -xb; /* swap legs */ yy = y0; y0 = y3; y3 = yy; sy = -sy; yb = -yb; x1 = x2; } break; /* finish curve by line */ } while (leg--); /* try other end */ /* remaining part in case of cusp or crunode */ lineAA(x0, y0, x3, y3, setPixelAA); } function cubicBezierGeneral( x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, segment: typeof cubicBezierSegment | typeof cubicBezierSegmentAA, setPixel: setPixelFn | setPixelAlphaFn ) { let n = 0; let i = 0; let xc = x0 + x1 - x2 - x3; let xa = xc - 4 * (x1 - x2); let xb = x0 - x1 - x2 + x3; let xd = xb + 4 * (x1 + x2); let yc = y0 + y1 - y2 - y3; let ya = yc - 4 * (y1 - y2); let yb = y0 - y1 - y2 + y3; let yd = yb + 4 * (y1 + y2); var fx0 = x0, fx1: number, fx2: number, fx3: number, fy0 = y0, fy1: number, fy2: number, fy3: number; let t1 = xb * xb - xa * xc; let t2: number; const t = [0, 0, 0, 0, 0]; /* sub-divide curve at gradient sign changes */ if (xa == 0) { /* horizontal */ if (Math.abs(xc) < 2 * Math.abs(xb)) t[n++] = xc / (2 * xb); /* one change */ } else if (t1 > 0.0) { /* two changes */ t2 = Math.sqrt(t1); t1 = (xb - t2) / xa; if (Math.abs(t1) < 1) t[n++] = t1; t1 = (xb + t2) / xa; if (Math.abs(t1) < 1) t[n++] = t1; } t1 = yb * yb - ya * yc; if (ya == 0) { /* vertical */ if (Math.abs(yc) < 2 * Math.abs(yb)) t[n++] = yc / (2 * yb); /* one change */ } else if (t1 > 0.0) { /* two changes */ t2 = Math.sqrt(t1); t1 = (yb - t2) / ya; if (Math.abs(t1) < 1) t[n++] = t1; t1 = (yb + t2) / ya; if (Math.abs(t1) < 1) t[n++] = t1; } for (i = 1; i < n; i++ /* bubble sort of 4 points */) if ((t1 = t[i - 1]) > t[i]) { t[i - 1] = t[i]; t[i] = t1; i = 0; } t1 = -1; t[n] = 1; /* begin / end point */ for (i = 0; i <= n; i++) { /* plot each segment separately */ t2 = t[i]; /* sub-divide at t[i-1], t[i] */ fx1 = (t1 * (t1 * xb - 2 * xc) - t2 * (t1 * (t1 * xa - 2 * xb) + xc) + xd) / 8 - fx0; fy1 = (t1 * (t1 * yb - 2 * yc) - t2 * (t1 * (t1 * ya - 2 * yb) + yc) + yd) / 8 - fy0; fx2 = (t2 * (t2 * xb - 2 * xc) - t1 * (t2 * (t2 * xa - 2 * xb) + xc) + xd) / 8 - fx0; fy2 = (t2 * (t2 * yb - 2 * yc) - t1 * (t2 * (t2 * ya - 2 * yb) + yc) + yd) / 8 - fy0; fx0 -= fx3 = (t2 * (t2 * (3 * xb - t2 * xa) - 3 * xc) + xd) / 8; fy0 -= fy3 = (t2 * (t2 * (3 * yb - t2 * ya) - 3 * yc) + yd) / 8; x3 = Math.floor(fx3 + 0.5); y3 = Math.floor(fy3 + 0.5); /* scale bounds to int */ if (fx0 != 0.0) { fx1 *= fx0 = (x0 - x3) / fx0; fx2 *= fx0; } if (fy0 != 0.0) { fy1 *= fy0 = (y0 - y3) / fy0; fy2 *= fy0; } if (x0 != x3 || y0 != y3) /* @ts-expect-error segment t1 - t2 */ segment(x0, y0, x0 + fx1, y0 + fy1, x0 + fx2, y0 + fy2, x3, y3, setPixel); x0 = x3; y0 = y3; fx0 = fx3; fy0 = fy3; t1 = t2; } } /** * plot any cubic Bezier curve * @param {number} x0 * @param {number} y0 * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @param {number} x3 * @param {number} y3 * @param {setPixel} setPixel */ export function cubicBezier( x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, setPixel ) { cubicBezierGeneral( x0, y0, x1, y1, x2, y2, x3, y3, cubicBezierSegment, setPixel ); } /** * plot any cubic Bezier curve * @param {number} x0 * @param {number} y0 * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @param {number} x3 * @param {number} y3 * @param {setPixelAlpha} setPixelAA */ export function cubicBezierAA( x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, setPixelAA: setPixelAlphaFn ) { cubicBezierGeneral( x0, y0, x1, y1, x2, y2, x3, y3, cubicBezierSegmentAA, setPixelAA ); }