bresenham-zingl
Version:
Bresenham rasterisation algorithms by Alois Zingl
371 lines (357 loc) • 9.94 kB
text/typescript
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 */
}