From: Elizabeth Figura zfigura@codeweavers.com
Ellipses must be exactly tangent with their bounding rects. Generating arcs 1 pixel off from tangent may result in visual corruption (if the application makes assumptions about what visual rect was drawn to), and may also result in noticeable visual errors (as is the case in bug 57296). --- dlls/win32u/dibdrv/graphics.c | 160 +++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 72 deletions(-)
diff --git a/dlls/win32u/dibdrv/graphics.c b/dlls/win32u/dibdrv/graphics.c index 9d956e51950..e3c1f7ec3d9 100644 --- a/dlls/win32u/dibdrv/graphics.c +++ b/dlls/win32u/dibdrv/graphics.c @@ -171,16 +171,21 @@ static void add_pen_lines_bounds( dibdrv_physdev *dev, int count, const POINT *p add_clipped_bounds( dev, &bounds, dev->clip ); }
-/* Draw the "top half" of the ellipse, viz. the half that doesn't have any - * points in the lower-right quadrant. - * - * We draw as many as eight arcs; in each we always increment one of X or Y +static void lp_to_dp_double( const DC *dc, double *x, double *y ) +{ + double in_x = *x, in_y = *y; + + *x = in_x * dc->xformWorld2Vport.eM11 + in_y * dc->xformWorld2Vport.eM21 + dc->xformWorld2Vport.eDx; + *y = in_x * dc->xformWorld2Vport.eM12 + in_y * dc->xformWorld2Vport.eM22 + dc->xformWorld2Vport.eDy; +} + +/* We draw as many as eight arcs; in each we always increment one of X or Y * (depending on whether the absolute value of the slope is less than * or greater than 1) and possibly increment the other. * * Loosely based on an algorithm by David Eberly. */ -static unsigned int generate_ellipse_top_half( const DC *dc, double width, double height, POINT *points ) +static unsigned int generate_ellipse_top_half( const DC *dc, const RECT *rect, POINT *points ) { /* A general ellipse centered at (0, 0) can be defined by * ax² + 2bxy + cy² - d = 0. @@ -189,15 +194,19 @@ static unsigned int generate_ellipse_top_half( const DC *dc, double width, doubl * yields an ellipse in the same form. * Precompute the transformed coefficients. */ const XFORM *xform = &dc->xformVport2World; - double wsq = (width / 2) * (width / 2); - double hsq = (height / 2) * (height / 2); + double center_x = (rect->left + rect->right) / 2.0; + double center_y = (rect->top + rect->bottom) / 2.0; + const double width = rect->right - rect->left, height = rect->bottom - rect->top; + const double wsq = (width / 2) * (width / 2); + const double hsq = (height / 2) * (height / 2); const double a = (hsq * xform->eM11 * xform->eM11) + (wsq * xform->eM12 * xform->eM12); const double b = (hsq * xform->eM11 * xform->eM21) + (wsq * xform->eM12 * xform->eM22); const double c = (hsq * xform->eM21 * xform->eM21) + (wsq * xform->eM22 * xform->eM22); const double d = (hsq * wsq); unsigned int pos = 0; - int start_x; - POINT pt; + double x, y, start_x; /* relative coordinates */ + + lp_to_dp_double( dc, ¢er_x, ¢er_y );
/* We start from the point that satisfies y = 0, i.e. ax² = d. * Note that a must be nonzero if the transformation matrix was singular. @@ -207,70 +216,79 @@ static unsigned int generate_ellipse_top_half( const DC *dc, double width, doubl * This point is not on the ellipse axis, but that doesn't matter for our * algorithm. We will take this set of points and rotate it 180 degrees * to get the opposite side. */ - pt.x = -sqrt(d / a); - pt.y = 0; - start_x = pt.x; + x = GDI_ROUND( center_x - sqrt(d / a) ) - center_x; + y = GDI_ROUND( center_y ) - center_y; + start_x = x;
- while (pt.y > 0 || (pt.y == 0 && pt.x != -start_x)) + while (y > 0 || (y == 0 && x != -start_x)) { - double dx = (b * pt.x + c * pt.y); - double dy = -(a * pt.x + b * pt.y); + double dx = (b * x + c * y); + double dy = -(a * x + b * y); int x_inc = (dx > 0 ? 1 : -1); int y_inc = (dy > 0 ? 1 : -1); double sigma1, sigma2;
- points[pos++] = pt; + points[pos].x = GDI_ROUND( center_x + x ); + points[pos].y = GDI_ROUND( center_y + y ); + ++pos;
if (fabs(dy) > fabs(dx)) { /* Increment y, maybe increment x. */ - pt.y += y_inc; + y += y_inc;
/* If this point has 45° slope, and it's directly adjacent to one * we just wrote, using the algorithm as-is will select this point * and then the one horizontally adjacent to it (our reflection * across the 45° line.) This creates an L-shaped corner, which we * don't want. Force incrementing X to skip that corner. */ - if (fabs(b * pt.x + c * pt.y) == fabs(a * pt.x + b * pt.y)) + if (fabs(b * x + c * y) == fabs(a * x + b * y)) { - pt.x += x_inc; + x += x_inc; continue; }
- sigma1 = (a * pt.x * pt.x) + (2 * b * pt.x * pt.y) + (c * pt.y * pt.y) - d; - pt.x += x_inc; - sigma2 = (a * pt.x * pt.x) + (2 * b * pt.x * pt.y) + (c * pt.y * pt.y) - d; + sigma1 = (a * x * x) + (2 * b * x * y) + (c * y * y) - d; + x += x_inc; + sigma2 = (a * x * x) + (2 * b * x * y) + (c * y * y) - d;
/* Pick whichever point is closer to the ellipse. */ if (fabs(sigma2) > fabs(sigma1)) - pt.x -= x_inc; + x -= x_inc; } else { /* Increment x, maybe increment y. */ - pt.x += x_inc; + x += x_inc;
- if (fabs(b * pt.x + c * pt.y) == fabs(a * pt.x + b * pt.y)) + if (fabs(b * x + c * y) == fabs(a * x + b * y)) { - pt.y += y_inc; + y += y_inc; continue; }
- sigma1 = (a * pt.x * pt.x) + (2 * b * pt.x * pt.y) + (c * pt.y * pt.y) - d; - pt.y += y_inc; - sigma2 = (a * pt.x * pt.x) + (2 * b * pt.x * pt.y) + (c * pt.y * pt.y) - d; + sigma1 = (a * x * x) + (2 * b * x * y) + (c * y * y) - d; + y += y_inc; + sigma2 = (a * x * x) + (2 * b * x * y) + (c * y * y) - d;
if (fabs(sigma2) > fabs(sigma1)) - pt.y -= y_inc; + y -= y_inc; } } return pos; }
-static int find_intersection( const POINT *points, double x, double y, int count ) +static int find_intersection( const DC *dc, const POINT *points, const RECT *rect, double x, double y, int count ) { + double center_x = (rect->left + rect->right) / 2.0; + double center_y = (rect->top + rect->bottom) / 2.0; int i;
+ lp_to_dp_double( dc, ¢er_x, ¢er_y ); + lp_to_dp_double( dc, &x, &y ); + x -= center_x; + y -= center_y; + /* First point is always x <= 0, y == 0, so if y >= 0 we are in the * top half. */
@@ -282,7 +300,7 @@ static int find_intersection( const POINT *points, double x, double y, int count /* top half */ for (i = 0; i < count; i++) { - if (points[i].x * y >= points[i].y * x) + if ((points[i].x - center_x) * y >= (points[i].y - center_y) * x) break; } return i; @@ -292,30 +310,21 @@ static int find_intersection( const POINT *points, double x, double y, int count /* bottom half */ for (i = 0; i < count; i++) { - if (points[i].x * -y >= points[i].y * -x) + if ((points[i].x - center_x) * -y >= (points[i].y - center_y) * -x) break; } return count + i; } }
-static void lp_to_dp_no_translate( DC *dc, double *x, double *y ) -{ - double in_x = *x, in_y = *y; - - *x = in_x * dc->xformWorld2Vport.eM11 + in_y * dc->xformWorld2Vport.eM21; - *y = in_x * dc->xformWorld2Vport.eM12 + in_y * dc->xformWorld2Vport.eM22; -} - -static int get_arc_points( DC *dc, int arc_dir, const RECT *rect, double start_x, double start_y, - double end_x, double end_y, POINT *points ) +static int get_arc_points( DC *dc, int arc_dir, const RECT *rect, int start_x, int start_y, + int end_x, int end_y, POINT *points ) { int i, pos, count, start_pos, end_pos; - int width = rect->right - rect->left; - int height = rect->bottom - rect->top; - POINT center = {rect->left + width / 2, rect->top + height / 2}; + POINT reflect; + RECT dp_rect;
- count = generate_ellipse_top_half( dc, width, height, points ); + count = generate_ellipse_top_half( dc, rect, points );
/* The ellipse is always generated counterclockwise from the origin. * This means our points will essentially be backwards if the world @@ -323,13 +332,8 @@ static int get_arc_points( DC *dc, int arc_dir, const RECT *rect, double start_x if (dc->xformWorld2Vport.eM11 * dc->xformWorld2Vport.eM22 < dc->xformWorld2Vport.eM12 * dc->xformWorld2Vport.eM21) arc_dir = (arc_dir == AD_CLOCKWISE ? AD_COUNTERCLOCKWISE : AD_CLOCKWISE);
- /* Transform the start and end, but do not translate them, so that they - * remain relative to the ellipse center. */ - lp_to_dp_no_translate( dc, &start_x, &start_y ); - lp_to_dp_no_translate( dc, &end_x, &end_y ); - - start_pos = find_intersection( points, start_x, start_y, count ); - end_pos = find_intersection( points, end_x, end_y, count ); + start_pos = find_intersection( dc, points, rect, start_x, start_y, count ); + end_pos = find_intersection( dc, points, rect, end_x, end_y, count ); if (arc_dir == AD_CLOCKWISE) { int tmp = start_pos; @@ -338,7 +342,24 @@ static int get_arc_points( DC *dc, int arc_dir, const RECT *rect, double start_x } if (end_pos <= start_pos) end_pos += 2 * count;
- lp_to_dp( dc, ¢er, 1 ); + /* The bottom half of the ellipse can be generated by reflecting each + * point across the ellipse center. In general, this is P' = C + (C - P). + * If C = (L + R) / 2 [for opposite rect corners L and R] then this is + * (L + R - P). + * + * However, we are dealing with already transformed points, and the + * LP to DP transformation f(x) = Mx + B is not linear due to the offset (B) + * component. However, the reflected point f(P') can be expressed as: + * + * f(P') = f(L + R - P) + * = M(L + R - P) + B + * = ML + B + MR + B - MP - B + * = f(L) + f(R) - f(P) + */ + dp_rect = *rect; + lp_to_dp( dc, (POINT *)&dp_rect, 2 ); + reflect.x = dp_rect.left + dp_rect.right; + reflect.y = dp_rect.bottom + dp_rect.top;
pos = count; if (arc_dir == AD_COUNTERCLOCKWISE) @@ -347,13 +368,13 @@ static int get_arc_points( DC *dc, int arc_dir, const RECT *rect, double start_x { if ((i / count) % 2 == 0) { - points[pos].x = center.x + points[i % count].x; - points[pos].y = center.y + points[i % count].y; + points[pos].x = points[i % count].x; + points[pos].y = points[i % count].y; } else { - points[pos].x = center.x - points[i % count].x; - points[pos].y = center.y - points[i % count].y; + points[pos].x = reflect.x - points[i % count].x; + points[pos].y = reflect.y - points[i % count].y; } } } @@ -363,13 +384,13 @@ static int get_arc_points( DC *dc, int arc_dir, const RECT *rect, double start_x { if ((i / count) % 2 == 0) { - points[pos].x = center.x + points[i % count].x; - points[pos].y = center.y + points[i % count].y; + points[pos].x = points[i % count].x; + points[pos].y = points[i % count].y; } else { - points[pos].x = center.x - points[i % count].x; - points[pos].y = center.y - points[i % count].y; + points[pos].x = reflect.x - points[i % count].x; + points[pos].y = reflect.y - points[i % count].y; } } } @@ -416,11 +437,6 @@ static BOOL draw_arc( PHYSDEV dev, INT left, INT top, INT right, INT bottom, pt[0].y = start_y; pt[1].x = end_x; pt[1].y = end_y; - /* make them relative to the ellipse center */ - pt[0].x -= rect.left + width / 2; - pt[0].y -= rect.top + height / 2; - pt[1].x -= rect.left + width / 2; - pt[1].y -= rect.top + height / 2;
max_points = get_arc_max_points( dc, &rect ); points = malloc( max_points * sizeof(*points) ); @@ -1570,12 +1586,12 @@ BOOL dibdrv_RoundRect( PHYSDEV dev, INT left, INT top, INT right, INT bottom, order_rect( &rect );
SetRect( &arc_rect, rect.left, rect.top, rect.left + ellipse_width, rect.top + ellipse_height ); - /* Points are relative to the arc center. - * We just need to specify any point on the vector. */ - count = get_arc_points( dc, AD_CLOCKWISE, &arc_rect, -1.0, 0.0, 0.0, -1.0, top_points ); + count = get_arc_points( dc, AD_CLOCKWISE, &arc_rect, arc_rect.left, (arc_rect.top + arc_rect.bottom) / 2, + (arc_rect.left + arc_rect.right) / 2, arc_rect.top, top_points );
SetRect( &arc_rect, rect.right - ellipse_width, rect.top, rect.right, rect.top + ellipse_height ); - count += get_arc_points( dc, AD_CLOCKWISE, &arc_rect, 0.0, -1.0, 1.0, 0.0, top_points + count ); + count += get_arc_points( dc, AD_CLOCKWISE, &arc_rect, (arc_rect.left + arc_rect.right + 1) / 2, arc_rect.top, + arc_rect.right, (arc_rect.top + arc_rect.bottom) / 2, top_points + count );
if (count * 2 > max_points) ERR( "point count %u * 2 exceeds max points %u\n", count, max_points );