[PATCH 0/2] MR10050: d2d1: Correct arc rotation angles for ellipse segments.
Signed-off-by: Nikolay Sivov <nsivov@codeweavers.com> -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10050
From: Nikolay Sivov <nsivov@codeweavers.com> Signed-off-by: Nikolay Sivov <nsivov@codeweavers.com> --- dlls/d2d1/geometry.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlls/d2d1/geometry.c b/dlls/d2d1/geometry.c index 07442260502..8f784ba7868 100644 --- a/dlls/d2d1/geometry.c +++ b/dlls/d2d1/geometry.c @@ -4794,7 +4794,7 @@ static void d2d_ellipse_geometry_stream(struct d2d_geometry *geometry, const D2D arcs[0].size.width = e->radiusX; arcs[0].size.height = e->radiusY; - arcs[0].rotationAngle = 90.0f; + arcs[0].rotationAngle = 0.0f; arcs[0].sweepDirection = D2D1_SWEEP_DIRECTION_CLOCKWISE; arcs[0].arcSize = D2D1_ARC_SIZE_SMALL; arcs[1] = arcs[2] = arcs[3] = arcs[0]; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10050
From: Nikolay Sivov <nsivov@codeweavers.com> Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=57435 Signed-off-by: Nikolay Sivov <nsivov@codeweavers.com> --- dlls/d2d1/geometry.c | 228 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 225 insertions(+), 3 deletions(-) diff --git a/dlls/d2d1/geometry.c b/dlls/d2d1/geometry.c index 8f784ba7868..cefd0692d62 100644 --- a/dlls/d2d1/geometry.c +++ b/dlls/d2d1/geometry.c @@ -1142,6 +1142,229 @@ static bool d2d_figure_add_lines(struct d2d_figure *figure, const D2D1_POINT_2F return true; } +static bool d2d_arc_check_radius(float halfchord2, float fuzz2, float *radius) +{ + bool accept = !(*radius * *radius <= halfchord2 * fuzz2); + if (accept) + { + if (*radius < 0.0f) + *radius = -*radius; + } + return accept; +} + +static int d2d_arc_get_piece_count(const D2D_POINT_2F *start, const D2D_POINT_2F *end, + bool large_arc, bool sweep_up, float *cos_angle, float *sin_angle) +{ + float angle; + int count; + + *cos_angle = d2d_point_dot(start, end); + *sin_angle = start->x * end->y - start->y * end->x; + + if (*cos_angle >= 0.0f) + { + if (!large_arc) return 1; + count = 4; + } + else + { + count = large_arc ? 3 : 2; + } + + angle = atan2f(*sin_angle, *cos_angle); + if (sweep_up) + { + if (angle < 0.0f) + angle += 2.0f * M_PI; + } + else + { + if (angle > 0.0f) + angle -= 2.0f * M_PI; + } + + angle /= count; + *cos_angle = cosf(angle); + *sin_angle = sinf(angle); + + return count; +} + +static float d2d_arc_get_bezier_distance(float rDot, bool sweep_up) +{ + float denom_squared, denominator; + const float fuzz = 1.e-6; + float numerator, rA, dist; + + rA = 0.5f * (1.0f + rDot); + if (rA < 0.0f) + return 0.0f; + + denom_squared = 1.0f - rA; + if (denom_squared <= 0.0f) + return 0.0f; + + denominator = sqrt(denom_squared); + numerator = (4.0f / 3.0f) * (1.0f - sqrt(rA)); + + dist = (numerator <= denominator * fuzz) ? 0.0f : numerator / denominator; + if (!sweep_up) + dist = -dist; + + return dist; +} + +/* Approximation logic is taken in its entirety from WpfGfx graphics core of + Windows Presentation Foundation framework, distributed under MIT license. */ +static int d2d_arc_to_bezier(const D2D_POINT_2F *start_point, const D2D1_ARC_SEGMENT *arc, + D2D_POINT_2F *points) +{ + float x, y, rHalfChord2, rCos, rSin, rCosArcAngle, rSinArcAngle, dist, rotation; + D2D_POINT_2F ptStart, ptEnd, vecToBez1, vecToBez2; + const float FUZZ = 1.e-6; + const float PI_OVER_180 = 0.0174532925199432957692; + bool large_arc, sweep_up, zero_center = false; + D2D_POINT_2F center, radius; + D2D1_MATRIX_3X2_F m; + int cPieces = -1; + + float fuzz2 = FUZZ * FUZZ; + int i, j; + + d2d_point_set(&radius, arc->size.width, arc->size.height); + rotation = arc->rotationAngle; + large_arc = arc->arcSize == D2D1_ARC_SIZE_LARGE; + sweep_up = arc->sweepDirection == D2D1_SWEEP_DIRECTION_CLOCKWISE; + + x = 0.5f * (arc->point.x - start_point->x); + y = 0.5f * (arc->point.y - start_point->y); + + rHalfChord2 = x * x + y * y; + if (rHalfChord2 < fuzz2) + return -1; + + if (!d2d_arc_check_radius(rHalfChord2, fuzz2, &radius.x) || + !d2d_arc_check_radius(rHalfChord2, fuzz2, &radius.y)) + { + return 0; + } + + if (fabs(rotation) < FUZZ) + { + rCos = 1.0f; + rSin = 0.0f; + } + else + { + float tmp; + + rotation = -rotation * PI_OVER_180; + + rCos = cosf(rotation); + rSin = sinf(rotation); + + tmp = x * rCos - y * rSin; + y = x * rSin + y * rCos; + x = tmp; + } + + x /= radius.x; + y /= radius.y; + + rHalfChord2 = x * x + y * y; + if (rHalfChord2 > 1.0f) + { + float tmp = sqrtf(rHalfChord2); + + radius.x *= tmp; + radius.y *= tmp; + center.x = center.y = 0.0f; + zero_center = true; + + x /= tmp; + y /= tmp; + } + else + { + float tmp = sqrtf((1.0f - rHalfChord2) / rHalfChord2); + if (large_arc != sweep_up) + { + center.x = -tmp * y; + center.y = tmp * x; + } + else + { + center.x = tmp * y; + center.y = -tmp * x; + } + } + + d2d_point_set(&ptStart, -x - center.x, -y - center.y); + d2d_point_set(&ptEnd, x - center.x, y - center.y); + + m._11 = rCos * radius.x; + m._12 = -rSin * radius.x; + m._21 = rSin * radius.y; + m._22 = rCos * radius.y; + m._31 = 0.5f * (arc->point.x + start_point->x); + m._32 = 0.5f * (arc->point.y + start_point->y); + if (!zero_center) + { + m._31 += (m._11 * center.x + m._12 * center.x); + m._32 += (m._21 * center.x + m._22 * center.y); + } + + cPieces = d2d_arc_get_piece_count(&ptStart, &ptEnd, large_arc, sweep_up, &rCosArcAngle, &rSinArcAngle); + + dist = d2d_arc_get_bezier_distance(rCosArcAngle, sweep_up); + d2d_point_set(&vecToBez1, -dist * ptStart.y, dist * ptStart.x); + + j = 0; + for (i = 1; i < cPieces; ++i) + { + D2D_POINT_2F ptPieceEnd; + + d2d_point_set(&ptPieceEnd, ptStart.x * rCosArcAngle - ptStart.y * rSinArcAngle, + ptStart.x * rSinArcAngle + ptStart.y * rCosArcAngle); + d2d_point_set(&vecToBez2, -dist * ptPieceEnd.y, dist * ptPieceEnd.x); + + d2d_point_transform(&points[j++], &m, ptStart.x + vecToBez1.x, ptStart.y + vecToBez1.y); + d2d_point_transform(&points[j++], &m, ptPieceEnd.x - vecToBez2.x, ptPieceEnd.y - vecToBez2.y); + d2d_point_transform(&points[j++], &m, ptPieceEnd.x, ptPieceEnd.y); + + ptStart = ptPieceEnd; + vecToBez1 = vecToBez2; + } + + d2d_point_set(&vecToBez2, -dist * ptEnd.y, dist * ptEnd.x); + d2d_point_transform(&points[j++], &m, ptStart.x + vecToBez1.x, ptStart.y + vecToBez1.y); + d2d_point_transform(&points[j++], &m, ptEnd.x - vecToBez2.x, ptEnd.y - vecToBez2.y); + d2d_point_set(&points[j], arc->point.x, arc->point.y); + + return cPieces; +} + +static bool d2d_figure_add_arc(struct d2d_figure *figure, const D2D1_ARC_SEGMENT *arc) +{ + size_t last = figure->vertex_count - 1; + D2D_POINT_2F points[12]; + int count = 0; + + count = d2d_arc_to_bezier(&figure->vertices[last], arc, points); + + if (count > 0) + { + return d2d_figure_add_beziers(figure, (D2D1_BEZIER_SEGMENT *)points, count); + } + else if (count == 0) + { + return d2d_figure_add_lines(figure, points, 1); + } + + return true; +} + static bool d2d_figure_produce_vertices(struct d2d_figure *figure) { union @@ -1178,10 +1401,9 @@ static bool d2d_figure_produce_vertices(struct d2d_figure *figure) size = FIELD_OFFSET(struct d2d_segment_lines, points[s.lines->count]); break; case D2D_SEGMENT_TYPE_ARCS: - /* FIXME: use a sequence of bezier curves */ for (j = 0; j < s.arcs->count; ++j) { - if (!d2d_figure_add_vertex(figure, s.arcs->segments[j].point)) + if (!d2d_figure_add_arc(figure, &s.arcs->segments[j])) return false; } @@ -3578,7 +3800,7 @@ static void STDMETHODCALLTYPE d2d_geometry_sink_AddArc(ID2D1GeometrySink *iface, { struct d2d_geometry *geometry = impl_from_ID2D1GeometrySink(iface); - FIXME("iface %p, arc %p stub!\n", iface, arc); + TRACE("iface %p, arc %p.\n", iface, arc); if (geometry->u.path.state != D2D_GEOMETRY_STATE_FIGURE) { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10050
participants (2)
-
Nikolay Sivov -
Nikolay Sivov (@nsivov)