/* See LICENSE file for copyright and license details. */
#include "common.h"
#ifndef TEST
#ifndef ROWWISE_RESET_INKLEVEL
void
draw_vertical_line_opposite_only(RASTER *restrict raster, size_t x, double y1, double y2, int ydir)
{
size_t y, start, end;
CELL *cells;
cells = &raster->cells[x];
start = (size_t)y1;
end = (size_t)y2;
y = start;
if (ydir > 0) {
cells[end * raster->width].opposite_coverage -= 1.0 - (y2 - (double)end);
cells[start * raster->width].opposite_coverage -= y1 - (double)start;
cells = &cells[start * raster->width];
do {
cells->opposite_coverage += 1.0;
cells += raster->width;
} while (y++ != end);
} else {
cells[end * raster->width].opposite_coverage += y2 - (double)end;
cells[start * raster->width].opposite_coverage += 1.0 - (y1 - (double)start);
cells = &cells[start * raster->width];
do {
cells->opposite_coverage -= 1.0;
cells -= raster->width;
} while (y-- != end);
}
}
#endif
void
draw_vertical_line(RASTER *restrict raster, double x1, double y1, double y2, int ydir)
{
size_t x, y, start, end;
double cell_coverage;
CELL *cells;
x = (size_t)x1;
cell_coverage = 1.0 - (x1 - (double)x);
cells = &raster->cells[x];
start = (size_t)y1;
end = (size_t)y2;
start -= (size_t)(start == raster->height);
end -= (size_t)(end == raster->height);
y = start;
if (ydir > 0) {
cells[end * raster->width].opposite_coverage -= 1.0 - (y2 - (double)end);
cells[end * raster->width].cell_coverage -= (1.0 - (y2 - (double)end)) * cell_coverage;
cells[start * raster->width].opposite_coverage -= y1 - (double)start;
cells[start * raster->width].cell_coverage -= (y1 - (double)start) * cell_coverage;
cells = &cells[start * raster->width];
do {
cells->opposite_coverage += 1.0;
cells->cell_coverage += cell_coverage;
cells += raster->width;
} while (y++ != end);
} else {
cells[end * raster->width].opposite_coverage += y2 - (double)end;
cells[end * raster->width].cell_coverage += (y2 - (double)end) * cell_coverage;
cells[start * raster->width].opposite_coverage += 1.0 - (y1 - (double)start);
cells[start * raster->width].cell_coverage += (1.0 - (y1 - (double)start)) * cell_coverage;
cells = &cells[start * raster->width];
do {
cells->opposite_coverage -= 1.0;
cells->cell_coverage -= cell_coverage;
cells -= raster->width;
} while (y-- != end);
}
}
void
draw_diagonal_line(RASTER *restrict raster, double x1, double y1, double x2, double y2,
double dx, double dy, int xdir, int ydir)
{
/* This code is taken, with rewrites and added documentation,
* from Thomas Oltmann's ISC licensed libschrift. */
ssize_t rowsize;
CELL *cell;
size_t startCellX, startCellY, step, nsteps = 0;
double ydiff, halfdx, one_minus_x1_plus_column;
double nextProgress, prevProgress;
double nextXProgress, nextYProgress;
double xDistanceToProgress, yDistanceToProgress;
int alongX;
/* See just beneath */
xDistanceToProgress = fabs(1.0 / dx);
yDistanceToProgress = fabs(1.0 / dy);
/* Get the column (x1, y1) is in. This is nothing
* more complicated then the floor of `x1`, however
* if `x1 > x2` and `x1` is on a cell edge, we say
* that it is in the left cell rather than the
* right cell; hence `ceil(x1) - 1`.
* Then we figure out how many steps will take
* horizontally when drawing the line. If (x1, y1)
* and (x2, y2) are in the same column, this is 0.
* Hence this the difference between the floor of
* min(x1, x2) and ceiling of max(x1, x2), less 1;
* less 1 because if (x1, y1) and (x2, y2) are in
* the same column the difference is 1: it is how
* many cells wide are covered, which is the 1
* greater than the number of edges between the
* cells.
* Third, we calculate (`nextXProgress`) where the
* line next (from (x1, y1)) intersections with a
* vertical line in the cell grid, and convert
* it the horizontal progression towards (x2, y2)
* by dividing by the unsigned projection of the
* line onto the horizontal raster axis.
*/
if (xdir > 0) {
double x1floor = floor(x1), x2ceil = ceil(x2);
startCellX = (size_t)x1floor;
nsteps += (size_t)x2ceil - (size_t)x1floor - 1;
nextXProgress = (x1floor + 1.0 - x1) * xDistanceToProgress;
} else {
double x2floor = floor(x2), x1ceil = ceil(x1);
startCellX = (size_t)x1ceil - 1;
nsteps += (size_t)x1ceil - (size_t)x2floor - 1;
nextXProgress = (x1 + 1.0 - x1ceil) * xDistanceToProgress;
}
/* Same as above, but on the vertical axis */
if (ydir > 0) {
double y1floor = floor(y1), y2ceil = ceil(y2);
startCellY = (size_t)y1floor;
nsteps += (size_t)y2ceil - (size_t)y1floor - 1;
nextYProgress = (y1floor + 1.0 - y1) * yDistanceToProgress;
} else {
double y2floor = floor(y2), y1ceil = ceil(y1);
startCellY = (size_t)y1ceil - 1;
nsteps += (size_t)y1ceil - (size_t)y2floor - 1;
nextYProgress = (y1 + 1.0 - y1ceil) * yDistanceToProgress;
}
/* These values are in [0, 1], where 0 corresponds to (x1, y1)
* and 1 corresponds to (x2, y2). `prevProgress` is the amount of
* work done at the beginning of for-loop body, and `nextProgress`
* is the amount of work done at the end of the for-loop body. */
prevProgress = 0.0;
nextProgress = fmin(nextXProgress, nextYProgress);
/* These are just tricks to reduce the number of operations made */
halfdx = 0.5 * dx;
one_minus_x1_plus_column = 1.0 - x1 + (double)startCellX;
cell = &raster->cells[startCellY * raster->width + startCellX];
rowsize = ydir * (ssize_t)raster->width;
/* Visit all cells the line intersect (and one extra for every time
* the line intersects a cell corner); `nsteps` is the number of
* horizontal steps plus the number of vertical steps (Manhattan walk). */
for (step = 0; step < nsteps; step++) {
/* `nextProgress` and `prevProgress` are [0, 1] values
* measuring the progression from (x1, y1) to (x2, y2),
* hence `nextProgress - prevProgress` the the progress
* this interation makes, and it multiplied by `dy` is
* how much we travel vertically. This is how much
* additional ink shall be applied to all cells on this
* cells right (in the same row). */
cell->opposite_coverage += ydiff = (nextProgress - prevProgress) * dy;
/* This is the mount of ink we need in in this cell
* (minus all the sum of `.opposite_coverage` of
* all cells on the left in the same row). This is
* the area of a right trapezoid made by cells right
* edge, the line `y = prevProgress * dy`, the line
* `y = nextProgress * dy` and the draw line. */
cell->cell_coverage += ydiff * (one_minus_x1_plus_column - halfdx * (prevProgress + nextProgress));
/* Step either vertically or horizontally, whichever is shortest */
prevProgress = nextProgress;
alongX = nextXProgress < nextYProgress;
nextXProgress += alongX ? xDistanceToProgress : 0;
nextYProgress += alongX ? 0 : yDistanceToProgress;
cell += alongX ? xdir : 0;
cell += alongX ? 0 : rowsize;
one_minus_x1_plus_column += alongX ? xdir : 0;
nextProgress = fmin(nextXProgress, nextYProgress);
}
/* In case we get beyond (x2, y2), that is, if `prevProgress > 1`. */
cell->opposite_coverage += ydiff = (1.0 - prevProgress) * dy;
cell->cell_coverage += ydiff * (one_minus_x1_plus_column - halfdx * (prevProgress + 1.0));
}
void
draw_bounded_line(RASTER *restrict raster, double x1, double y1, double x2, double y2)
{
double dx, dy = y2 - y1;
int xdir, ydir = SIGNUM(dy);
if (!ydir)
return;
dx = x2 - x1;
xdir = SIGNUM(dx);
if (!xdir)
draw_vertical_line(raster, x1, y1, y2, ydir);
else
draw_diagonal_line(raster, x1, y1, x2, y2, dx, dy, xdir, ydir);
}
#else
static RASTER *raster;
static RASTER *refraster;
static double
frand(double a, double b)
{
int i = rand();
double f = (double)i / (double)RAND_MAX;
double max = fmax(a, b);
double min = fmin(a, b);
return fma(f, max - min, min);
}
static void
check_against_reference(double x1, double y1, double x2, double y2)
{
size_t i;
alarm(5);
draw_linear_bezier_reference(refraster, x1, y1, x2, y2);
alarm(0);
for (i = 0; i < 100; i++) {
ASSERT(intolerant_eq(raster->cells[i].cell_coverage, refraster->cells[i].cell_coverage));
ASSERT(intolerant_eq(raster->cells[i].opposite_coverage, refraster->cells[i].opposite_coverage));
}
}
static void
check_vertical_line(double x, double y1, double y2)
{
alarm(5);
draw_vertical_line(raster, x, y1, y2, SIGNUM(y2 - y1));
alarm(0);
check_against_reference(x, y1, x, y2);
}
static void
check_diagonal_line(double x1, double y1, double x2, double y2)
{
double dx = x2 - x1;
double dy = y2 - y1;
alarm(5);
draw_diagonal_line(raster, x1, y1, x2, y2, dx, dy, SIGNUM(dx), SIGNUM(dy));
alarm(0);
check_against_reference(x1, y1, x2, y2);
}
static void
check_bounded_line(double x1, double y1, double x2, double y2)
{
alarm(5);
draw_bounded_line(raster, x1, y1, x2, y2);
alarm(0);
check_against_reference(x1, y1, x2, y2);
}
#ifndef ROWWISE_RESET_INKLEVEL
static void
check_vertical_line_opposite_only(double y1, double y2)
{
alarm(5);
draw_vertical_line_opposite_only(raster, 9, y1, y2, SIGNUM(y2 - y1));
alarm(0);
check_against_reference(20, y1, 20, y2);
}
#endif
static void
reset(void)
{
rtgrpblib_reset_raster(raster, 10, 10);
rtgrpblib_reset_raster(refraster, 10, 10);
}
int
main(void)
{
double x1, x2;
double y1, y2;
size_t i;
raster = rtgrpblib_create_raster(10, 10);
ASSERT(raster);
refraster = rtgrpblib_create_raster(10, 10);
ASSERT(refraster);
srand((unsigned)(uintptr_t)raster);
for (i = 0; i < 10000UL; i++) {
x1 = frand(0, 10);
y1 = frand(0, 10);
x2 = frand(0, 10);
y2 = frand(0, 10);
check_vertical_line(x1, y1, y2);
reset();
if (x1 != x2) {
check_diagonal_line(x1, y1, x2, y2);
reset();
}
check_bounded_line(x1, y1, x1, y2);
reset();
check_bounded_line(x1, y1, x2, y2);
reset();
#ifndef ROWWISE_RESET_INKLEVEL
check_vertical_line_opposite_only(y1, y2);
reset();
#endif
}
free(raster);
free(refraster);
return 0;
}
#endif