Replace the ellipse algorithm completely to fix infinite-loops w/huge ellipse sizes

Probably this bug was introduced in 85497740606d9ecdbf9702a24e6c76fb77803a93
This commit is contained in:
David Capello 2019-02-21 08:26:42 -03:00
parent 6a1a21726e
commit f3265656f2

View File

@ -91,176 +91,67 @@ void algo_line_continuous(int x0, int y0, int x1, int y1, void* data, AlgoPixel
}
}
/* Additional helper functions for the ellipse-drawing helper function
below corresponding to routines in Bresenham's algorithm. */
namespace {
int bresenham_ellipse_error(int rx, int ry, int x, int y) {
return x*x*ry*ry + y*y*rx*rx - rx*rx*ry*ry;
}
// Initialize positions x and y for Bresenham's algorithm
void bresenham_ellipse_init(int rx, int ry, int& x, int& y) {
// Start at the fatter pole
if (rx > ry) {
x = 0;
y = ry;
}
else {
x = rx;
y = 0;
}
}
// Move to next pixel to draw, according to Bresenham's algorithm
void bresenham_ellipse_step(int rx, int ry, int& x, int& y) {
// Move towards the skinnier pole. Having 2 cases isn't needed, but it ensures
// swapping rx and ry is the same as rotating 90 degrees.
if (rx > ry) {
int ex = bresenham_ellipse_error(rx, ry, x, y-1);
int ey = bresenham_ellipse_error(rx, ry, x+1, y);
int exy = bresenham_ellipse_error(rx, ry, x+1, y-1);
if (ex + exy < 0) ++x;
if (ey + exy > 0) --y;
}
else {
int ex = bresenham_ellipse_error(rx, ry, x, y+1);
int ey = bresenham_ellipse_error(rx, ry, x-1, y);
int exy = bresenham_ellipse_error(rx, ry, x-1, y+1);
if (ex + exy > 0) --x;
if (ey + exy < 0) ++y;
}
}
}
/* Helper function for the ellipse drawing routines. Calculates the
points of an ellipse which fits onto the rectangle specified by x1,
y1, x2 and y2, and calls the specified routine for each one. The
output proc has the same format as for do_line, and if the width or
height of the ellipse is only 1 or 2 pixels, do_line will be
called.
Copyright (C) 2002 by Elias Pschernig (eliaspschernig@aon.at)
for Allegro 4.x.
Adapted for ASEPRITE by David A. Capello. */
void algo_ellipse(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc)
{
int mx, my, rx, ry;
int x, y;
/* Cheap hack to get elllipses with integer diameter, by just offsetting
* some quadrants by one pixel. */
int mx2, my2;
mx = (x1 + x2) / 2;
mx2 = (x1 + x2 + 1) / 2;
my = (y1 + y2) / 2;
my2 = (y1 + y2 + 1) / 2;
rx = ABS(x1 - x2);
ry = ABS(y1 - y2);
if (rx == 1) { algo_line_perfect(x2, y1, x2, y2, data, proc); rx--; }
if (rx == 0) { algo_line_perfect(x1, y1, x1, y2, data, proc); return; }
if (ry == 1) { algo_line_perfect(x1, y2, x2, y2, data, proc); ry--; }
if (ry == 0) { algo_line_perfect(x1, y1, x2, y1, data, proc); return; }
rx /= 2;
ry /= 2;
/* Draw the 4 poles. */
proc(mx, my2 + ry, data);
proc(mx, my - ry, data);
proc(mx2 + rx, my, data);
proc(mx - rx, my, data);
/* For even diameter axis, double the poles. */
if (mx != mx2) {
proc(mx2, my2 + ry, data);
proc(mx2, my - ry, data);
}
if (my != my2) {
proc(mx2 + rx, my2, data);
proc(mx - rx, my2, data);
}
/* Initialize drawing position at a pole. */
bresenham_ellipse_init(rx, ry, x, y);
for (;;) {
/* Step to the next pixel to draw. */
bresenham_ellipse_step(rx, ry, x, y);
/* Edge conditions */
if (y == 0 && x < rx) ++y; // don't move to horizontal radius except at pole
if (x == 0 && y < ry) ++x; // don't move to vertical radius except at pole
if (y <= 0 || x <= 0) break; // stop before pole, since it's already drawn
/* Process pixel */
proc(mx2 + x, my - y, data);
proc(mx - x, my - y, data);
proc(mx2 + x, my2 + y, data);
proc(mx - x, my2 + y, data);
}
}
void algo_ellipsefill(int x1, int y1, int x2, int y2, void *data, AlgoHLine proc)
{
int mx, my, rx, ry;
int x, y;
/* Cheap hack to get elllipses with integer diameter, by just offsetting
* some quadrants by one pixel. */
int mx2, my2;
mx = (x1 + x2) / 2;
mx2 = (x1 + x2 + 1) / 2;
my = (y1 + y2) / 2;
my2 = (y1 + y2 + 1) / 2;
rx = ABS (x1 - x2);
ry = ABS (y1 - y2);
if (rx == 1) { int c; for (c=y1; c<=y2; c++) proc(x2, c, x2, data); rx--; }
if (rx == 0) { int c; for (c=y1; c<=y2; c++) proc(x1, c, x1, data); return; }
if (ry == 1) { proc(x1, y2, x2, data); ry--; }
if (ry == 0) { proc(x1, y1, x2, data); return; }
rx /= 2;
ry /= 2;
/* Draw the north and south poles (possibly 2 pixels) */
proc(mx, my2 + ry, mx2, data);
proc(mx, my - ry, mx2, data);
/* Draw the equator (possibly width 2) */
proc(mx - rx, my, mx2 + rx, data);
if (my != my2) proc(mx - rx, my2, mx2 + rx, data);
/* Initialize drawing position at a pole. */
bresenham_ellipse_init(rx, ry, x, y);
for (;;) {
/* Step to the next pixel to draw. */
bresenham_ellipse_step(rx, ry, x, y);
/* Edge conditions */
if (y == 0 && x < rx) ++y; // don't move to horizontal radius except at pole
if (x == 0 && y < ry) ++x; // don't move to vertical radius except at pole
if (y <= 0 || x <= 0) break; // stop before pole, since it's already drawn
/* Draw the north and south 'lines of latitude' */
proc(mx - x, my - y, mx2 + x, data);
proc(mx - x, my2 + y, mx2 + x, data);
}
}
// Rotated ellipse code based on Alois Zingl work released under the
// MIT license http://members.chello.at/easyfilter/bresenham.html
// Ellipse code based on Alois Zingl work released under the MIT
// license http://members.chello.at/easyfilter/bresenham.html
//
// Adapted for Aseprite by David Capello
void algo_ellipse(int x0, int y0, int x1, int y1, void* data, AlgoPixel proc)
{
long a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; // diameter
double dx = 4*(1.0-a)*b*b, dy = 4*(b1+1)*a*a; // error increment
double err = dx+dy+b1*a*a, e2; // error of 1.step
if (x0 > x1) { x0 = x1; x1 += a; } // if called with swapped points
if (y0 > y1) y0 = y1; // .. exchange them
y0 += (b+1)/2; y1 = y0-b1; // starting pixel
a = 8*a*a; b1 = 8*b*b;
do {
proc(x1, y0, data); // I. Quadrant
proc(x0, y0, data); // II. Quadrant
proc(x0, y1, data); // III. Quadrant
proc(x1, y1, data); // IV. Quadrant
e2 = 2*err;
if (e2 <= dy) { y0++; y1--; err += dy += a; } // y step
if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } // x step
} while (x0 <= x1);
while (y0-y1 <= b) { // too early stop of flat ellipses a=1
proc(x0-1, y0, data); // -> finish tip of ellipse
proc(x1+1, y0++, data);
proc(x0-1, y1, data);
proc(x1+1, y1--, data);
}
}
void algo_ellipsefill(int x0, int y0, int x1, int y1, void* data, AlgoHLine proc)
{
long a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; // diameter
double dx = 4*(1.0-a)*b*b, dy = 4*(b1+1)*a*a; // error increment
double err = dx+dy+b1*a*a, e2; // error of 1.step
if (x0 > x1) { x0 = x1; x1 += a; } // if called with swapped points
if (y0 > y1) y0 = y1; // .. exchange them
y0 += (b+1)/2; y1 = y0-b1; // starting pixel
a = 8*a*a; b1 = 8*b*b;
do {
proc(x0, y0, x1, data);
proc(x0, y1, x1, data);
e2 = 2*err;
if (e2 <= dy) { y0++; y1--; err += dy += a; } // y step
if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } // x step
} while (x0 <= x1);
while (y0-y1 <= b) { // too early stop of flat ellipses a=1
proc(x0-1, y0, x0-1, data); // -> finish tip of ellipse
proc(x1+1, y0++, x1+1, data);
proc(x0-1, y1, x0-1, data);
proc(x1+1, y1--, x1+1, data);
}
}
static void draw_quad_rational_bezier_seg(int x0, int y0,
int x1, int y1,
int x2, int y2,