UNPKG

bresenham-zingl

Version:

Bresenham rasterisation algorithms by Alois Zingl

371 lines (357 loc) 9.94 kB
import assert from "../assert"; import { line, lineAA } from "../line"; /** * plot any quadratic rational Bezier curve * @param {number} x0 * @param {number} y0 * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @param {number} w * @param {setPixel} setPixel */ export function quadRationalBezier( x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, w: number, setPixel ) { var x = x0 - 2 * x1 + x2, y = y0 - 2 * y1 + y2; var xx = x0 - x1, yy = y0 - y1, ww, t, q; assert(w >= 0.0, "width is negative"); if (xx * (x2 - x1) > 0) { /* horizontal cut at P4? */ if (yy * (y2 - y1) > 0) if (Math.abs(xx * y) > Math.abs(yy * x)) { /* vertical cut at P6 too? */ /* which first? */ x0 = x2; x2 = xx + x1; y0 = y2; y2 = yy + y1; /* swap points */ } /* now horizontal cut at P4 comes first */ if (x0 == x2 || w == 1) t = (x0 - x1) / x; else { /* non-rational or rational case */ q = Math.sqrt(4 * w * w * (x0 - x1) * (x2 - x1) + (x2 - x0) * (x2 - x0)); if (x1 < x0) q = -q; t = (2 * w * (x0 - x1) - x0 + x2 + q) / (2 * (1 - w) * (x2 - x0)); /* t at P4 */ } q = 1 / (2 * t * (1 - t) * (w - 1) + 1); /* sub-divide at t */ xx = (t * t * (x0 - 2 * w * x1 + x2) + 2 * t * (w * x1 - x0) + x0) * q; /* = P4 */ yy = (t * t * (y0 - 2 * w * y1 + y2) + 2 * t * (w * y1 - y0) + y0) * q; ww = t * (w - 1) + 1; ww *= ww * q; /* squared weight P3 */ w = ((1 - t) * (w - 1) + 1) * Math.sqrt(q); /* weight P8 */ x = Math.floor(xx + 0.5); y = Math.floor(yy + 0.5); /* P4 */ yy = ((xx - x0) * (y1 - y0)) / (x1 - x0) + y0; /* intersect P3 | P0 P1 */ quadRationalBezierSegment( x0, y0, x, Math.floor(yy + 0.5), x, y, ww, setPixel ); yy = ((xx - x2) * (y1 - y2)) / (x1 - x2) + y2; /* intersect P4 | P1 P2 */ y1 = Math.floor(yy + 0.5); x0 = x1 = x; y0 = y; /* P0 = P4, P1 = P8 */ } if ((y0 - y1) * (y2 - y1) > 0) { /* vertical cut at P6? */ if (y0 == y2 || w == 1) t = (y0 - y1) / (y0 - 2 * y1 + y2); else { /* non-rational or rational case */ q = Math.sqrt(4 * w * w * (y0 - y1) * (y2 - y1) + (y2 - y0) * (y2 - y0)); if (y1 < y0) q = -q; t = (2 * w * (y0 - y1) - y0 + y2 + q) / (2 * (1 - w) * (y2 - y0)); /* t at P6 */ } q = 1 / (2 * t * (1 - t) * (w - 1) + 1); /* sub-divide at t */ xx = (t * t * (x0 - 2 * w * x1 + x2) + 2 * t * (w * x1 - x0) + x0) * q; /* = P6 */ yy = (t * t * (y0 - 2 * w * y1 + y2) + 2 * t * (w * y1 - y0) + y0) * q; ww = t * (w - 1) + 1; ww *= ww * q; /* squared weight P5 */ w = ((1 - t) * (w - 1) + 1) * Math.sqrt(q); /* weight P7 */ x = Math.floor(xx + 0.5); y = Math.floor(yy + 0.5); /* P6 */ xx = ((x1 - x0) * (yy - y0)) / (y1 - y0) + x0; /* intersect P6 | P0 P1 */ quadRationalBezierSegment( x0, y0, Math.floor(xx + 0.5), y, x, y, ww, setPixel ); xx = ((x1 - x2) * (yy - y2)) / (y1 - y2) + x2; /* intersect P7 | P1 P2 */ x1 = Math.floor(xx + 0.5); x0 = x; y0 = y1 = y; /* P0 = P6, P1 = P7 */ } quadRationalBezierSegment( x0, y0, x1, y1, x2, y2, w * w, setPixel ); /* remaining */ } /** * plot a limited rational Bezier segment, squared weight * @param {number} x0 * @param {number} y0 * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @param {number} w * @param {setPixel} setPixel */ export function quadRationalBezierSegment( x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, w: number, setPixel ) { var sx = x2 - x1, sy = y2 - y1; /* relative values for checks */ var dx = x0 - x2, dy = y0 - y2, xx = x0 - x1, yy = y0 - y1; var xy = xx * sy + yy * sx, cur = xx * sy - yy * sx, err; /* curvature */ assert(xx * sx <= 0.0 && yy * sy <= 0.0, "sign of gradient must not change"); if (cur != 0.0 && w > 0.0) { /* no straight line */ if (sx * sx + sy * sy > xx * xx + yy * yy) { /* begin with longer part */ x2 = x0; x0 -= dx; y2 = y0; y0 -= dy; cur = -cur; /* swap P0 P2 */ } xx = 2 * (4 * w * sx * xx + dx * dx); /* differences 2nd degree */ yy = 2 * (4 * w * sy * yy + dy * dy); sx = x0 < x2 ? 1 : -1; /* x step direction */ sy = y0 < y2 ? 1 : -1; /* y step direction */ xy = -2 * sx * sy * (2 * w * xy + dx * dy); if (cur * sx * sy < 0.0) { /* negated curvature? */ xx = -xx; yy = -yy; xy = -xy; cur = -cur; } dx = 4 * w * (x1 - x0) * sy * cur + xx / 2 + xy; /* differences 1st degree */ dy = 4 * w * (y0 - y1) * sx * cur + yy / 2 + xy; if (w < 0.5 && (dy > xy || dx < xy)) { /* flat ellipse, algorithm fails */ cur = (w + 1) / 2; w = Math.sqrt(w); xy = 1 / (w + 1); sx = Math.floor( ((x0 + 2 * w * x1 + x2) * xy) / 2 + 0.5 ); /* subdivide curve in half */ sy = Math.floor(((y0 + 2 * w * y1 + y2) * xy) / 2 + 0.5); dx = Math.floor((w * x1 + x0) * xy + 0.5); dy = Math.floor((y1 * w + y0) * xy + 0.5); quadRationalBezierSegment( x0, y0, dx, dy, sx, sy, cur, setPixel ); /* plot separately */ dx = Math.floor((w * x1 + x2) * xy + 0.5); dy = Math.floor((y1 * w + y2) * xy + 0.5); quadRationalBezierSegment(sx, sy, dx, dy, x2, y2, cur, setPixel); return; } err = dx + dy - xy; /* error 1.step */ do { setPixel(x0, y0); /* plot curve */ if (x0 == x2 && y0 == y2) return; /* last pixel -> curve finished */ x1 = 2 * err > dy ? 1 : 0; y1 = 2 * (err + yy) < -dy ? 1 : 0; /* save value for test of x step */ if (2 * err < dx || y1) { y0 += sy; dy += xy; err += dx += xx; } /* y step */ if (2 * err > dx || x1) { x0 += sx; dx += xy; err += dy += yy; } /* x step */ } while (dy <= xy && dx >= xy); /* gradient negates -> algorithm fails */ } line(x0, y0, x2, y2, setPixel); /* plot remaining needle to end */ } /** * draw an anti-aliased rational quadratic Bezier segment, squared weight * @param {number} x0 * @param {number} y0 * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @param {number} w * @param {setPixelAlpha} setPixelAA */ export function quadRationalBezierSegmentAA( x0, y0, x1, y1, x2, y2, w, setPixelAA ) { var sx = x2 - x1, sy = y2 - y1; /* relative values for checks */ var dx = x0 - x2, dy = y0 - y2, xx = x0 - x1, yy = y0 - y1; var xy = xx * sy + yy * sx, cur = xx * sy - yy * sx, err, ed; /* curvature */ var f; assert( xx * sx <= 0.0 && yy * sy <= 0.0 ); /* sign of gradient must not change */ if (cur != 0.0 && w > 0.0) { /* no straight line */ if (sx * sx + sy * sy > xx * xx + yy * yy) { /* begin with longer part */ x2 = x0; x0 -= dx; y2 = y0; y0 -= dy; cur = -cur; /* swap P0 P2 */ } xx = 2 * (4 * w * sx * xx + dx * dx); /* differences 2nd degree */ yy = 2 * (4 * w * sy * yy + dy * dy); sx = x0 < x2 ? 1 : -1; /* x step direction */ sy = y0 < y2 ? 1 : -1; /* y step direction */ xy = -2 * sx * sy * (2 * w * xy + dx * dy); if (cur * sx * sy < 0) { /* negated curvature? */ xx = -xx; yy = -yy; cur = -cur; xy = -xy; } dx = 4 * w * (x1 - x0) * sy * cur + xx / 2 + xy; /* differences 1st degree */ dy = 4 * w * (y0 - y1) * sx * cur + yy / 2 + xy; if (w < 0.5 && dy > dx) { /* flat ellipse, algorithm fails */ cur = (w + 1) / 2; w = Math.sqrt(w); xy = 1 / (w + 1); sx = Math.floor( ((x0 + 2 * w * x1 + x2) * xy) / 2 + 0.5 ); /* subdivide curve in half */ sy = Math.floor(((y0 + 2 * w * y1 + y2) * xy) / 2 + 0.5); dx = Math.floor((w * x1 + x0) * xy + 0.5); dy = Math.floor((y1 * w + y0) * xy + 0.5); quadRationalBezierSegmentAA( x0, y0, dx, dy, sx, sy, cur, setPixelAA ); /* plot apart */ dx = Math.floor((w * x1 + x2) * xy + 0.5); dy = Math.floor((y1 * w + y2) * xy + 0.5); return quadRationalBezierSegmentAA( sx, sy, dx, dy, x2, y2, cur, setPixelAA ); } err = dx + dy - xy; /* error 1st step */ do { /* pixel loop */ cur = Math.min(dx - xy, xy - dy); ed = Math.max(dx - xy, xy - dy); ed += (2 * ed * cur * cur) / (4 * ed * ed + cur * cur); /* approximate error distance */ x1 = (255 * Math.abs(err - dx - dy + xy)) / ed; /* get blend value by pixel error */ if (x1 < 256) setPixelAA(x0, y0, x1); /* plot curve */ if ((f = 2 * err + dy < 0)) { /* y step */ if (y0 == y2) return; /* last pixel -> curve finished */ if (dx - err < ed) setPixelAA(x0 + sx, y0, (255 * Math.abs(dx - err)) / ed); } if (2 * err + dx > 0) { /* x step */ if (x0 == x2) return; /* last pixel -> curve finished */ if (err - dy < ed) setPixelAA(x0, y0 + sy, (255 * Math.abs(err - dy)) / ed); x0 += sx; dx += xy; err += dy += yy; } if (f) { y0 += sy; dy += xy; err += dx += xx; } /* y step */ } while (dy < dx); /* gradient negates -> algorithm fails */ } lineAA(x0, y0, x2, y2, setPixelAA); /* plot remaining needle to end */ }