Fix integer overflow in gdispGDrawThickLine().
Handling the whole width/height range with Newton algorithm was too difficult. Switched to bisection search with a separate prescaling step.
This commit is contained in:
parent
cd31df48b7
commit
ee69db45b3
1 changed files with 81 additions and 44 deletions
|
@ -2578,57 +2578,94 @@ void gdispGDrawBox(GDisplay *g, coord_t x, coord_t y, coord_t cx, coord_t cy, co
|
||||||
return (n + d/2) / d;
|
return (n + d/2) / d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Find a vector (nx, ny) that is perpendicular to (dx, dy) and has length
|
||||||
|
* equal to 'norm'. */
|
||||||
|
static void get_normal_vector(coord_t dx, coord_t dy, coord_t norm, coord_t *nx, coord_t *ny)
|
||||||
|
{
|
||||||
|
int32_t dx2, dy2, len_sq, norm_sq, norm_sq2;
|
||||||
|
int div, step, best, delta, abs_delta;
|
||||||
|
|
||||||
|
dx2 = dx; dy2 = dy;
|
||||||
|
norm_sq = (int32_t)norm * norm;
|
||||||
|
norm_sq2 = norm_sq * 512;
|
||||||
|
|
||||||
|
/* Scale dx2 and dy2 so that
|
||||||
|
* len_sq / 2 <= norm_sq * 512 <= len_sq * 2.
|
||||||
|
* The scaling by 512 is to yield higher accuracy in division later. */
|
||||||
|
len_sq = dx2 * dx2 + dy2 * dy2;
|
||||||
|
|
||||||
|
if (len_sq < norm_sq2)
|
||||||
|
{
|
||||||
|
while (len_sq < norm_sq2)
|
||||||
|
{
|
||||||
|
len_sq <<= 2; dx2 <<= 1; dy2 <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (len_sq > norm_sq2)
|
||||||
|
{
|
||||||
|
while (len_sq > norm_sq2)
|
||||||
|
{
|
||||||
|
len_sq >>= 2; dx2 >>= 1; dy2 >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now find the divider div so that
|
||||||
|
* len_sq / div^2 == norm_sq i.e. div = sqrt(len_sq / norm_sq)
|
||||||
|
*
|
||||||
|
* This is done using bisection search to avoid the need for floating
|
||||||
|
* point sqrt.
|
||||||
|
*
|
||||||
|
* Based on previous scaling, we know that
|
||||||
|
* len_sq / 2 <= norm_sq * 512 <=> div <= sqrt(1024) = 32
|
||||||
|
* len_sq * 2 >= norm_sq * 512 <=> div >= sqrt(256) = 16
|
||||||
|
*/
|
||||||
|
div = 24; step = 8;
|
||||||
|
best = 256;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
dx = dx2 / div;
|
||||||
|
dy = dy2 / div;
|
||||||
|
len_sq = dx*dx + dy*dy;
|
||||||
|
|
||||||
|
delta = len_sq - norm_sq;
|
||||||
|
|
||||||
|
abs_delta = (delta >= 0) ? delta : -delta;
|
||||||
|
|
||||||
|
if (abs_delta < best)
|
||||||
|
{
|
||||||
|
*nx = dy;
|
||||||
|
*ny = -dx;
|
||||||
|
best = abs_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta > 0)
|
||||||
|
div += step;
|
||||||
|
else if (delta < 0)
|
||||||
|
div -= step;
|
||||||
|
else if (delta == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (step == 0)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
step >>= 1; /* Do one round with step = 0 to calculate final result. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void gdispGDrawThickLine(GDisplay *g, coord_t x0, coord_t y0, coord_t x1, coord_t y1, color_t color, coord_t width, bool_t round) {
|
void gdispGDrawThickLine(GDisplay *g, coord_t x0, coord_t y0, coord_t x1, coord_t y1, color_t color, coord_t width, bool_t round) {
|
||||||
coord_t dx, dy, nx, ny;
|
coord_t dx, dy, nx = 0, ny = 0;
|
||||||
|
|
||||||
/* Compute the direction vector for the line */
|
/* Compute the direction vector for the line */
|
||||||
dx = x1 - x0;
|
dx = x1 - x0;
|
||||||
dy = y1 - y0;
|
dy = y1 - y0;
|
||||||
|
|
||||||
/* Compute vector for the normal of the line */
|
/* Compute a normal vector with length 'width'. */
|
||||||
nx = dy;
|
get_normal_vector(dx, dy, width, &nx, &ny);
|
||||||
ny = -dx;
|
|
||||||
|
|
||||||
/* Normalize the normal vector to length width.
|
/* Handle 1px wide lines gracefully */
|
||||||
* This uses Newton-Raphson to avoid the need for floating point sqrt.
|
if (nx == 0 && ny == 0)
|
||||||
* We try to solve f(div) = div^2 - len/width^2 = 0.
|
nx = 1;
|
||||||
* The closed-form solution is div = sqrt(len) / width.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
int32_t div, len, tmp;
|
|
||||||
uint8_t i;
|
|
||||||
|
|
||||||
len = (int32_t)nx*nx + (int32_t)ny*ny;
|
|
||||||
div = 100; /* Initial guess for divider; not critical */
|
|
||||||
|
|
||||||
/* If the line length is quite short, premultiply the vector
|
|
||||||
* in order to get better accuracy in width. */
|
|
||||||
if (len < 1024)
|
|
||||||
{
|
|
||||||
nx <<= 8;
|
|
||||||
ny <<= 8;
|
|
||||||
len <<= 16;
|
|
||||||
}
|
|
||||||
else if (len < 65536)
|
|
||||||
{
|
|
||||||
nx <<= 4;
|
|
||||||
ny <<= 4;
|
|
||||||
len <<= 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
int prev = div;
|
|
||||||
for (i = 0; i < 5; i++) {
|
|
||||||
tmp = width * width * div;
|
|
||||||
div -= (tmp * div - len) / (2 * tmp);
|
|
||||||
|
|
||||||
if (div == prev)
|
|
||||||
break; // No change, iteration complete
|
|
||||||
prev = div;
|
|
||||||
}
|
|
||||||
|
|
||||||
nx = rounding_div(nx, div);
|
|
||||||
ny = rounding_div(ny, div);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Offset the x0,y0 by half the width of the line. This way we
|
/* Offset the x0,y0 by half the width of the line. This way we
|
||||||
* can keep the width of the line accurate even if it is not evenly
|
* can keep the width of the line accurate even if it is not evenly
|
||||||
|
|
Loading…
Add table
Reference in a new issue