From 00fafb27e3f544ea5b4902c45bc746c9d56e1f05 Mon Sep 17 00:00:00 2001 From: Henri Verbeet Date: Wed, 16 Aug 2017 21:58:24 +0200 Subject: [PATCH] d2d1: Implement bezier/bezier intersections. Signed-off-by: Henri Verbeet Signed-off-by: Alexandre Julliard --- dlls/d2d1/geometry.c | 141 ++++++++++++++++++++++++++++++++++++++--- dlls/d2d1/tests/d2d1.c | 8 +-- 2 files changed, 137 insertions(+), 12 deletions(-) diff --git a/dlls/d2d1/geometry.c b/dlls/d2d1/geometry.c index e0e9bc0a71f..537f57fea88 100644 --- a/dlls/d2d1/geometry.c +++ b/dlls/d2d1/geometry.c @@ -503,6 +503,62 @@ static float d2d_point_ccw(const D2D1_POINT_2F *a, const D2D1_POINT_2F *b, const return det_d[det_d_len - 1]; } +static BOOL d2d_rect_check_overlap(const D2D_RECT_F *p, const D2D_RECT_F *q) +{ + return p->left < q->right && p->top < q->bottom && p->right > q->left && p->bottom > q->top; +} + +static void d2d_rect_get_bezier_bounds(D2D_RECT_F *bounds, const D2D1_POINT_2F *p0, + const D2D1_POINT_2F *p1, const D2D1_POINT_2F *p2) +{ + D2D1_POINT_2F p; + float root; + + bounds->left = p0->x; + bounds->top = p0->y; + bounds->right = p0->x; + bounds->bottom = p0->y; + + d2d_rect_expand(bounds, p2); + + /* f(t) = (1 - t)²P₀ + 2(1 - t)tP₁ + t²P₂ + * f'(t) = 2(1 - t)(P₁ - P₀) + 2t(P₂ - P₁) + * = 2(P₂ - 2P₁ + P₀)t + 2(P₁ - P₀) + * + * f'(t) = 0 + * t = (P₀ - P₁) / (P₂ - 2P₁ + P₀) */ + root = (p0->x - p1->x) / (p2->x - 2.0f * p1->x + p0->x); + if (root > 0.0f && root < 1.0f) + { + d2d_point_calculate_bezier(&p, p0, p1, p2, root); + d2d_rect_expand(bounds, &p); + } + + root = (p0->y - p1->y) / (p2->y - 2.0f * p1->y + p0->y); + if (root > 0.0f && root < 1.0f) + { + d2d_point_calculate_bezier(&p, p0, p1, p2, root); + d2d_rect_expand(bounds, &p); + } +} + +static void d2d_rect_get_bezier_segment_bounds(D2D_RECT_F *bounds, const D2D1_POINT_2F *p0, + const D2D1_POINT_2F *p1, const D2D1_POINT_2F *p2, float start, float end) +{ + D2D1_POINT_2F q[3], r[2]; + + d2d_point_lerp(&r[0], p0, p1, start); + d2d_point_lerp(&r[1], p1, p2, start); + d2d_point_lerp(&q[0], &r[0], &r[1], start); + + end = (end - start) / (1.0f - start); + d2d_point_lerp(&q[1], &q[0], &r[1], end); + d2d_point_lerp(&r[0], &r[1], p2, end); + d2d_point_lerp(&q[2], &q[1], &r[0], end); + + d2d_rect_get_bezier_bounds(bounds, &q[0], &q[1], &q[2]); +} + static BOOL d2d_array_reserve(void **elements, size_t *capacity, size_t element_count, size_t element_size) { size_t new_capacity, max_capacity; @@ -1766,6 +1822,71 @@ static BOOL d2d_geometry_intersect_bezier_line(struct d2d_geometry *geometry, return TRUE; } +static BOOL d2d_geometry_intersect_bezier_bezier(struct d2d_geometry *geometry, + struct d2d_geometry_intersections *intersections, + const struct d2d_segment_idx *idx_p, float start_p, float end_p, + const struct d2d_segment_idx *idx_q, float start_q, float end_q) +{ + const D2D1_POINT_2F *p[3], *q[3]; + const struct d2d_figure *figure; + D2D_RECT_F p_bounds, q_bounds; + D2D1_POINT_2F intersection; + float centre_p, centre_q; + size_t next; + + figure = &geometry->u.path.figures[idx_p->figure_idx]; + p[0] = &figure->vertices[idx_p->vertex_idx]; + p[1] = &figure->bezier_controls[idx_p->control_idx]; + next = idx_p->vertex_idx + 1; + if (next == figure->vertex_count) + next = 0; + p[2] = &figure->vertices[next]; + + figure = &geometry->u.path.figures[idx_q->figure_idx]; + q[0] = &figure->vertices[idx_q->vertex_idx]; + q[1] = &figure->bezier_controls[idx_q->control_idx]; + next = idx_q->vertex_idx + 1; + if (next == figure->vertex_count) + next = 0; + q[2] = &figure->vertices[next]; + + d2d_rect_get_bezier_segment_bounds(&p_bounds, p[0], p[1], p[2], start_p, end_p); + d2d_rect_get_bezier_segment_bounds(&q_bounds, q[0], q[1], q[2], start_q, end_q); + + if (!d2d_rect_check_overlap(&p_bounds, &q_bounds)) + return TRUE; + + centre_p = (start_p + end_p) / 2.0f; + centre_q = (start_q + end_q) / 2.0f; + + if (end_p - start_p < 1e-3f) + { + d2d_point_calculate_bezier(&intersection, p[0], p[1], p[2], centre_p); + if (start_p > 0.0f && end_p < 1.0f && !d2d_geometry_intersections_add(intersections, + idx_p, centre_p, intersection)) + return FALSE; + if (start_q > 0.0f && end_q < 1.0f && !d2d_geometry_intersections_add(intersections, + idx_q, centre_q, intersection)) + return FALSE; + return TRUE; + } + + if (!d2d_geometry_intersect_bezier_bezier(geometry, intersections, + idx_p, start_p, centre_p, idx_q, start_q, centre_q)) + return FALSE; + if (!d2d_geometry_intersect_bezier_bezier(geometry, intersections, + idx_p, start_p, centre_p, idx_q, centre_q, end_q)) + return FALSE; + if (!d2d_geometry_intersect_bezier_bezier(geometry, intersections, + idx_p, centre_p, end_p, idx_q, start_q, centre_q)) + return FALSE; + if (!d2d_geometry_intersect_bezier_bezier(geometry, intersections, + idx_p, centre_p, end_p, idx_q, centre_q, end_q)) + return FALSE; + + return TRUE; +} + static BOOL d2d_geometry_apply_intersections(struct d2d_geometry *geometry, struct d2d_geometry_intersections *intersections) { @@ -1834,7 +1955,6 @@ static BOOL d2d_geometry_apply_intersections(struct d2d_geometry *geometry, /* Intersect the geometry's segments with themselves. This uses the * straightforward approach of testing everything against everything, but * there certainly exist more scalable algorithms for this. */ -/* FIXME: Beziers can't currently self-intersect. */ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry) { struct d2d_geometry_intersections intersections = {0}; @@ -1859,10 +1979,7 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry) figure_q = &geometry->u.path.figures[idx_q.figure_idx]; if (idx_q.figure_idx != idx_p.figure_idx) { - if (figure_p->bounds.left > figure_q->bounds.right - || figure_q->bounds.left > figure_p->bounds.right - || figure_p->bounds.top > figure_q->bounds.bottom - || figure_q->bounds.top > figure_p->bounds.bottom) + if (!d2d_rect_check_overlap(&figure_p->bounds, &figure_q->bounds)) continue; max_q = figure_q->vertex_count; } @@ -1877,9 +1994,17 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry) type_q = figure_q->vertex_types[idx_q.vertex_idx]; if (type_q == D2D_VERTEX_TYPE_BEZIER) { - if (type_p != D2D_VERTEX_TYPE_BEZIER - && !d2d_geometry_intersect_bezier_line(geometry, &intersections, &idx_q, &idx_p)) - goto done; + if (type_p == D2D_VERTEX_TYPE_BEZIER) + { + if (!d2d_geometry_intersect_bezier_bezier(geometry, &intersections, + &idx_p, 0.0f, 1.0f, &idx_q, 0.0f, 1.0f)) + goto done; + } + else + { + if (!d2d_geometry_intersect_bezier_line(geometry, &intersections, &idx_q, &idx_p)) + goto done; + } ++idx_q.control_idx; } else diff --git a/dlls/d2d1/tests/d2d1.c b/dlls/d2d1/tests/d2d1.c index 07aeffe0144..ade57437fa9 100644 --- a/dlls/d2d1/tests/d2d1.c +++ b/dlls/d2d1/tests/d2d1.c @@ -2364,7 +2364,7 @@ static void test_path_geometry(void) "EyYTVBQjFFYUIhRWFSAVVxUeFVgWHBZZFhoWWhcYF1sWGBZcFxYWXhcUF18WFBZhFhIWYxUSFWUV" "EBVnFBAUaRQOFGsTDhJvEgwSchAMEHYPCg96DQoMggEICgiLAQQIBJQBCJgBCJkBBpoBBpoBBpoB" "BpsBBJwBBJwBBJwBBJwBBJ0BAp4BAp4BAp4BAp4BAp4BAp4BAp4BAgAA"); - todo_wine ok(match, "Figure does not match.\n"); + ok(match, "Figure does not match.\n"); match = compare_figure(surface, 0, 226, 160, 160, 0xff652e89, 64, "7xoCngECngECngECngECngECngECngECnQEEnAEEnAEEnAEEnAEEmwEGmgEGmgEGmgEGmQEImAEI" "lAEECASLAQgKCIEBDQoMew8KD3YQDBByEgwSbhMOEmwUDhRpFBAUZxUQFWUVEhVjFhIWYRYUFl8X" @@ -2374,7 +2374,7 @@ static void test_path_geometry(void) "EyYTVBQjFFYUIhRWFSAVVxUeFVgWHBZZFhoWWhcYF1sWGBZcFxYWXhcUF18WFBZhFhIWYxUSFWUV" "EBVnFBAUaRQOFGsTDhJvEgwSchAMEHYPCg96DQoMggEICgiLAQQIBJQBCJgBCJkBBpoBBpoBBpoB" "BpsBBJwBBJwBBJwBBJwBBJ0BAp4BAp4BAp4BAp4BAp4BAp4BAp4BAgAA"); - todo_wine ok(match, "Figure does not match.\n"); + ok(match, "Figure does not match.\n"); match = compare_figure(surface, 160, 0, 320, 160, 0xff652e89, 64, "gVQBwAIBWgHlAQFYAecBAVYB6QEBVAHrAQEjDCMB7AECHhQeAu0BAxoYGgPvAQMWHhYD8QEDFCAU" "A/MBBBAkEAT0AQUOJw0F9QEGCioKBvcBBggsCAb4AQgFLgUI+QEJATIBCfsBCAIwAgj8AQcFLAUH" @@ -2386,7 +2386,7 @@ static void test_path_geometry(void) "AgMNIA0D/wEFCSYJBf4BBgYqBgf8AQgDLgMI+wFG+gEIAzADCPkBBwYuBgf3AQYKKgoG9gEFDCgM" "BfUBBBAlDwTzAQQSIhIE8QEDFh4WA/ABAhkaGQLvAQIcFhwC7QECIBAgAusBASgEKAHpAQFWAecB" "AVgB5QEBWgHAAgHhUgAA"); - todo_wine ok(match, "Figure does not match.\n"); + ok(match, "Figure does not match.\n"); match = compare_figure(surface, 160, 160, 320, 160, 0xff652e89, 64, "/VUB5QEBWAHnAQFWAekBAVQB6wECIQ8hAe0BAh0VHQLuAQIZGhkD7wEDFh4WA/EBBBMhEwPzAQQQ" "JQ8F9AEFDCgNBfUBBgoqCgb3AQcHLQcG+QEIBC8ECPkBPAEJ+wEIAy8CCP0BBgYrBQf9AQUJJgkF" @@ -2398,7 +2398,7 @@ static void test_path_geometry(void) "IA0E/gEFCSYJBf4BBgYrBQf8AQgDLwII+wE8AQn6AQgELwQI+AEHBy0HBvcBBgoqCgb2AQUNJw0F" "9AEEECQQBfIBBBMhEwPxAQMWHhYD8AECGRoZA+4BAh0VHQLsAQIhDiIB6wEBVAHpAQFWAecBAVgB" "wAIBwlYA"); - todo_wine ok(match, "Figure does not match.\n"); + ok(match, "Figure does not match.\n"); ID2D1TransformedGeometry_Release(transformed_geometry); ID2D1PathGeometry_Release(geometry);