Signed-off-by: Connor McAdams conmanx360@gmail.com --- dlls/d2d1/geometry.c | 64 +++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 21 deletions(-)
diff --git a/dlls/d2d1/geometry.c b/dlls/d2d1/geometry.c index c18b648aef..59de86e718 100644 --- a/dlls/d2d1/geometry.c +++ b/dlls/d2d1/geometry.c @@ -409,6 +409,32 @@ static void d2d_point_normalise(D2D1_POINT_2F *p) d2d_point_scale(p, 1.0f / l); }
+static void d2d_bezier_cubic_to_quad(const D2D1_POINT_2F *p0, const D2D1_POINT_2F *p1, + const D2D1_POINT_2F *p2, const D2D1_POINT_2F *p3, D2D1_POINT_2F *c0) +{ + c0->x = (p1->x + p2->x) * 0.75f; + c0->y = (p1->y + p2->y) * 0.75f; + c0->x -= (p0->x + p3->x) * 0.25f; + c0->y -= (p0->y + p3->y) * 0.25f; +} + +static void d2d_bezier_quad_to_cubic(const D2D1_POINT_2F *p0, const D2D1_POINT_2F *p1, + const D2D1_POINT_2F *p2, D2D1_POINT_2F *c0, D2D1_POINT_2F *c1) +{ + d2d_point_lerp(c0, p0, p1, 2.0f / 3.0f); + d2d_point_lerp(c1, p2, p1, 2.0f / 3.0f); +} + +static BOOL d2d_vertex_type_is_bezier(enum d2d_vertex_type t) +{ + return (t == D2D_VERTEX_TYPE_BEZIER || t == D2D_VERTEX_TYPE_SPLIT_BEZIER); +} + +static BOOL d2d_vertex_type_is_split_bezier(enum d2d_vertex_type t) +{ + return t == D2D_VERTEX_TYPE_SPLIT_BEZIER; +} + /* This implementation is based on the paper "Adaptive Precision * Floating-Point Arithmetic and Fast Robust Geometric Predicates" and * associated (Public Domain) code by Jonathan Richard Shewchuk. */ @@ -1875,7 +1901,7 @@ static BOOL d2d_geometry_apply_intersections(struct d2d_geometry *geometry,
figure = &geometry->u.path.figures[inter->figure_idx]; vertex_type = figure->vertex_types[inter->vertex_idx + vertex_offset]; - if (vertex_type != D2D_VERTEX_TYPE_BEZIER && vertex_type != D2D_VERTEX_TYPE_SPLIT_BEZIER) + if (!d2d_vertex_type_is_bezier(vertex_type)) { if (!d2d_figure_insert_vertex(&geometry->u.path.figures[inter->figure_idx], inter->vertex_idx + vertex_offset + 1, inter->p)) @@ -1961,9 +1987,9 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry) for (idx_q.vertex_idx = 0; idx_q.vertex_idx < max_q; ++idx_q.vertex_idx) { type_q = figure_q->vertex_types[idx_q.vertex_idx]; - if (type_q == D2D_VERTEX_TYPE_BEZIER) + if (d2d_vertex_type_is_bezier(type_q)) { - if (type_p == D2D_VERTEX_TYPE_BEZIER) + if (d2d_vertex_type_is_bezier(type_p)) { if (!d2d_geometry_intersect_bezier_bezier(geometry, &intersections, &idx_p, 0.0f, 1.0f, &idx_q, 0.0f, 1.0f)) @@ -1978,7 +2004,7 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry) } else { - if (type_p == D2D_VERTEX_TYPE_BEZIER) + if (d2d_vertex_type_is_bezier(type_p)) { if (!d2d_geometry_intersect_bezier_line(geometry, &intersections, &idx_p, &idx_q)) goto done; @@ -1991,7 +2017,7 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry) } } } - if (type_p == D2D_VERTEX_TYPE_BEZIER) + if (d2d_vertex_type_is_bezier(type_p)) ++idx_p.control_idx; } } @@ -2328,7 +2354,7 @@ static BOOL d2d_geometry_add_figure_outline(struct d2d_geometry *geometry, if (!i) { prev_type = figure->vertex_types[figure->vertex_count - 1]; - if (prev_type == D2D_VERTEX_TYPE_BEZIER) + if (d2d_vertex_type_is_bezier(prev_type)) prev = &figure->bezier_controls[figure->bezier_control_count - 1]; else prev = &figure->vertices[figure->vertex_count - 1]; @@ -2336,13 +2362,13 @@ static BOOL d2d_geometry_add_figure_outline(struct d2d_geometry *geometry, else { prev_type = figure->vertex_types[i - 1]; - if (prev_type == D2D_VERTEX_TYPE_BEZIER) + if (d2d_vertex_type_is_bezier(prev_type)) prev = &figure->bezier_controls[bezier_idx - 1]; else prev = &figure->vertices[i - 1]; }
- if (type == D2D_VERTEX_TYPE_BEZIER) + if (d2d_vertex_type_is_bezier(type)) next = &figure->bezier_controls[bezier_idx++]; else if (i == figure->vertex_count - 1) next = &figure->vertices[0]; @@ -2372,7 +2398,7 @@ static BOOL d2d_geometry_add_figure_outline(struct d2d_geometry *geometry, ERR("Failed to add line segment.\n"); return FALSE; } - else if (type == D2D_VERTEX_TYPE_BEZIER) + else if (d2d_vertex_type_is_bezier(type)) { const D2D1_POINT_2F *p2;
@@ -2577,10 +2603,8 @@ static void STDMETHODCALLTYPE d2d_geometry_sink_AddBeziers(ID2D1GeometrySink *if D2D1_RECT_F bezier_bounds;
/* FIXME: This tries to approximate a cubic bezier with a quadratic one. */ - p.x = (beziers[i].point1.x + beziers[i].point2.x) * 0.75f; - p.y = (beziers[i].point1.y + beziers[i].point2.y) * 0.75f; - p.x -= (figure->vertices[figure->vertex_count - 1].x + beziers[i].point3.x) * 0.25f; - p.y -= (figure->vertices[figure->vertex_count - 1].y + beziers[i].point3.y) * 0.25f; + d2d_bezier_cubic_to_quad(&figure->vertices[figure->vertex_count - 1], &beziers[i].point1, + &beziers[i].point2, &beziers[i].point3, &p); figure->vertex_types[figure->vertex_count - 1] = D2D_VERTEX_TYPE_BEZIER;
d2d_rect_get_bezier_bounds(&bezier_bounds, &figure->vertices[figure->vertex_count - 1], @@ -2674,8 +2698,7 @@ static BOOL d2d_geometry_get_bezier_segment_idx(struct d2d_geometry *geometry, s
for (; idx->vertex_idx < figure->vertex_count; ++idx->vertex_idx) { - if (figure->vertex_types[idx->vertex_idx] == D2D_VERTEX_TYPE_BEZIER - || figure->vertex_types[idx->vertex_idx] == D2D_VERTEX_TYPE_SPLIT_BEZIER) + if (d2d_vertex_type_is_bezier(figure->vertex_types[idx->vertex_idx])) return TRUE; } } @@ -3198,7 +3221,7 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_GetBounds(ID2D1PathGeometry * for (bezier_idx = 0, ++j; j < figure->vertex_count; ++j) { if (figure->vertex_types[j] == D2D_VERTEX_TYPE_NONE - || figure->vertex_types[j] == D2D_VERTEX_TYPE_SPLIT_BEZIER) + || d2d_vertex_type_is_split_bezier(figure->vertex_types[j])) continue;
switch (type) @@ -3230,7 +3253,7 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_GetBounds(ID2D1PathGeometry * type = figure->vertex_types[j]; }
- if (type == D2D_VERTEX_TYPE_BEZIER) + if (d2d_vertex_type_is_bezier(type)) { p1 = figure->original_bezier_controls[bezier_idx++]; d2d_point_transform(&p1, transform, p1.x, p1.y); @@ -3355,8 +3378,7 @@ static void d2d_geometry_simplify_quadratic(ID2D1SimplifiedGeometrySink *sink, { D2D1_BEZIER_SEGMENT b;
- d2d_point_lerp(&b.point1, p0, p1, 2.0f / 3.0f); - d2d_point_lerp(&b.point2, p2, p1, 2.0f / 3.0f); + d2d_bezier_quad_to_cubic(p0, p1, p2, &b.point1, &b.point2); b.point3 = *p2;
if (option == D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES) @@ -3401,7 +3423,7 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *i for (bezier_idx = 0, ++j; j < figure->vertex_count; ++j) { if (figure->vertex_types[j] == D2D_VERTEX_TYPE_NONE - || figure->vertex_types[j] == D2D_VERTEX_TYPE_SPLIT_BEZIER) + || d2d_vertex_type_is_split_bezier(figure->vertex_types[j])) continue;
switch (type) @@ -3436,7 +3458,7 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *i type = figure->vertex_types[j]; }
- if (type == D2D_VERTEX_TYPE_BEZIER) + if (d2d_vertex_type_is_bezier(type)) { p1 = figure->original_bezier_controls[bezier_idx++]; if (transform)
Store both cubic bezier control points instead of storing them as a quadratic approximation.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- -v2: When splitting cubics for overlap, lower them to a quadratic first to prevent infinite splitting issue. Also remove d2d_vertex_is_unsplit_bezier, and stop using pointers per bezier control point.
dlls/d2d1/geometry.c | 379 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 335 insertions(+), 44 deletions(-)
diff --git a/dlls/d2d1/geometry.c b/dlls/d2d1/geometry.c index 59de86e718..e5aa8d6327 100644 --- a/dlls/d2d1/geometry.c +++ b/dlls/d2d1/geometry.c @@ -48,8 +48,10 @@ enum d2d_vertex_type { D2D_VERTEX_TYPE_NONE, D2D_VERTEX_TYPE_LINE, - D2D_VERTEX_TYPE_BEZIER, - D2D_VERTEX_TYPE_SPLIT_BEZIER, + D2D_VERTEX_TYPE_QUADRATIC_BEZIER, + D2D_VERTEX_TYPE_CUBIC_BEZIER, + D2D_VERTEX_TYPE_SPLIT_QUAD_BEZIER, + D2D_VERTEX_TYPE_SPLIT_CUBIC_BEZIER, };
struct d2d_segment_idx @@ -425,14 +427,52 @@ static void d2d_bezier_quad_to_cubic(const D2D1_POINT_2F *p0, const D2D1_POINT_2 d2d_point_lerp(c1, p2, p1, 2.0f / 3.0f); }
+static void d2d_bezier_split_cubic(const D2D1_POINT_2F *p0, const D2D1_POINT_2F *p1, + const D2D1_POINT_2F *p2, const D2D1_POINT_2F *p3, float t, D2D1_BEZIER_SEGMENT *left, + D2D1_BEZIER_SEGMENT *right, D2D1_POINT_2F *center) +{ + D2D1_POINT_2F q[4], r[3], mid; + + d2d_point_lerp(&q[0], p0, p1, t); + d2d_point_lerp(&q[1], p1, p2, t); + d2d_point_lerp(&q[2], p2, p3, t); + + d2d_point_lerp(&r[0], &q[0], &q[1], t); + d2d_point_lerp(&r[1], &q[1], &q[2], t); + d2d_point_lerp(&mid, &r[0], &r[1], t); + + if (center) + *center = mid; + + if (left) + { + left->point1 = q[0]; + left->point2 = r[0]; + left->point3 = mid; + } + + if (right) + { + right->point1 = r[1]; + right->point2 = q[2]; + right->point3 = *p3; + } +} + static BOOL d2d_vertex_type_is_bezier(enum d2d_vertex_type t) { - return (t == D2D_VERTEX_TYPE_BEZIER || t == D2D_VERTEX_TYPE_SPLIT_BEZIER); + return (t == D2D_VERTEX_TYPE_QUADRATIC_BEZIER || t == D2D_VERTEX_TYPE_CUBIC_BEZIER + || t == D2D_VERTEX_TYPE_SPLIT_QUAD_BEZIER || t == D2D_VERTEX_TYPE_SPLIT_CUBIC_BEZIER); +} + +static BOOL d2d_vertex_type_is_cubic_bezier(enum d2d_vertex_type t) +{ + return (t == D2D_VERTEX_TYPE_CUBIC_BEZIER || t == D2D_VERTEX_TYPE_SPLIT_CUBIC_BEZIER); }
static BOOL d2d_vertex_type_is_split_bezier(enum d2d_vertex_type t) { - return t == D2D_VERTEX_TYPE_SPLIT_BEZIER; + return (t == D2D_VERTEX_TYPE_SPLIT_QUAD_BEZIER || t == D2D_VERTEX_TYPE_SPLIT_CUBIC_BEZIER); }
/* This implementation is based on the paper "Adaptive Precision @@ -639,37 +679,71 @@ static BOOL d2d_figure_add_vertex(struct d2d_figure *figure, D2D1_POINT_2F verte return TRUE; }
-static BOOL d2d_figure_insert_bezier_control(struct d2d_figure *figure, size_t idx, const D2D1_POINT_2F *p) +static BOOL d2d_figure_insert_bezier_controls(struct d2d_figure *figure, size_t idx, size_t count, + const D2D1_POINT_2F *p) { + unsigned int i; + if (!d2d_array_reserve((void **)&figure->bezier_controls, &figure->bezier_controls_size, - figure->bezier_control_count + 1, sizeof(*figure->bezier_controls))) + figure->bezier_control_count + count, sizeof(*figure->bezier_controls))) { ERR("Failed to grow bezier controls array.\n"); return FALSE; }
- memmove(&figure->bezier_controls[idx + 1], &figure->bezier_controls[idx], + memmove(&figure->bezier_controls[idx + count], &figure->bezier_controls[idx], (figure->bezier_control_count - idx) * sizeof(*figure->bezier_controls)); - figure->bezier_controls[idx] = *p; - ++figure->bezier_control_count; + + for (i = 0; i < count; ++i) + figure->bezier_controls[idx + i] = p[i]; + + figure->bezier_control_count += count;
return TRUE; }
-static BOOL d2d_figure_add_bezier_control(struct d2d_figure *figure, const D2D1_POINT_2F *p) +static BOOL d2d_figure_insert_bezier_control(struct d2d_figure *figure, size_t idx, const D2D1_POINT_2F *p) +{ + return d2d_figure_insert_bezier_controls(figure, idx, 1, p); +} + +static BOOL d2d_figure_add_bezier_controls(struct d2d_figure *figure, size_t count, const D2D1_POINT_2F *p) { + unsigned int i; + if (!d2d_array_reserve((void **)&figure->bezier_controls, &figure->bezier_controls_size, - figure->bezier_control_count + 1, sizeof(*figure->bezier_controls))) + figure->bezier_control_count + count, sizeof(*figure->bezier_controls))) { ERR("Failed to grow bezier controls array.\n"); return FALSE; }
- figure->bezier_controls[figure->bezier_control_count++] = *p; + for (i = 0; i < count; ++i) + figure->bezier_controls[figure->bezier_control_count + i] = p[i]; + + figure->bezier_control_count += count;
return TRUE; }
+static BOOL d2d_figure_add_bezier_control(struct d2d_figure *figure, const D2D1_POINT_2F *p) +{ + return d2d_figure_add_bezier_controls(figure, 1, p); +} + +static unsigned int d2d_figure_get_bezier_count(struct d2d_figure *figure) +{ + unsigned int bezier_count, i; + + for (i = bezier_count = 0; i < figure->vertex_count; ++i) + { + if (d2d_vertex_type_is_bezier(figure->vertex_types[i])) + ++bezier_count; + } + + return bezier_count; +} + static void d2d_cdt_edge_rot(struct d2d_cdt_edge_ref *dst, const struct d2d_cdt_edge_ref *src) { dst->idx = src->idx; @@ -1749,15 +1823,25 @@ static BOOL d2d_geometry_intersect_bezier_line(struct d2d_geometry *geometry, const D2D1_POINT_2F *p[3], *q[2]; const struct d2d_figure *figure; float y[3], root, theta, d, e; + enum d2d_vertex_type type; + D2D1_POINT_2F tmp; size_t next;
figure = &geometry->u.path.figures[idx_p->figure_idx]; + type = figure->vertex_types[idx_p->vertex_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]; + if (d2d_vertex_type_is_cubic_bezier(type)) + { + d2d_bezier_cubic_to_quad(p[0], p[1], &figure->bezier_controls[idx_p->control_idx + 1], + p[2], &tmp); + + p[1] = &tmp; + }
figure = &geometry->u.path.figures[idx_q->figure_idx]; q[0] = &figure->vertices[idx_q->vertex_idx]; @@ -1822,28 +1906,45 @@ static BOOL d2d_geometry_intersect_bezier_bezier(struct d2d_geometry *geometry, 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) { + D2D1_POINT_2F intersection, tmp_p, tmp_q; + enum d2d_vertex_type type_p, type_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]; + type_p = figure->vertex_types[idx_p->vertex_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]; + if (d2d_vertex_type_is_cubic_bezier(type_p)) + { + d2d_bezier_cubic_to_quad(p[0], p[1], &figure->bezier_controls[idx_p->control_idx + 1], + p[2], &tmp_p); + + p[1] = &tmp_p; + }
figure = &geometry->u.path.figures[idx_q->figure_idx]; + type_q = figure->vertex_types[idx_q->vertex_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]; + if (d2d_vertex_type_is_cubic_bezier(type_q)) + { + d2d_bezier_cubic_to_quad(q[0], q[1], &figure->bezier_controls[idx_q->control_idx + 1], + q[2], &tmp_q); + + q[1] = &tmp_q; + }
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); @@ -1886,10 +1987,11 @@ static BOOL d2d_geometry_apply_intersections(struct d2d_geometry *geometry, struct d2d_geometry_intersections *intersections) { size_t vertex_offset, control_offset, next, i; + enum d2d_vertex_type vertex_type, split_type; struct d2d_geometry_intersection *inter; - enum d2d_vertex_type vertex_type; - const D2D1_POINT_2F *p[3]; + const D2D1_POINT_2F *p[4]; struct d2d_figure *figure; + D2D1_BEZIER_SEGMENT b[2]; D2D1_POINT_2F q[2]; float t, t_prev;
@@ -1928,19 +2030,40 @@ static BOOL d2d_geometry_apply_intersections(struct d2d_geometry *geometry, next = inter->vertex_idx + vertex_offset + 1; if (next == figure->vertex_count) next = 0; - p[2] = &figure->vertices[next]; + p[3] = &figure->vertices[next];
- d2d_point_lerp(&q[0], p[0], p[1], t); - d2d_point_lerp(&q[1], p[1], p[2], t); + if (d2d_vertex_type_is_cubic_bezier(vertex_type)) + { + p[2] = &figure->bezier_controls[inter->control_idx + control_offset + 1]; + + d2d_bezier_split_cubic(p[0], p[1], p[2], p[3], t, &b[0], &b[1], NULL); + figure->bezier_controls[inter->control_idx + control_offset] = b[0].point1; + figure->bezier_controls[inter->control_idx + control_offset + 1] = b[0].point2; + + if (!(d2d_figure_insert_bezier_controls(figure, inter->control_idx + control_offset + 2, + 2, (D2D1_POINT_2F *)&b[1]))) + return FALSE; + control_offset += 2; + + split_type = D2D_VERTEX_TYPE_SPLIT_CUBIC_BEZIER; + } + else + { + d2d_point_lerp(&q[0], p[0], p[1], t); + d2d_point_lerp(&q[1], p[1], p[3], t); + + figure->bezier_controls[inter->control_idx + control_offset] = q[0]; + if (!(d2d_figure_insert_bezier_control(figure, inter->control_idx + control_offset + 1, &q[1]))) + return FALSE; + ++control_offset; + + split_type = D2D_VERTEX_TYPE_SPLIT_QUAD_BEZIER; + }
- figure->bezier_controls[inter->control_idx + control_offset] = q[0]; - if (!(d2d_figure_insert_bezier_control(figure, inter->control_idx + control_offset + 1, &q[1]))) - return FALSE; - ++control_offset;
if (!(d2d_figure_insert_vertex(figure, inter->vertex_idx + vertex_offset + 1, inter->p))) return FALSE; - figure->vertex_types[inter->vertex_idx + vertex_offset + 1] = D2D_VERTEX_TYPE_SPLIT_BEZIER; + figure->vertex_types[inter->vertex_idx + vertex_offset + 1] = split_type; ++vertex_offset; }
@@ -2001,6 +2124,8 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry) goto done; } ++idx_q.control_idx; + if (d2d_vertex_type_is_cubic_bezier(type_q)) + ++idx_q.control_idx; } else { @@ -2018,7 +2143,11 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry) } } if (d2d_vertex_type_is_bezier(type_p)) + { ++idx_p.control_idx; + if (d2d_vertex_type_is_cubic_bezier(type_p)) + ++idx_p.control_idx; + } } }
@@ -2375,6 +2504,9 @@ static BOOL d2d_geometry_add_figure_outline(struct d2d_geometry *geometry, else next = &figure->vertices[i + 1];
+ if (d2d_vertex_type_is_cubic_bezier(type)) + bezier_idx++; + if (figure_end == D2D1_FIGURE_END_CLOSED || (i && i < figure->vertex_count - 1)) { D2D1_POINT_2F q_next, q_prev; @@ -2401,12 +2533,20 @@ static BOOL d2d_geometry_add_figure_outline(struct d2d_geometry *geometry, else if (d2d_vertex_type_is_bezier(type)) { const D2D1_POINT_2F *p2; + D2D1_POINT_2F tmp;
if (i == figure->vertex_count - 1) p2 = &figure->vertices[0]; else p2 = &figure->vertices[i + 1];
+ if (d2d_vertex_type_is_cubic_bezier(type)) + { + d2d_bezier_cubic_to_quad(p0, &figure->bezier_controls[bezier_idx - 2], + &figure->bezier_controls[bezier_idx - 1], p2, &tmp); + next = &tmp; + } + if (!d2d_geometry_outline_add_bezier_segment(geometry, p0, next, p2)) { ERR("Failed to add bezier segment.\n"); @@ -2605,12 +2745,12 @@ static void STDMETHODCALLTYPE d2d_geometry_sink_AddBeziers(ID2D1GeometrySink *if /* FIXME: This tries to approximate a cubic bezier with a quadratic one. */ d2d_bezier_cubic_to_quad(&figure->vertices[figure->vertex_count - 1], &beziers[i].point1, &beziers[i].point2, &beziers[i].point3, &p); - figure->vertex_types[figure->vertex_count - 1] = D2D_VERTEX_TYPE_BEZIER; + figure->vertex_types[figure->vertex_count - 1] = D2D_VERTEX_TYPE_CUBIC_BEZIER;
d2d_rect_get_bezier_bounds(&bezier_bounds, &figure->vertices[figure->vertex_count - 1], &p, &beziers[i].point3);
- if (!d2d_figure_add_bezier_control(figure, &p)) + if (!d2d_figure_add_bezier_controls(figure, 2, (D2D1_POINT_2F *)&beziers[i])) { ERR("Failed to add bezier control.\n"); geometry->u.path.state = D2D_GEOMETRY_STATE_ERROR; @@ -2683,22 +2823,30 @@ static void d2d_path_geometry_free_figures(struct d2d_geometry *geometry)
static BOOL d2d_geometry_get_bezier_segment_idx(struct d2d_geometry *geometry, struct d2d_segment_idx *idx, BOOL next) { + struct d2d_figure *figure = &geometry->u.path.figures[idx->figure_idx]; + enum d2d_vertex_type type = figure->vertex_types[idx->vertex_idx]; + if (next) { + if (d2d_vertex_type_is_cubic_bezier(type)) + ++idx->control_idx; + ++idx->vertex_idx; ++idx->control_idx; }
for (; idx->figure_idx < geometry->u.path.figure_count; ++idx->figure_idx, idx->vertex_idx = idx->control_idx = 0) { - struct d2d_figure *figure = &geometry->u.path.figures[idx->figure_idx]; + figure = &geometry->u.path.figures[idx->figure_idx];
if (!figure->bezier_control_count || figure->flags & D2D_FIGURE_FLAG_HOLLOW) continue;
for (; idx->vertex_idx < figure->vertex_count; ++idx->vertex_idx) { - if (d2d_vertex_type_is_bezier(figure->vertex_types[idx->vertex_idx])) + type = figure->vertex_types[idx->vertex_idx]; + + if (d2d_vertex_type_is_bezier(type)) return TRUE; } } @@ -2721,27 +2869,44 @@ static BOOL d2d_geometry_get_next_bezier_segment_idx(struct d2d_geometry *geomet static BOOL d2d_geometry_check_bezier_overlap(struct d2d_geometry *geometry, const struct d2d_segment_idx *idx_p, const struct d2d_segment_idx *idx_q) { + D2D1_POINT_2F v_q[3], v_p, v_qp, tmp_p, tmp_q; const D2D1_POINT_2F *a[3], *b[3], *p[2], *q; const struct d2d_figure *figure; - D2D1_POINT_2F v_q[3], v_p, v_qp; + enum d2d_vertex_type type; unsigned int i, j, score; float det, t;
figure = &geometry->u.path.figures[idx_p->figure_idx]; + type = figure->vertex_types[idx_p->vertex_idx]; a[0] = &figure->vertices[idx_p->vertex_idx]; a[1] = &figure->bezier_controls[idx_p->control_idx]; if (idx_p->vertex_idx == figure->vertex_count - 1) a[2] = &figure->vertices[0]; else a[2] = &figure->vertices[idx_p->vertex_idx + 1]; + if (d2d_vertex_type_is_cubic_bezier(type)) + { + d2d_bezier_cubic_to_quad(a[0], a[1], &figure->bezier_controls[idx_p->control_idx + 1], + a[2], &tmp_p); + + a[1] = &tmp_p; + }
figure = &geometry->u.path.figures[idx_q->figure_idx]; + type = figure->vertex_types[idx_q->vertex_idx]; b[0] = &figure->vertices[idx_q->vertex_idx]; b[1] = &figure->bezier_controls[idx_q->control_idx]; if (idx_q->vertex_idx == figure->vertex_count - 1) b[2] = &figure->vertices[0]; else b[2] = &figure->vertices[idx_q->vertex_idx + 1]; + if (d2d_vertex_type_is_cubic_bezier(type)) + { + d2d_bezier_cubic_to_quad(b[0], b[1], &figure->bezier_controls[idx_q->control_idx + 1], + b[2], &tmp_q); + + b[1] = &tmp_q; + }
if (d2d_point_ccw(a[0], a[1], a[2]) == 0.0f || d2d_point_ccw(b[0], b[1], b[2]) == 0.0f) return FALSE; @@ -2809,40 +2974,81 @@ static BOOL d2d_geometry_check_bezier_overlap(struct d2d_geometry *geometry, static float d2d_geometry_bezier_ccw(struct d2d_geometry *geometry, const struct d2d_segment_idx *idx) { const struct d2d_figure *figure = &geometry->u.path.figures[idx->figure_idx]; + enum d2d_vertex_type type = figure->vertex_types[idx->vertex_idx]; size_t next = idx->vertex_idx + 1; + const D2D1_POINT_2F *c0; + D2D1_POINT_2F tmp;
if (next == figure->vertex_count) next = 0;
- return d2d_point_ccw(&figure->vertices[idx->vertex_idx], - &figure->bezier_controls[idx->control_idx], &figure->vertices[next]); + c0 = &figure->bezier_controls[idx->control_idx]; + if (d2d_vertex_type_is_cubic_bezier(type)) + { + d2d_bezier_cubic_to_quad(&figure->vertices[idx->vertex_idx], c0, + &figure->bezier_controls[idx->control_idx + 1], &figure->vertices[next], &tmp); + + c0 = &tmp; + } + + return d2d_point_ccw(&figure->vertices[idx->vertex_idx], c0, &figure->vertices[next]); }
static BOOL d2d_geometry_split_bezier(struct d2d_geometry *geometry, const struct d2d_segment_idx *idx) { + D2D1_POINT_2F q[3], tmp, c[2]; const D2D1_POINT_2F *p[3]; struct d2d_figure *figure; - D2D1_POINT_2F q[3]; + enum d2d_vertex_type type; size_t next;
figure = &geometry->u.path.figures[idx->figure_idx]; + type = figure->vertex_types[idx->vertex_idx]; p[0] = &figure->vertices[idx->vertex_idx]; p[1] = &figure->bezier_controls[idx->control_idx]; next = idx->vertex_idx + 1; if (next == figure->vertex_count) next = 0; p[2] = &figure->vertices[next]; + if (d2d_vertex_type_is_cubic_bezier(type)) + { + d2d_bezier_cubic_to_quad(p[0], p[1], &figure->bezier_controls[idx->control_idx + 1], + p[2], &tmp); + + p[1] = &tmp; + }
d2d_point_lerp(&q[0], p[0], p[1], 0.5f); d2d_point_lerp(&q[1], p[1], p[2], 0.5f); d2d_point_lerp(&q[2], &q[0], &q[1], 0.5f);
- figure->bezier_controls[idx->control_idx] = q[0]; - if (!(d2d_figure_insert_bezier_control(figure, idx->control_idx + 1, &q[1]))) - return FALSE; + /* Since our current overlap detection is only intended for quadratics, + * lower a cubic to a quadratic, then elevate it back to a cubic so we + * don't have to do any messy control point rearrangement. */ + if (d2d_vertex_type_is_cubic_bezier(type)) + { + d2d_bezier_quad_to_cubic(p[0], &q[0], &q[2], &c[0], &c[1]); + figure->bezier_controls[idx->control_idx] = c[0]; + figure->bezier_controls[idx->control_idx + 1] = c[1]; + + d2d_bezier_quad_to_cubic(&q[2], &q[1], p[2], &c[0], &c[1]); + if (!(d2d_figure_insert_bezier_controls(figure, idx->control_idx + 2, 2, c))) + return FALSE; + + type = D2D_VERTEX_TYPE_SPLIT_CUBIC_BEZIER; + } + else + { + figure->bezier_controls[idx->control_idx] = q[0]; + if (!(d2d_figure_insert_bezier_control(figure, idx->control_idx + 1, &q[1]))) + return FALSE; + + type = D2D_VERTEX_TYPE_SPLIT_QUAD_BEZIER; + } + if (!(d2d_figure_insert_vertex(figure, idx->vertex_idx + 1, q[2]))) return FALSE; - figure->vertex_types[idx->vertex_idx + 1] = D2D_VERTEX_TYPE_SPLIT_BEZIER; + figure->vertex_types[idx->vertex_idx + 1] = type;
return TRUE; } @@ -2854,6 +3060,7 @@ static HRESULT d2d_geometry_resolve_beziers(struct d2d_geometry *geometry) const D2D1_POINT_2F *p[3]; struct d2d_figure *figure; size_t bezier_idx, i; + D2D1_POINT_2F tmp;
if (!d2d_geometry_get_first_bezier_segment_idx(geometry, &idx_p)) return S_OK; @@ -2861,6 +3068,8 @@ static HRESULT d2d_geometry_resolve_beziers(struct d2d_geometry *geometry) /* Split overlapping bezier control triangles. */ while (d2d_geometry_get_next_bezier_segment_idx(geometry, &idx_p)) { + figure = &geometry->u.path.figures[idx_p.figure_idx]; + d2d_geometry_get_first_bezier_segment_idx(geometry, &idx_q); while (idx_q.figure_idx < idx_p.figure_idx || idx_q.vertex_idx < idx_p.vertex_idx) { @@ -2872,6 +3081,9 @@ static HRESULT d2d_geometry_resolve_beziers(struct d2d_geometry *geometry) return E_OUTOFMEMORY; if (idx_p.figure_idx == idx_q.figure_idx) { + if (d2d_vertex_type_is_cubic_bezier(figure->vertex_types[idx_p.vertex_idx])) + ++idx_p.control_idx; + ++idx_p.vertex_idx; ++idx_p.control_idx; } @@ -2888,9 +3100,11 @@ static HRESULT d2d_geometry_resolve_beziers(struct d2d_geometry *geometry)
for (i = 0; i < geometry->u.path.figure_count; ++i) { - if (geometry->u.path.figures[i].flags & D2D_FIGURE_FLAG_HOLLOW) + figure = &geometry->u.path.figures[i]; + if (figure->flags & D2D_FIGURE_FLAG_HOLLOW) continue; - geometry->fill.bezier_vertex_count += 3 * geometry->u.path.figures[i].bezier_control_count; + + geometry->fill.bezier_vertex_count += 3 * d2d_figure_get_bezier_count(figure); }
if (!(geometry->fill.bezier_vertices = heap_calloc(geometry->fill.bezier_vertex_count, @@ -2912,6 +3126,19 @@ static HRESULT d2d_geometry_resolve_beziers(struct d2d_geometry *geometry) p[1] = &figure->bezier_controls[idx_p.control_idx];
i = idx_p.vertex_idx + 1; + if (d2d_vertex_type_is_cubic_bezier(figure->vertex_types[idx_p.vertex_idx])) + { + if (i == figure->vertex_count) + p[2] = &figure->vertices[0]; + else + p[2] = &figure->vertices[i]; + + d2d_bezier_cubic_to_quad(p[0], p[1], &figure->bezier_controls[idx_p.control_idx + 1], + p[2], &tmp); + + p[1] = &tmp; + } + if (d2d_path_geometry_point_inside(geometry, p[1], FALSE)) { sign = 1.0f; @@ -3025,7 +3252,7 @@ static void STDMETHODCALLTYPE d2d_geometry_sink_AddQuadraticBeziers(ID2D1Geometr d2d_rect_get_bezier_bounds(&bezier_bounds, &figure->vertices[figure->vertex_count - 1], &beziers[i].point1, &beziers[i].point2);
- figure->vertex_types[figure->vertex_count - 1] = D2D_VERTEX_TYPE_BEZIER; + figure->vertex_types[figure->vertex_count - 1] = D2D_VERTEX_TYPE_QUADRATIC_BEZIER; if (!d2d_figure_add_bezier_control(figure, &beziers[i].point1)) { ERR("Failed to add bezier.\n"); @@ -3232,7 +3459,7 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_GetBounds(ID2D1PathGeometry * d2d_rect_expand(bounds, &p); break;
- case D2D_VERTEX_TYPE_BEZIER: + case D2D_VERTEX_TYPE_QUADRATIC_BEZIER: p1 = figure->original_bezier_controls[bezier_idx++]; d2d_point_transform(&p1, transform, p1.x, p1.y); p2 = figure->vertices[j]; @@ -3242,6 +3469,20 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_GetBounds(ID2D1PathGeometry * p = p2; break;
+ case D2D_VERTEX_TYPE_CUBIC_BEZIER: + d2d_bezier_cubic_to_quad(&figure->vertices[j - 1], + &figure->original_bezier_controls[bezier_idx], + &figure->original_bezier_controls[bezier_idx + 1], + &figure->vertices[j], &p1); + bezier_idx += 2; + d2d_point_transform(&p1, transform, p1.x, p1.y); + p2 = figure->vertices[j]; + d2d_point_transform(&p2, transform, p2.x, p2.y); + d2d_rect_get_bezier_bounds(&bezier_bounds, &p, &p1, &p2); + d2d_rect_union(bounds, &bezier_bounds); + p = p2; + break; + default: FIXME("Unhandled vertex type %#x.\n", type); p = figure->vertices[j]; @@ -3256,6 +3497,15 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_GetBounds(ID2D1PathGeometry * if (d2d_vertex_type_is_bezier(type)) { p1 = figure->original_bezier_controls[bezier_idx++]; + if (d2d_vertex_type_is_cubic_bezier(type)) + { + d2d_bezier_cubic_to_quad(&figure->vertices[j - 1], + &figure->original_bezier_controls[bezier_idx - 1], + &figure->original_bezier_controls[bezier_idx], + &figure->vertices[0], &p1); + + ++bezier_idx; + } d2d_point_transform(&p1, transform, p1.x, p1.y); p2 = figure->vertices[0]; d2d_point_transform(&p2, transform, p2.x, p2.y); @@ -3387,6 +3637,23 @@ static void d2d_geometry_simplify_quadratic(ID2D1SimplifiedGeometrySink *sink, ID2D1SimplifiedGeometrySink_AddBeziers(sink, &b, 1); }
+static void d2d_geometry_simplify_cubic(ID2D1SimplifiedGeometrySink *sink, + D2D1_GEOMETRY_SIMPLIFICATION_OPTION option, const D2D1_POINT_2F *p0, + const D2D1_POINT_2F *p1, const D2D1_POINT_2F *p2, const D2D1_POINT_2F *p3, + float tolerance) +{ + D2D1_BEZIER_SEGMENT b; + + b.point1 = *p1; + b.point2 = *p2; + b.point3 = *p3; + + if (option == D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES) + d2d_geometry_flatten_cubic(sink, p0, &b, tolerance); + else + ID2D1SimplifiedGeometrySink_AddBeziers(sink, &b, 1); +} + static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *iface, D2D1_GEOMETRY_SIMPLIFICATION_OPTION option, const D2D1_MATRIX_3X2_F *transform, float tolerance, ID2D1SimplifiedGeometrySink *sink) @@ -3394,8 +3661,8 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *i struct d2d_geometry *geometry = impl_from_ID2D1PathGeometry(iface); enum d2d_vertex_type type = D2D_VERTEX_TYPE_NONE; unsigned int i, j, bezier_idx; + D2D1_POINT_2F p, p1, p2, p3; D2D1_FIGURE_BEGIN begin; - D2D1_POINT_2F p, p1, p2; D2D1_FIGURE_END end;
TRACE("iface %p, option %#x, transform %p, tolerance %.8e, sink %p.\n", @@ -3435,7 +3702,7 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *i ID2D1SimplifiedGeometrySink_AddLines(sink, &p, 1); break;
- case D2D_VERTEX_TYPE_BEZIER: + case D2D_VERTEX_TYPE_QUADRATIC_BEZIER: p1 = figure->original_bezier_controls[bezier_idx++]; if (transform) d2d_point_transform(&p1, transform, p1.x, p1.y); @@ -3446,6 +3713,20 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *i p = p2; break;
+ case D2D_VERTEX_TYPE_CUBIC_BEZIER: + p1 = figure->original_bezier_controls[bezier_idx++]; + if (transform) + d2d_point_transform(&p1, transform, p1.x, p1.y); + p2 = figure->original_bezier_controls[bezier_idx++]; + if (transform) + d2d_point_transform(&p2, transform, p2.x, p2.y); + p3 = figure->vertices[j]; + if (transform) + d2d_point_transform(&p3, transform, p3.x, p3.y); + d2d_geometry_simplify_cubic(sink, option, &p, &p1, &p2, &p3, tolerance); + p = p3; + break; + default: FIXME("Unhandled vertex type %#x.\n", type); p = figure->vertices[j]; @@ -3463,10 +3744,20 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *i p1 = figure->original_bezier_controls[bezier_idx++]; if (transform) d2d_point_transform(&p1, transform, p1.x, p1.y); - p2 = figure->vertices[0]; + p3 = figure->vertices[0]; if (transform) - d2d_point_transform(&p2, transform, p2.x, p2.y); - d2d_geometry_simplify_quadratic(sink, option, &p, &p1, &p2, tolerance); + d2d_point_transform(&p3, transform, p3.x, p3.y); + if (d2d_vertex_type_is_cubic_bezier(type)) + { + p2 = figure->original_bezier_controls[bezier_idx++]; + if (transform) + d2d_point_transform(&p2, transform, p2.x, p2.y); + d2d_geometry_simplify_cubic(sink, option, &p, &p1, &p2, &p3, tolerance); + } + else + { + d2d_geometry_simplify_quadratic(sink, option, &p, &p1, &p3, tolerance); + } }
end = figure->flags & D2D_FIGURE_FLAG_CLOSED ? D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN;
Add new flattening methods for cubic beziers and quadratic beziers.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- dlls/d2d1/geometry.c | 249 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 215 insertions(+), 34 deletions(-)
diff --git a/dlls/d2d1/geometry.c b/dlls/d2d1/geometry.c index e5aa8d6327..001f142fe5 100644 --- a/dlls/d2d1/geometry.c +++ b/dlls/d2d1/geometry.c @@ -374,6 +374,13 @@ static void d2d_fp_scale_expansion_zeroelim(float *out, size_t *out_len, const f *out_len = out_idx; }
+static void d2d_point_add(D2D1_POINT_2F *out, + const D2D1_POINT_2F *a, const D2D1_POINT_2F *b) +{ + out->x = a->x + b->x; + out->y = a->y + b->y; +} + static void d2d_point_subtract(D2D1_POINT_2F *out, const D2D1_POINT_2F *a, const D2D1_POINT_2F *b) { @@ -3577,48 +3584,222 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_CompareWithGeometry(ID2D1Path return E_NOTIMPL; }
+/* + * Cubic flattening method derived from now expired US Patent 5,367,617, + * titled "System and Method of Hybrid Forward Differencing To Render Bezier + * Splines". This is the same method used in WPF, which through testing + * appears to be the same method used in D2D. + * + * This method uses the same principles as Adaptive Foward Differencing, + * except instead of adaptively creating points along the curve depending on + * the flattening tolerance threshold, we always split at fixed points, so + * that the results line up with recursive subdivision. + * + * Applying AFD to cubic/quadratic beziers basically comes down to this: + * p[0] is always p[0]. + * p[1] is always the difference between the end point and the start point. + * p[2] is the second derivative at p[0] for cubics, and just the individual + * second derivative for quadratics. + * p[3] is the second derivative at p[3] for cubics, and unused for + * quadratics, because the second derivative for a quadratic is non-linear. + */ +static BOOL d2d_cubic_flattener_half_step_check(D2D1_POINT_2F *p2, D2D1_POINT_2F *p3, float step_size, + float flat_tol) +{ + /* Step size should never go below half of this amount, which is the + * minimum possible tolerance of this method, 0.000001f. */ + if (step_size < 0.001f) + return FALSE; + + /* Check if either derivative is greater than the flattening tolerance, if + * one of them is, there is more halving to be done. */ + if (p2) + { + if ((max(fabsf(p2->x), fabsf(p2->y))) > flat_tol) + return TRUE; + } + + if (p3) + { + if ((max(fabsf(p3->x), fabsf(p3->y))) > flat_tol) + return TRUE; + } + + return FALSE; +} + +/* + * Try to take a double-step forward if doing so would be within tolerance and + * also wouldn't break up splitting at halves. + */ +static BOOL d2d_cubic_flattener_try_double_step(D2D1_POINT_2F *p, int *steps, float *step_size, float q_tol) +{ + BOOL doubled = (0 == (*steps & 1)); + D2D1_POINT_2F tmp; + + if (doubled) + { + tmp = p[2]; + d2d_point_scale(&tmp, 2.0f); + d2d_point_subtract(&tmp, &tmp, &p[3]); + + if (((max(fabsf(p[3].x), fabsf(p[3].y))) <= q_tol) && ((max(fabsf(tmp.x), fabsf(tmp.y))) <= q_tol)) + { + d2d_point_scale(&p[1], 2.0f); + + p[1].x += p[2].x; + p[1].y += p[2].y; + + d2d_point_scale(&p[3], 4.0f); + + p[2] = tmp; + d2d_point_scale(&p[2], 4.0f); + + *steps /= 2; // Halve the number of steps left + *step_size *= 2.0f; + } + else + doubled = FALSE; + } + + return doubled; +} + +/* + * Take a half step backwards on the cubic bezier, splitting the cubic bezier + * in half. + */ +static void d2d_cubic_flattener_half_step(D2D1_POINT_2F *p, int *steps, float *step_size) +{ + d2d_point_add(&p[2], &p[2], &p[3]); + d2d_point_scale(&p[2], 0.125f); + + d2d_point_subtract(&p[1], &p[1], &p[2]); + d2d_point_scale(&p[1], 0.50f); + + d2d_point_scale(&p[3], 0.25f); + + *steps *= 2; + *step_size *= 0.5f; +} + +static void d2d_quadratic_flattener_half_step(D2D1_POINT_2F *p, int *steps, float *step_size) +{ + d2d_point_scale(&p[1], 0.5f); + p[1].x += p[2].x * -0.125f; + p[1].y += p[2].y * -0.125f; + + d2d_point_scale(&p[2], 0.25f); + + *steps *= 2; + *step_size *= 0.5f; +} + +/* + * Go forward a step from a previously halved cubic bezier. + */ +static void d2d_cubic_flattener_forward_step(ID2D1SimplifiedGeometrySink *sink, D2D1_POINT_2F *p, + int *steps, float *step_size) +{ + D2D1_POINT_2F tmp = p[2]; + + d2d_point_add(&p[0], &p[0], &p[1]); + d2d_point_add(&p[1], &p[1], &tmp); + d2d_point_add(&p[2], &p[2], &tmp); + d2d_point_subtract(&p[2], &p[2], &p[3]); + + p[3] = tmp; + + ID2D1SimplifiedGeometrySink_AddLines(sink, &p[0], 1); + + *steps -= 1; +} + +static void d2d_quadratic_flattener_forward_step(ID2D1SimplifiedGeometrySink *sink, D2D1_POINT_2F *p, + int *steps, float *step_size) +{ + p[0].x += p[1].x; + p[0].y += p[1].y; + + p[1].x += p[2].x; + p[1].y += p[2].y; + + ID2D1SimplifiedGeometrySink_AddLines(sink, &p[0], 1); + + *steps -= 1; +} + static void d2d_geometry_flatten_cubic(ID2D1SimplifiedGeometrySink *sink, const D2D1_POINT_2F *p0, const D2D1_BEZIER_SEGMENT *b, float tolerance) { - D2D1_BEZIER_SEGMENT b0, b1; - D2D1_POINT_2F q; - float d; + float flat_tol, q_flat_tol; + float step_size = 1.0f; + int step_count = 1; + D2D1_POINT_2F q[4];
- /* It's certainly possible to calculate the maximum deviation of the - * approximation from the curve, but it's a little involved. Instead, note - * that if the control points were evenly spaced and collinear, p1 would - * be exactly between p0 and p2, and p2 would be exactly between p1 and - * p3. The deviation is a decent enough approximation, and much easier to - * calculate. - * - * p1' = (p0 + p2) / 2 - * p2' = (p1 + p3) / 2 - * d = ‖p1 - p1'‖₁ + ‖p2 - p2'‖₁ */ - d2d_point_lerp(&q, p0, &b->point2, 0.5f); - d2d_point_subtract(&q, &b->point1, &q); - d = fabsf(q.x) + fabsf(q.y); - d2d_point_lerp(&q, &b->point1, &b->point3, 0.5f); - d2d_point_subtract(&q, &b->point2, &q); - d += fabsf(q.x) + fabsf(q.y); - if (d < tolerance) - { - ID2D1SimplifiedGeometrySink_AddLines(sink, &b->point3, 1); - return; + + q[0] = *p0; + d2d_point_subtract(&q[1], &b->point3, p0); + q[2].x = (b->point1.x - b->point2.x * 2.0f + b->point3.x) * 6.0f; + q[2].y = (b->point1.y - b->point2.y * 2.0f + b->point3.y) * 6.0f; + q[3].x = (p0->x - b->point1.x * 2.0f + b->point2.x) * 6.0f; + q[3].y = (p0->y - b->point1.y * 2.0f + b->point2.y) * 6.0f; + + flat_tol = tolerance >= 0.0f ? tolerance : 0.0f; + flat_tol *= 6.0f; + q_flat_tol = flat_tol * 0.25f; + + while (d2d_cubic_flattener_half_step_check(&q[2], &q[3], step_size, flat_tol)) + d2d_cubic_flattener_half_step(q, &step_count, &step_size); + + while(step_count > 1) + { + d2d_cubic_flattener_forward_step(sink, q, &step_count, &step_size); + + if (d2d_cubic_flattener_half_step_check(&q[2], NULL, step_size, flat_tol)) + d2d_cubic_flattener_half_step(q, &step_count, &step_size); + else + { + while (d2d_cubic_flattener_try_double_step(q, &step_count, &step_size, q_flat_tol)) + continue; + } + + ID2D1SimplifiedGeometrySink_SetSegmentFlags(sink, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN); }
- d2d_point_lerp(&q, &b->point1, &b->point2, 0.5f); + /* Add final point. */ + ID2D1SimplifiedGeometrySink_AddLines(sink, &b->point3, 1); + ID2D1SimplifiedGeometrySink_SetSegmentFlags(sink, D2D1_PATH_SEGMENT_NONE); +} + +static void d2d_geometry_flatten_quadratic(ID2D1SimplifiedGeometrySink *sink, const D2D1_POINT_2F *p0, + const D2D1_POINT_2F *p1, const D2D1_POINT_2F *p2, float tolerance) +{ + float step_size = 1.0f; + D2D1_POINT_2F q[3]; + int step_count = 1; + float flat_tol; + + q[0] = *p0; + d2d_point_subtract(&q[1], p2, p0); + q[2].x = 2.0f * (p2->x - (2.0f * p1->x) + p0->x); + q[2].y = 2.0f * (p2->y - (2.0f * p1->y) + p0->y);
- b1.point3 = b->point3; - d2d_point_lerp(&b1.point2, &b1.point3, &b->point2, 0.5f); - d2d_point_lerp(&b1.point1, &b1.point2, &q, 0.5f); + flat_tol = tolerance >= 0.0f ? tolerance : 0.0f; + flat_tol *= 6.0f;
- d2d_point_lerp(&b0.point1, p0, &b->point1, 0.5f); - d2d_point_lerp(&b0.point2, &b0.point1, &q, 0.5f); - d2d_point_lerp(&b0.point3, &b0.point2, &b1.point1, 0.5f); + while (d2d_cubic_flattener_half_step_check(&q[2], NULL, step_size, flat_tol)) + d2d_quadratic_flattener_half_step(q, &step_count, &step_size);
- d2d_geometry_flatten_cubic(sink, p0, &b0, tolerance); - ID2D1SimplifiedGeometrySink_SetSegmentFlags(sink, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN); - d2d_geometry_flatten_cubic(sink, &b0.point3, &b1, tolerance); + while (step_count > 1) + { + d2d_quadratic_flattener_forward_step(sink, q, &step_count, &step_size); + + ID2D1SimplifiedGeometrySink_SetSegmentFlags(sink, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN); + } + + /* Add final point. */ + ID2D1SimplifiedGeometrySink_AddLines(sink, p2, 1); ID2D1SimplifiedGeometrySink_SetSegmentFlags(sink, D2D1_PATH_SEGMENT_NONE); }
@@ -3632,7 +3813,7 @@ static void d2d_geometry_simplify_quadratic(ID2D1SimplifiedGeometrySink *sink, b.point3 = *p2;
if (option == D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES) - d2d_geometry_flatten_cubic(sink, p0, &b, tolerance); + d2d_geometry_flatten_quadratic(sink, p0, p1, p2, tolerance); else ID2D1SimplifiedGeometrySink_AddBeziers(sink, &b, 1); }
Add functions for calculating the bounds of a cubic bezier.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- dlls/d2d1/geometry.c | 187 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 157 insertions(+), 30 deletions(-)
diff --git a/dlls/d2d1/geometry.c b/dlls/d2d1/geometry.c index 001f142fe5..951d28f85e 100644 --- a/dlls/d2d1/geometry.c +++ b/dlls/d2d1/geometry.c @@ -401,7 +401,7 @@ static void d2d_point_lerp(D2D1_POINT_2F *out, out->y = a->y * (1.0f - t) + b->y * t; }
-static void d2d_point_calculate_bezier(D2D1_POINT_2F *out, const D2D1_POINT_2F *p0, +static void d2d_point_calculate_quadratic_bezier(D2D1_POINT_2F *out, const D2D1_POINT_2F *p0, const D2D1_POINT_2F *p1, const D2D1_POINT_2F *p2, float t) { float t_c = 1.0f - t; @@ -410,6 +410,20 @@ static void d2d_point_calculate_bezier(D2D1_POINT_2F *out, const D2D1_POINT_2F * out->y = t_c * (t_c * p0->y + t * p1->y) + t * (t_c * p1->y + t * p2->y); }
+static void d2d_point_calculate_cubic_bezier(D2D1_POINT_2F *out, const D2D1_POINT_2F *p0, + const D2D1_POINT_2F *p1, const D2D1_POINT_2F *p2, const D2D1_POINT_2F *p3, float t) +{ + D2D1_POINT_2F q[5]; + + d2d_point_lerp(&q[0], p0, p1, t); + d2d_point_lerp(&q[1], p1, p2, t); + d2d_point_lerp(&q[2], p2, p3, t); + + d2d_point_lerp(&q[3], &q[0], &q[1], t); + d2d_point_lerp(&q[4], &q[1], &q[2], t); + d2d_point_lerp(out, &q[3], &q[4], t); +} + static void d2d_point_normalise(D2D1_POINT_2F *p) { float l; @@ -566,6 +580,37 @@ static float d2d_point_ccw(const D2D1_POINT_2F *a, const D2D1_POINT_2F *b, const return det_d[det_d_len - 1]; }
+static unsigned int d2d_solve_quadratic_roots(float a, float b, float c, float *roots) +{ + float sq_root, root, tmp; + unsigned int root_count = 0; + + tmp = b * b - 4.0f * a * c; + + if (tmp >= 0.0f) + { + sq_root = sqrtf(tmp); + + if (b >= 0.0f) + root = (-b - sq_root) / (2.0f * a); + else + root = (2.0f * c) / (-b + sq_root); + + if (root > 0.0f && root < 1.0f) + roots[root_count++] = root; + + if (b >= 0.0f) + root = (2.0f * c) / (-b - sq_root); + else + root = (-b + sq_root) / (2.0f * a); + + if (root > 0.0f && root < 1.0f) + roots[root_count++] = root; + } + + return root_count; +} + static void d2d_rect_union(D2D1_RECT_F *l, const D2D1_RECT_F *r) { l->left = min(l->left, r->left); @@ -579,7 +624,7 @@ 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, +static void d2d_rect_get_quadratic_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; @@ -601,18 +646,101 @@ static void d2d_rect_get_bezier_bounds(D2D_RECT_F *bounds, const D2D1_POINT_2F * 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_point_calculate_quadratic_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_point_calculate_quadratic_bezier(&p, p0, p1, p2, root); d2d_rect_expand(bounds, &p); } }
+static BOOL d2d_check_if_cubic_is_quad(const D2D1_POINT_2F *p0, const D2D1_POINT_2F *p1, + const D2D1_POINT_2F *p2, const D2D1_POINT_2F *p3) +{ + D2D1_POINT_2F tmp; + + tmp.x = -p0->x + 3.0f * p1->x - 3.0f * p2->x + p3->x; + tmp.y = -p0->y + 3.0f * p1->y - 3.0f * p2->y + p3->y; + + if ((tmp.x < 0.0005f && tmp.x > -0.0005f) && (tmp.y < 0.0005f && tmp.y > -0.0005f)) + return TRUE; + else + return FALSE; +} + +static void d2d_rect_get_cubic_bezier_bounds(D2D_RECT_F *bounds, const D2D1_POINT_2F *p0, + const D2D1_POINT_2F *p1, const D2D1_POINT_2F *p2, const D2D1_POINT_2F *p3) +{ + D2D1_POINT_2F p, v1, v2, v3; + float roots[2], a[2], b[2], c[2]; + unsigned int i, y, root_count; + + if (d2d_check_if_cubic_is_quad(p0, p1, p2, p3)) + { + d2d_bezier_cubic_to_quad(p0, p1, p2, p3, &p); + d2d_rect_get_quadratic_bezier_bounds(bounds, p0, &p, p3); + return; + } + + bounds->left = p0->x; + bounds->top = p0->y; + bounds->right = p0->x; + bounds->bottom = p0->y; + + d2d_rect_expand(bounds, p3); + + /* + * f(t) = (1 - t)³P₀ + 3(1 - t)²tP₁ + 3(1 - t)t²P₂ + t³P₃ + * f'(t) = 3(1 - t)²(P₁ - P₀) + 6(1 - t)t(P₂ - P₁) + 3t²(P₃ - P₂) + * + * Or, from https://pomax.github.io/bezierinfo/#extremities + * V₁ = 3(P₁ - P₀) + * V₂ = 3(P₂ - P₁) + * V₃ = 3(P₃ - P₂) + * f'(t) = V₁(1 - t)² + 2V₂(1 - t)t + V₃t² + * = (V₁ - 2V₂ + V₃)t² + 2(V₂ - V₁)t + V₁ + * + * And new quadratic coefficients a, b, and c are: + * a = V₁ - 2V₂ + V₃ + * b = 2(V₂ - V₁) + * c = V₁ + * + * f'(t) = 0 + * t = (-b ± √(b² - 4ac)) / 2a + */ + d2d_point_subtract(&v1, p1, p0); + d2d_point_scale(&v1, 3.0f); + + d2d_point_subtract(&v2, p2, p1); + d2d_point_scale(&v2, 3.0f); + + d2d_point_subtract(&v3, p3, p2); + d2d_point_scale(&v3, 3.0f); + + a[0] = v1.x - 2.0f * v2.x + v3.x; + a[1] = v1.y - 2.0f * v2.y + v3.y; + + b[0] = 2.0f * (v2.x - v1.x); + b[1] = 2.0f * (v2.y - v1.y); + + c[0] = v1.x; + c[1] = v1.y; + + for (i = 0; i < 2; ++i) + { + root_count = d2d_solve_quadratic_roots(a[i], b[i], c[i], roots); + for (y = 0; y < root_count; ++y) + { + d2d_point_calculate_cubic_bezier(&p, p0, p1, p2, p3, roots[y]); + 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) { @@ -627,7 +755,7 @@ static void d2d_rect_get_bezier_segment_bounds(D2D_RECT_F *bounds, const D2D1_PO 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]); + d2d_rect_get_quadratic_bezier_bounds(bounds, &q[0], &q[1], &q[2]); }
static BOOL d2d_figure_insert_vertex(struct d2d_figure *figure, size_t idx, D2D1_POINT_2F vertex) @@ -1804,7 +1932,7 @@ static BOOL d2d_geometry_add_bezier_line_intersections(struct d2d_geometry *geom D2D1_POINT_2F intersection; float t;
- d2d_point_calculate_bezier(&intersection, p[0], p[1], p[2], s); + d2d_point_calculate_quadratic_bezier(&intersection, p[0], p[1], p[2], s); if (fabsf(q[1]->x - q[0]->x) > fabsf(q[1]->y - q[0]->y)) t = (intersection.x - q[0]->x) / (q[1]->x - q[0]->x); else @@ -1964,7 +2092,7 @@ static BOOL d2d_geometry_intersect_bezier_bezier(struct d2d_geometry *geometry,
if (end_p - start_p < 1e-3f) { - d2d_point_calculate_bezier(&intersection, p[0], p[1], p[2], centre_p); + d2d_point_calculate_quadratic_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; @@ -2754,8 +2882,8 @@ static void STDMETHODCALLTYPE d2d_geometry_sink_AddBeziers(ID2D1GeometrySink *if &beziers[i].point2, &beziers[i].point3, &p); figure->vertex_types[figure->vertex_count - 1] = D2D_VERTEX_TYPE_CUBIC_BEZIER;
- d2d_rect_get_bezier_bounds(&bezier_bounds, &figure->vertices[figure->vertex_count - 1], - &p, &beziers[i].point3); + d2d_rect_get_cubic_bezier_bounds(&bezier_bounds, &figure->vertices[figure->vertex_count - 1], + &beziers[i].point1, &beziers[i].point2, &beziers[i].point3);
if (!d2d_figure_add_bezier_controls(figure, 2, (D2D1_POINT_2F *)&beziers[i])) { @@ -3256,7 +3384,7 @@ static void STDMETHODCALLTYPE d2d_geometry_sink_AddQuadraticBeziers(ID2D1Geometr { D2D1_RECT_F bezier_bounds;
- d2d_rect_get_bezier_bounds(&bezier_bounds, &figure->vertices[figure->vertex_count - 1], + d2d_rect_get_quadratic_bezier_bounds(&bezier_bounds, &figure->vertices[figure->vertex_count - 1], &beziers[i].point1, &beziers[i].point2);
figure->vertex_types[figure->vertex_count - 1] = D2D_VERTEX_TYPE_QUADRATIC_BEZIER; @@ -3426,7 +3554,7 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_GetBounds(ID2D1PathGeometry * const struct d2d_figure *figure = &geometry->u.path.figures[i]; enum d2d_vertex_type type = D2D_VERTEX_TYPE_NONE; D2D1_RECT_F bezier_bounds; - D2D1_POINT_2F p, p1, p2; + D2D1_POINT_2F p, p1, p2, p3; size_t j, bezier_idx;
if (figure->flags & D2D_FIGURE_FLAG_HOLLOW) @@ -3471,23 +3599,21 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_GetBounds(ID2D1PathGeometry * d2d_point_transform(&p1, transform, p1.x, p1.y); p2 = figure->vertices[j]; d2d_point_transform(&p2, transform, p2.x, p2.y); - d2d_rect_get_bezier_bounds(&bezier_bounds, &p, &p1, &p2); + d2d_rect_get_quadratic_bezier_bounds(&bezier_bounds, &p, &p1, &p2); d2d_rect_union(bounds, &bezier_bounds); p = p2; break;
case D2D_VERTEX_TYPE_CUBIC_BEZIER: - d2d_bezier_cubic_to_quad(&figure->vertices[j - 1], - &figure->original_bezier_controls[bezier_idx], - &figure->original_bezier_controls[bezier_idx + 1], - &figure->vertices[j], &p1); - bezier_idx += 2; + p1 = figure->original_bezier_controls[bezier_idx++]; d2d_point_transform(&p1, transform, p1.x, p1.y); - p2 = figure->vertices[j]; + p2 = figure->original_bezier_controls[bezier_idx++]; d2d_point_transform(&p2, transform, p2.x, p2.y); - d2d_rect_get_bezier_bounds(&bezier_bounds, &p, &p1, &p2); + p3 = figure->vertices[j]; + d2d_point_transform(&p3, transform, p3.x, p3.y); + d2d_rect_get_cubic_bezier_bounds(&bezier_bounds, &p, &p1, &p2, &p3); d2d_rect_union(bounds, &bezier_bounds); - p = p2; + p = p3; break;
default: @@ -3504,19 +3630,20 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_GetBounds(ID2D1PathGeometry * if (d2d_vertex_type_is_bezier(type)) { p1 = figure->original_bezier_controls[bezier_idx++]; + d2d_point_transform(&p1, transform, p1.x, p1.y); + p3 = figure->vertices[0]; + d2d_point_transform(&p3, transform, p3.x, p3.y); if (d2d_vertex_type_is_cubic_bezier(type)) { - d2d_bezier_cubic_to_quad(&figure->vertices[j - 1], - &figure->original_bezier_controls[bezier_idx - 1], - &figure->original_bezier_controls[bezier_idx], - &figure->vertices[0], &p1); - - ++bezier_idx; + p2 = figure->original_bezier_controls[bezier_idx++]; + d2d_point_transform(&p2, transform, p2.x, p2.y); + d2d_rect_get_cubic_bezier_bounds(&bezier_bounds, &p, &p1, &p2, &p3); } - d2d_point_transform(&p1, transform, p1.x, p1.y); - p2 = figure->vertices[0]; - d2d_point_transform(&p2, transform, p2.x, p2.y); - d2d_rect_get_bezier_bounds(&bezier_bounds, &p, &p1, &p2); + else + { + d2d_rect_get_quadratic_bezier_bounds(&bezier_bounds, &p, &p1, &p2); + } + d2d_rect_union(bounds, &bezier_bounds); } }
Signed-off-by: Connor McAdams conmanx360@gmail.com --- dlls/d2d1/geometry.c | 63 ++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 23 deletions(-)
diff --git a/dlls/d2d1/geometry.c b/dlls/d2d1/geometry.c index 951d28f85e..f7a3906ddf 100644 --- a/dlls/d2d1/geometry.c +++ b/dlls/d2d1/geometry.c @@ -741,8 +741,9 @@ static void d2d_rect_get_cubic_bezier_bounds(D2D_RECT_F *bounds, const D2D1_POIN } }
-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) +static void d2d_rect_get_quadratic_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];
@@ -758,6 +759,21 @@ static void d2d_rect_get_bezier_segment_bounds(D2D_RECT_F *bounds, const D2D1_PO d2d_rect_get_quadratic_bezier_bounds(bounds, &q[0], &q[1], &q[2]); }
+static void d2d_rect_get_cubic_bezier_segment_bounds(D2D_RECT_F *bounds, + const D2D1_POINT_2F *p0, const D2D1_POINT_2F *p1, const D2D1_POINT_2F *p2, + const D2D1_POINT_2F *p3, float start, float end) +{ + D2D1_BEZIER_SEGMENT left, right; + D2D1_POINT_2F mid; + + d2d_bezier_split_cubic(p0, p1, p2, p3, start, NULL, &right, &mid); + + end = (end - start) / (1.0f - start); + d2d_bezier_split_cubic(&mid, &right.point1, &right.point2, p3, end, &left, NULL, NULL); + + d2d_rect_get_cubic_bezier_bounds(bounds, &mid, &left.point1, &left.point2, &left.point3); +} + static BOOL d2d_figure_insert_vertex(struct d2d_figure *figure, size_t idx, D2D1_POINT_2F vertex) { if (!d2d_array_reserve((void **)&figure->vertices, &figure->vertices_size, @@ -2041,11 +2057,11 @@ static BOOL d2d_geometry_intersect_bezier_bezier(struct d2d_geometry *geometry, 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) { - D2D1_POINT_2F intersection, tmp_p, tmp_q; - enum d2d_vertex_type type_p, type_q; - const D2D1_POINT_2F *p[3], *q[3]; + const D2D1_POINT_2F *p[4], *q[4]; const struct d2d_figure *figure; D2D_RECT_F p_bounds, q_bounds; + D2D1_POINT_2F intersection; + enum d2d_vertex_type type_p, type_q; float centre_p, centre_q; size_t next;
@@ -2053,36 +2069,33 @@ static BOOL d2d_geometry_intersect_bezier_bezier(struct d2d_geometry *geometry, type_p = figure->vertex_types[idx_p->vertex_idx]; p[0] = &figure->vertices[idx_p->vertex_idx]; p[1] = &figure->bezier_controls[idx_p->control_idx]; + if (type_p == D2D_VERTEX_TYPE_CUBIC_BEZIER) + p[2] = &figure->bezier_controls[idx_p->control_idx + 1]; next = idx_p->vertex_idx + 1; if (next == figure->vertex_count) next = 0; - p[2] = &figure->vertices[next]; - if (d2d_vertex_type_is_cubic_bezier(type_p)) - { - d2d_bezier_cubic_to_quad(p[0], p[1], &figure->bezier_controls[idx_p->control_idx + 1], - p[2], &tmp_p); - - p[1] = &tmp_p; - } + p[3] = &figure->vertices[next];
figure = &geometry->u.path.figures[idx_q->figure_idx]; type_q = figure->vertex_types[idx_q->vertex_idx]; q[0] = &figure->vertices[idx_q->vertex_idx]; q[1] = &figure->bezier_controls[idx_q->control_idx]; + if (type_q == D2D_VERTEX_TYPE_CUBIC_BEZIER) + q[2] = &figure->bezier_controls[idx_q->control_idx + 1]; next = idx_q->vertex_idx + 1; if (next == figure->vertex_count) next = 0; - q[2] = &figure->vertices[next]; - if (d2d_vertex_type_is_cubic_bezier(type_q)) - { - d2d_bezier_cubic_to_quad(q[0], q[1], &figure->bezier_controls[idx_q->control_idx + 1], - q[2], &tmp_q); + q[3] = &figure->vertices[next];
- q[1] = &tmp_q; - } + if (type_p == D2D_VERTEX_TYPE_CUBIC_BEZIER) + d2d_rect_get_cubic_bezier_segment_bounds(&p_bounds, p[0], p[1], p[2], p[3], start_p, end_p); + else + d2d_rect_get_quadratic_bezier_segment_bounds(&p_bounds, p[0], p[1], p[3], start_p, end_p);
- 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 (type_q == D2D_VERTEX_TYPE_CUBIC_BEZIER) + d2d_rect_get_cubic_bezier_segment_bounds(&q_bounds, q[0], q[1], q[2], q[3], start_q, end_q); + else + d2d_rect_get_quadratic_bezier_segment_bounds(&q_bounds, q[0], q[1], q[3], start_q, end_q);
if (!d2d_rect_check_overlap(&p_bounds, &q_bounds)) return TRUE; @@ -2092,7 +2105,11 @@ static BOOL d2d_geometry_intersect_bezier_bezier(struct d2d_geometry *geometry,
if (end_p - start_p < 1e-3f) { - d2d_point_calculate_quadratic_bezier(&intersection, p[0], p[1], p[2], centre_p); + if (type_p == D2D_VERTEX_TYPE_CUBIC_BEZIER) + d2d_point_calculate_cubic_bezier(&intersection, p[0], p[1], p[2], p[3], centre_p); + else + d2d_point_calculate_quadratic_bezier(&intersection, p[0], p[1], p[3], centre_p); + if (start_p > 0.0f && end_p < 1.0f && !d2d_geometry_intersections_add(intersections, idx_p, centre_p, intersection)) return FALSE;
Signed-off-by: Connor McAdams conmanx360@gmail.com --- dlls/d2d1/geometry.c | 213 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 181 insertions(+), 32 deletions(-)
diff --git a/dlls/d2d1/geometry.c b/dlls/d2d1/geometry.c index f7a3906ddf..6ec54b5773 100644 --- a/dlls/d2d1/geometry.c +++ b/dlls/d2d1/geometry.c @@ -1945,10 +1945,18 @@ static BOOL d2d_geometry_add_bezier_line_intersections(struct d2d_geometry *geom struct d2d_geometry_intersections *intersections, const struct d2d_segment_idx *idx_p, const D2D1_POINT_2F **p, const struct d2d_segment_idx *idx_q, const D2D1_POINT_2F **q, float s) { + const struct d2d_figure *figure; + enum d2d_vertex_type type; D2D1_POINT_2F intersection; float t;
- d2d_point_calculate_quadratic_bezier(&intersection, p[0], p[1], p[2], s); + figure = &geometry->u.path.figures[idx_p->figure_idx]; + type = figure->vertex_types[idx_p->vertex_idx]; + if (type == D2D_VERTEX_TYPE_CUBIC_BEZIER) + d2d_point_calculate_cubic_bezier(&intersection, p[0], p[1], p[2], p[3], s); + else + d2d_point_calculate_quadratic_bezier(&intersection, p[0], p[1], p[3], s); + if (fabsf(q[1]->x - q[0]->x) > fabsf(q[1]->y - q[0]->y)) t = (intersection.x - q[0]->x) / (q[1]->x - q[0]->x); else @@ -1967,45 +1975,18 @@ static BOOL d2d_geometry_add_bezier_line_intersections(struct d2d_geometry *geom return TRUE; }
-static BOOL d2d_geometry_intersect_bezier_line(struct d2d_geometry *geometry, +static BOOL d2d_geometry_get_quadratic_bezier_roots(struct d2d_geometry *geometry, struct d2d_geometry_intersections *intersections, - const struct d2d_segment_idx *idx_p, const struct d2d_segment_idx *idx_q) + const struct d2d_segment_idx *idx_p, const D2D1_POINT_2F **p, + const struct d2d_segment_idx *idx_q, const D2D1_POINT_2F **q) { - const D2D1_POINT_2F *p[3], *q[2]; - const struct d2d_figure *figure; float y[3], root, theta, d, e; - enum d2d_vertex_type type; - D2D1_POINT_2F tmp; - size_t next; - - figure = &geometry->u.path.figures[idx_p->figure_idx]; - type = figure->vertex_types[idx_p->vertex_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]; - if (d2d_vertex_type_is_cubic_bezier(type)) - { - d2d_bezier_cubic_to_quad(p[0], p[1], &figure->bezier_controls[idx_p->control_idx + 1], - p[2], &tmp); - - p[1] = &tmp; - } - - figure = &geometry->u.path.figures[idx_q->figure_idx]; - q[0] = &figure->vertices[idx_q->vertex_idx]; - next = idx_q->vertex_idx + 1; - if (next == figure->vertex_count) - next = 0; - q[1] = &figure->vertices[next];
/* Align the line with x-axis. */ theta = -atan2f(q[1]->y - q[0]->y, q[1]->x - q[0]->x); y[0] = (p[0]->x - q[0]->x) * sinf(theta) + (p[0]->y - q[0]->y) * cosf(theta); y[1] = (p[1]->x - q[0]->x) * sinf(theta) + (p[1]->y - q[0]->y) * cosf(theta); - y[2] = (p[2]->x - q[0]->x) * sinf(theta) + (p[2]->y - q[0]->y) * cosf(theta); + y[2] = (p[3]->x - q[0]->x) * sinf(theta) + (p[3]->y - q[0]->y) * cosf(theta);
/* Intersect the transformed curve with the x-axis. * @@ -2052,6 +2033,174 @@ static BOOL d2d_geometry_intersect_bezier_line(struct d2d_geometry *geometry, return TRUE; }
+/* Cubic root finding method adapted from code at + * https://pomax.github.io/bezierinfo/#extremities */ +static float d2d_cubic_bezier_cuberoot(float a) +{ + if (a < 0.0f) + return -powf(-a, 1.0f / 3.0f); + else + return powf(a, 1.0f / 3.0f); +} + +static int d2d_cubic_bezier_get_roots(float *y, float *roots) +{ + float mp3, r, t, cosphi, phi, crtr, t1, u1, v1; + float p, p_3, q, q2, disc, sd, tmp, a, b, c, d; + unsigned int root_count; + + /* First, we need to convert the bezier coefficients to 'power basis' + * coefficients so that we can use a generic cubic root solving equation. */ + a = (3.0f * y[0] - 6.0f * y[1] + 3.0f * y[2]); + b = (-3.0f * y[0] + 3.0f * y[1]); + c = y[0]; + d = (-y[0] + 3.0f * y[1] - 3.0f * y[2] + y[3]); + + /* Check if the curve is actually a quadratic. */ + if ((d < 0.0005f && d > -0.0005f)) + { + /* Check if it's actually a line. */ + if ((a < 0.0005f && a > -0.0005f)) + { + /* Check if it's just a point. If it is, no roots. */ + if ((b < 0.0005f && b > -0.0005f)) + return 0; + + roots[0] = -c / b; + return 1; + } + + return d2d_solve_quadratic_roots(a, b, c, roots); + } + + a /= d; + b /= d; + c /= d; + + p = (3.0f * b - a * a) / 3.0f; + p_3 = p / 3.0f; + q = (2.0f * a * a * a - 9.0f * a * b + 27.0f * c) / 27.0f; + q2 = q / 2.0f; + disc = q2 * q2 + p_3 * p_3 * p_3; + + if ((disc < 0.00005f && disc > -0.00005f)) + disc = 0.0f; + + if (disc < 0.0f) + { + /* Three real roots. */ + mp3 = -p / 3.0f; + r = sqrtf(mp3 * mp3 * mp3); + t = -q / (2.0f * r); + + if (t < -1.0f) + cosphi = -1.0f; + else if (t > 1.0f) + cosphi = 1.0f; + else + cosphi = t; + + phi = acosf(cosphi); + crtr = d2d_cubic_bezier_cuberoot(r); + t1 = 2.0f * crtr; + + roots[0] = t1 * cosf(phi / 3.0f) - a / 3.0f; + roots[1] = t1 * cosf((phi + 2.0f * M_PI) / 3.0f) - a / 3.0f; + roots[2] = t1 * cosf((phi + 4.0f * M_PI) / 3.0f) - a / 3.0f; + + root_count = 3; + } + else if (disc == 0.0f) + { + /* Three real roots, but two are equal. */ + if (q2 < 0.0f) + tmp = d2d_cubic_bezier_cuberoot(-q2); + else + tmp = -d2d_cubic_bezier_cuberoot(q2); + + roots[0] = 2.0f * tmp - (a / 3.0f); + roots[1] = -tmp - (a / 3.0f); + + root_count = 2; + } + else + { + /* One real root, and two complex roots. */ + sd = sqrtf(disc); + u1 = d2d_cubic_bezier_cuberoot(sd - q2); + v1 = d2d_cubic_bezier_cuberoot(sd + q2); + roots[0] = u1 - v1 - a / 3.0f; + + root_count = 1; + } + + return root_count; +} + +static BOOL d2d_geometry_get_cubic_bezier_roots(struct d2d_geometry *geometry, + struct d2d_geometry_intersections *intersections, + const struct d2d_segment_idx *idx_p, const D2D1_POINT_2F **p, + const struct d2d_segment_idx *idx_q, const D2D1_POINT_2F **q) +{ + float y[4], roots[3], theta; + unsigned int num_roots, i; + + /* Align the line with x-axis. */ + theta = -atan2f(q[1]->y - q[0]->y, q[1]->x - q[0]->x); + /* Rotate the Y coordinates of the cubic bezier. */ + y[0] = (p[0]->x - q[0]->x) * sinf(theta) + (p[0]->y - q[0]->y) * cosf(theta); + y[1] = (p[1]->x - q[0]->x) * sinf(theta) + (p[1]->y - q[0]->y) * cosf(theta); + y[2] = (p[2]->x - q[0]->x) * sinf(theta) + (p[2]->y - q[0]->y) * cosf(theta); + y[3] = (p[3]->x - q[0]->x) * sinf(theta) + (p[3]->y - q[0]->y) * cosf(theta); + + num_roots = d2d_cubic_bezier_get_roots(y, roots); + + for (i = 0; i < num_roots; ++i) + { + if (roots[i] >= 0.0f && roots[i] <= 1.0f) + { + if (!d2d_geometry_add_bezier_line_intersections(geometry, + intersections, idx_p, p, idx_q, q, roots[i])) + return FALSE; + } + } + + return TRUE; +} + +static BOOL d2d_geometry_intersect_bezier_line(struct d2d_geometry *geometry, + struct d2d_geometry_intersections *intersections, + const struct d2d_segment_idx *idx_p, const struct d2d_segment_idx *idx_q) +{ + const D2D1_POINT_2F *p[4], *q[2]; + const struct d2d_figure *figure; + enum d2d_vertex_type type; + size_t next; + + figure = &geometry->u.path.figures[idx_p->figure_idx]; + type = figure->vertex_types[idx_p->vertex_idx]; + p[0] = &figure->vertices[idx_p->vertex_idx]; + p[1] = &figure->bezier_controls[idx_p->control_idx]; + if (type == D2D_VERTEX_TYPE_CUBIC_BEZIER) + p[2] = &figure->bezier_controls[idx_p->control_idx + 1]; + next = idx_p->vertex_idx + 1; + if (next == figure->vertex_count) + next = 0; + p[3] = &figure->vertices[next]; + + figure = &geometry->u.path.figures[idx_q->figure_idx]; + q[0] = &figure->vertices[idx_q->vertex_idx]; + next = idx_q->vertex_idx + 1; + if (next == figure->vertex_count) + next = 0; + q[1] = &figure->vertices[next]; + + if (type == D2D_VERTEX_TYPE_CUBIC_BEZIER) + return d2d_geometry_get_cubic_bezier_roots(geometry, intersections, idx_p, p, idx_q, q); + else + return d2d_geometry_get_quadratic_bezier_roots(geometry, intersections, idx_p, p, idx_q, q); +} + 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,
Signed-off-by: Connor McAdams conmanx360@gmail.com --- dlls/d2d1/tests/d2d1.c | 357 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 357 insertions(+)
diff --git a/dlls/d2d1/tests/d2d1.c b/dlls/d2d1/tests/d2d1.c index 7c0ee91099..a3fe5f10a8 100644 --- a/dlls/d2d1/tests/d2d1.c +++ b/dlls/d2d1/tests/d2d1.c @@ -2494,6 +2494,45 @@ static void fill_geometry_sink_bezier(ID2D1GeometrySink *sink, unsigned int holl ID2D1GeometrySink_EndFigure(sink, D2D1_FIGURE_END_CLOSED); }
+static void fill_geometry_sink_cubic_bezier(ID2D1GeometrySink *sink, unsigned int hollow_count) +{ + D2D1_FIGURE_BEGIN begin; + unsigned int idx = 0; + D2D1_POINT_2F point; + + set_point(&point, 25.0f, 220.0f); + begin = idx++ < hollow_count ? D2D1_FIGURE_BEGIN_HOLLOW : D2D1_FIGURE_BEGIN_FILLED; + ID2D1GeometrySink_BeginFigure(sink, point, begin); + cubic_to(sink, 37.5f, 170.0f, 12.5f, 120.0f, 25.0f, 70.0f); + cubic_to(sink, 37.5f, 120.0f, 50.0f, 20.0f, 62.5f, 70.0f); + cubic_to(sink, 50.0f, 120.0f, 75.0f, 170.0f, 62.5f, 220.0f); + cubic_to(sink, 50.0f, 170.0f, 37.5f, 270.0f, 25.0f, 220.0f); + ID2D1GeometrySink_EndFigure(sink, D2D1_FIGURE_END_CLOSED); + + set_point(&point, 20.0f, 145.0f); + begin = idx++ < hollow_count ? D2D1_FIGURE_BEGIN_HOLLOW : D2D1_FIGURE_BEGIN_FILLED; + ID2D1GeometrySink_BeginFigure(sink, point, begin); + cubic_to(sink, 55.0f, 0.0f, 35.0f, 0.0f, 70.0f, 145.0f); + cubic_to(sink, 35.0f, 290.0f, 55.0f, 290.0f, 20.0f, 145.0f); + ID2D1GeometrySink_EndFigure(sink, D2D1_FIGURE_END_CLOSED); + + set_point(&point, 25.0f, 672.0f); + begin = idx++ < hollow_count ? D2D1_FIGURE_BEGIN_HOLLOW : D2D1_FIGURE_BEGIN_FILLED; + ID2D1GeometrySink_BeginFigure(sink, point, begin); + cubic_to(sink, 37.5f, 622.0f, 12.5f, 572.0f, 25.0f, 522.0f); + cubic_to(sink, 37.5f, 572.0f, 50.0f, 472.0f, 62.5f, 522.0f); + cubic_to(sink, 50.0f, 572.0f, 75.0f, 622.0f, 62.5f, 672.0f); + cubic_to(sink, 50.0f, 622.0f, 37.5f, 722.0f, 25.0f, 672.0f); + ID2D1GeometrySink_EndFigure(sink, D2D1_FIGURE_END_CLOSED); + + set_point(&point, 20.0f, 597.0f); + begin = idx++ < hollow_count ? D2D1_FIGURE_BEGIN_HOLLOW : D2D1_FIGURE_BEGIN_FILLED; + ID2D1GeometrySink_BeginFigure(sink, point, begin); + cubic_to(sink, 55.0f, 452.0f, 35.0f, 452.0f, 70.0f, 597.0f); + cubic_to(sink, 35.0f, 742.0f, 55.0f, 742.0f, 20.0f, 597.0f); + ID2D1GeometrySink_EndFigure(sink, D2D1_FIGURE_END_CLOSED); +} + static void test_path_geometry(void) { ID2D1TransformedGeometry *transformed_geometry; @@ -2786,6 +2825,154 @@ static void test_path_geometry(void) /* Figure 28. */ {SEGMENT_LINE, {{{ 75.0f, 300.0f}}}}, {SEGMENT_LINE, {{{ 5.0f, 300.0f}}}}, + /* Figure 29. */ + {SEGMENT_BEZIER, {{{3.75000000e+01f, 1.70000000e+02f}, + {1.25000000e+01f, 1.20000000e+02f}, + {2.50000000e+01f, 7.00000000e+01f}}}}, + {SEGMENT_BEZIER, {{{3.75000000e+01f, 1.20000000e+02f}, + {5.00000000e+01f, 2.00000000e+01f}, + {6.25000000e+01f, 7.00000000e+01f}}}}, + {SEGMENT_BEZIER, {{{5.00000000e+01f, 1.20000000e+02f}, + {7.50000000e+01f, 1.70000000e+02f}, + {6.25000000e+01f, 2.20000000e+02f}}}}, + {SEGMENT_BEZIER, {{{5.00000000e+01f, 1.70000000e+02f}, + {3.75000000e+01f, 2.70000000e+02f}, + {2.50000000e+01f, 2.20000000e+02f}}}}, + /* Figure 30. */ + {SEGMENT_BEZIER, {{{5.50000000e+01f, 0.00000000e+00f}, + {3.50000000e+01f, 0.00000000e+00f}, + {7.00000000e+01f, 1.45000000e+02f}}}}, + {SEGMENT_BEZIER, {{{3.50000000e+01f, 2.90000000e+02f}, + {5.50000000e+01f, 2.90000000e+02f}, + {2.00000000e+01f, 1.45000000e+02f}}}}, + /* Figure 31. */ + {SEGMENT_BEZIER, {{{3.75000000e+01f, 6.22000000e+02f}, + {1.25000000e+01f, 5.72000000e+02f}, + {2.50000000e+01f, 5.22000000e+02f}}}}, + {SEGMENT_BEZIER, {{{3.75000000e+01f, 5.72000000e+02f}, + {5.00000000e+01f, 4.72000000e+02f}, + {6.25000000e+01f, 5.22000000e+02f}}}}, + {SEGMENT_BEZIER, {{{5.00000000e+01f, 5.72000000e+02f}, + {7.50000000e+01f, 6.22000000e+02f}, + {6.25000000e+01f, 6.72000000e+02f}}}}, + {SEGMENT_BEZIER, {{{5.00000000e+01f, 6.22000000e+02f}, + {3.75000000e+01f, 7.22000000e+02f}, + {2.50000000e+01f, 6.72000000e+02f}}}}, + /* Figure 32. */ + {SEGMENT_BEZIER, {{{5.50000000e+01f, 4.52000000e+02f}, + {3.50000000e+01f, 4.52000000e+02f}, + {7.00000000e+01f, 5.97000000e+02f}}}}, + {SEGMENT_BEZIER, {{{3.50000000e+01f, 7.42000000e+02f}, + {5.50000000e+01f, 7.42000000e+02f}, + {2.00000000e+01f, 5.97000000e+02f}}}}, + /* Figure 33. */ + {SEGMENT_LINE, {{{2.50000000e+01f, 7.00000000e+01f}}}}, + {SEGMENT_LINE, {{{4.37500000e+01f, 7.00000000e+01f}}}}, + {SEGMENT_LINE, {{{6.25000000e+01f, 7.00000000e+01f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{6.25000000e+01f, 2.20000000e+02f}}}}, + {SEGMENT_LINE, {{{4.37500000e+01f, 2.20000000e+02f}}}}, + {SEGMENT_LINE, {{{2.50000000e+01f, 2.20000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + /* Figure 34. */ + {SEGMENT_LINE, {{{4.50000000e+01f, 3.62500000e+01f}}}}, + {SEGMENT_LINE, {{{7.00000000e+01f, 1.45000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{4.50000000e+01f, 2.53750000e+02f}}}}, + {SEGMENT_LINE, {{{2.00000000e+01f, 1.45000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + /* Figure 35. */ + {SEGMENT_LINE, {{{2.50000000e+01f, 5.22000000e+02f}}}}, + {SEGMENT_LINE, {{{4.37500000e+01f, 5.22000000e+02f}}}}, + {SEGMENT_LINE, {{{6.25000000e+01f, 5.22000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{6.25000000e+01f, 6.72000000e+02f}}}}, + {SEGMENT_LINE, {{{4.37500000e+01f, 6.72000000e+02f}}}}, + {SEGMENT_LINE, {{{2.50000000e+01f, 6.72000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + /* Figure 36. */ + {SEGMENT_LINE, {{{4.50000000e+01f, 4.88250000e+02f}}}}, + {SEGMENT_LINE, {{{7.00000000e+01f, 5.97000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{4.50000000e+01f, 7.05750000e+02f}}}}, + {SEGMENT_LINE, {{{2.00000000e+01f, 5.97000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + /* Figure 37. */ + {SEGMENT_LINE, {{{2.50000000e+01f, 1.45000000e+02f}}}}, + {SEGMENT_LINE, {{{2.50000000e+01f, 7.00000000e+01f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{3.43750000e+01f, 8.40625000e+01f}}}}, + {SEGMENT_LINE, {{{4.37500000e+01f, 7.00000000e+01f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{5.31250000e+01f, 5.59375000e+01f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{6.25000000e+01f, 7.00000000e+01f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{6.25000000e+01f, 1.45000000e+02f}}}}, + {SEGMENT_LINE, {{{6.25000000e+01f, 2.20000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{5.31250000e+01f, 2.05937500e+02f}}}}, + {SEGMENT_LINE, {{{4.37500000e+01f, 2.20000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{3.43750000e+01f, 2.34062500e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{2.50000000e+01f, 2.20000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + /* Figure 38. */ + {SEGMENT_LINE, {{{3.76562500e+01f, 6.34375000e+01f}}}}, + {SEGMENT_LINE, {{{4.50000000e+01f, 3.62500000e+01f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{5.23437500e+01f, 6.34375000e+01f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{7.00000000e+01f, 1.45000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{5.23437500e+01f, 2.26562500e+02f}}}}, + {SEGMENT_LINE, {{{4.50000000e+01f, 2.53750000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{3.76562500e+01f, 2.26562500e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{2.00000000e+01f, 1.45000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + /* Figure 39. */ + {SEGMENT_LINE, {{{2.50000000e+01f, 5.97000000e+02f}}}}, + {SEGMENT_LINE, {{{2.50000000e+01f, 5.22000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{3.43750000e+01f, 5.36062500e+02f}}}}, + {SEGMENT_LINE, {{{4.37500000e+01f, 5.22000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{5.31250000e+01f, 5.07937500e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{6.25000000e+01f, 5.22000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{6.25000000e+01f, 5.97000000e+02f}}}}, + {SEGMENT_LINE, {{{6.25000000e+01f, 6.72000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{5.31250000e+01f, 6.57937500e+02f}}}}, + {SEGMENT_LINE, {{{4.37500000e+01f, 6.72000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{3.43750000e+01f, 6.86062500e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{2.50000000e+01f, 6.72000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + /* Figure 40. */ + {SEGMENT_LINE, {{{3.76562500e+01f, 5.15437500e+02f}}}}, + {SEGMENT_LINE, {{{4.50000000e+01f, 4.88250000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{5.23437500e+01f, 5.15437500e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{7.00000000e+01f, 5.97000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{5.23437500e+01f, 6.78562500e+02f}}}}, + {SEGMENT_LINE, {{{4.50000000e+01f, 7.05750000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{3.76562500e+01f, 6.78562500e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + {SEGMENT_LINE, {{{2.00000000e+01f, 5.97000000e+02f}}}, D2D1_PATH_SEGMENT_FORCE_ROUND_LINE_JOIN}, + /* Figure 41. */ + {SEGMENT_BEZIER, {{{1.96464462e+02f, 1.60274170e+02f}, + {1.87625626e+02f, 5.42081528e+01f}, + {2.05303299e+02f, 5.42081528e+01f}}}}, + {SEGMENT_BEZIER, {{{2.05303299e+02f, 1.24918823e+02f}, + {2.31819809e+02f, 8.95634918e+01f}, + {2.31819809e+02f, 1.60274170e+02f}}}}, + {SEGMENT_BEZIER, {{{2.14142136e+02f, 1.60274170e+02f}, + {2.22980972e+02f, 2.66340179e+02f}, + {2.05303299e+02f, 2.66340179e+02f}}}}, + {SEGMENT_BEZIER, {{{2.05303299e+02f, 1.95629517e+02f}, + {1.78786804e+02f, 2.30984833e+02f}, + {1.78786804e+02f, 1.60274170e+02f}}}}, + /* Figure 42. */ + {SEGMENT_BEZIER, {{{2.38890869e+02f, 8.95634918e+01f}, + {2.24748734e+02f, 3.29949493e+01f}, + {2.23864853e+02f, 2.34520386e+02f}}}}, + {SEGMENT_BEZIER, {{{1.73483490e+02f, 2.38055908e+02f}, + {1.87625641e+02f, 2.94624451e+02f}, + {1.88509521e+02f, 9.30990295e+01f}}}}, + /* Figure 43. */ + {SEGMENT_BEZIER, {{{1.16561401e+02f, 4.79886414e+02f}, + {1.07722572e+02f, 3.73820404e+02f}, + {1.25400238e+02f, 3.73820435e+02f}}}}, + {SEGMENT_BEZIER, {{{1.25400238e+02f, 4.44531067e+02f}, + {1.51916748e+02f, 4.09175751e+02f}, + {1.51916733e+02f, 4.79886414e+02f}}}}, + {SEGMENT_BEZIER, {{{1.34239075e+02f, 4.79886414e+02f}, + {1.43077911e+02f, 5.85952454e+02f}, + {1.25400238e+02f, 5.85952454e+02f}}}}, + {SEGMENT_BEZIER, {{{1.25400238e+02f, 5.15241760e+02f}, + {9.88837280e+01f, 5.50597107e+02f}, + {9.88837280e+01f, 4.79886414e+02f}}}}, + /* Figure 44. */ + {SEGMENT_BEZIER, {{{1.58987808e+02f, 4.09175781e+02f}, + {1.44845673e+02f, 3.52607239e+02f}, + {1.43961792e+02f, 5.54132629e+02f}}}}, + {SEGMENT_BEZIER, {{{9.35804291e+01f, 5.57668152e+02f}, + {1.07722565e+02f, 6.14236694e+02f}, + {1.08606453e+02f, 4.12711273e+02f}}}}, }; static const struct expected_geometry_figure expected_figures[] = { @@ -2829,6 +3016,30 @@ static void test_path_geometry(void) {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 20.0f, 612.0f}, 8, &expected_segments[164]}, /* 28 */ {D2D1_FIGURE_BEGIN_HOLLOW, D2D1_FIGURE_END_OPEN, { 40.0f, 20.0f}, 2, &expected_segments[172]}, + /* 29 */ + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 25.0f, 220.0f}, 4, &expected_segments[174]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 20.0f, 145.0f}, 2, &expected_segments[178]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 25.0f, 672.0f}, 4, &expected_segments[180]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 20.0f, 597.0f}, 2, &expected_segments[184]}, + /* 33 */ + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 25.0f, 220.0f}, 6, &expected_segments[186]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 20.0f, 145.0f}, 4, &expected_segments[192]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 25.0f, 672.0f}, 6, &expected_segments[196]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 20.0f, 597.0f}, 4, &expected_segments[202]}, + /* 37 */ + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 25.0f, 220.0f}, 12, &expected_segments[206]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 20.0f, 145.0f}, 8, &expected_segments[218]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 25.0f, 672.0f}, 12, &expected_segments[226]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, { 20.0f, 597.0f}, 8, &expected_segments[238]}, + /* 41 */ + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, + {1.78786804e+02f, 1.60274170e+02f}, 4, &expected_segments[246]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, + {1.88509521e+02f, 9.30990295e+01f}, 2, &expected_segments[250]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, + {9.88837280e+01f, 4.79886414e+02f}, 4, &expected_segments[252]}, + {D2D1_FIGURE_BEGIN_FILLED, D2D1_FIGURE_END_CLOSED, + {1.08606453e+02f, 4.12711273e+02f}, 2, &expected_segments[256]}, };
if (!(device = create_device())) @@ -3070,6 +3281,8 @@ static void test_path_geometry(void)
ID2D1PathGeometry_Release(geometry);
+ + /* GetBounds() with bezier segments. */ hr = ID2D1Factory_CreatePathGeometry(factory, &geometry); ok(SUCCEEDED(hr), "Failed to create path geometry, hr %#x.\n", hr); @@ -3087,6 +3300,12 @@ static void test_path_geometry(void) ok(match, "Got unexpected rectangle {%.8e, %.8e, %.8e, %.8e}.\n", rect.left, rect.top, rect.right, rect.bottom);
+ ID2D1RenderTarget_BeginDraw(rt); + set_color(&color, 0.396f, 0.180f, 0.537f, 1.0f); + ID2D1RenderTarget_Clear(rt, &color); + ID2D1RenderTarget_FillGeometry(rt, (ID2D1Geometry *)geometry, (ID2D1Brush *)brush, NULL); + hr = ID2D1RenderTarget_EndDraw(rt, NULL, NULL); + set_matrix_identity(&matrix); translate_matrix(&matrix, 80.0f, 640.0f); scale_matrix(&matrix, 2.0f, 0.5f); @@ -3556,7 +3775,145 @@ static void test_path_geometry(void) ok(SUCCEEDED(hr), "Failed to simplify geometry, hr %#x.\n", hr); geometry_sink_check(&simplify_sink, D2D1_FILL_MODE_ALTERNATE, 1, &expected_figures[28], 1); geometry_sink_cleanup(&simplify_sink); + ID2D1PathGeometry_Release(geometry); + + /* GetBounds() with cubic bezier segments. */ + hr = ID2D1Factory_CreatePathGeometry(factory, &geometry); + ok(SUCCEEDED(hr), "Failed to create path geometry, hr %#x.\n", hr); + hr = ID2D1PathGeometry_Open(geometry, &sink); + ok(SUCCEEDED(hr), "Failed to open geometry sink, hr %#x.\n", hr); + fill_geometry_sink_cubic_bezier(sink, 0); + hr = ID2D1GeometrySink_Close(sink); + ok(SUCCEEDED(hr), "Failed to close geometry sink, hr %#x.\n", hr); + ID2D1GeometrySink_Release(sink); + + set_rect(&rect, 0.0f, 0.0f, 0.0f, 0.0f); + hr = ID2D1PathGeometry_GetBounds(geometry, NULL, &rect); + ok(SUCCEEDED(hr), "Failed to get geometry bounds, hr %#x.\n", hr); + match = compare_rect(&rect, 20.0f, 36.25f, 70.0f, 705.75f, 0); + ok(match, "Got unexpected rectangle {%.8e, %.8e, %.8e, %.8e}.\n", + rect.left, rect.top, rect.right, rect.bottom); + + set_matrix_identity(&matrix); + translate_matrix(&matrix, 80.0f, 640.0f); + scale_matrix(&matrix, 2.0f, 0.5f); + set_rect(&rect, 0.0f, 0.0f, 0.0f, 0.0f); + hr = ID2D1PathGeometry_GetBounds(geometry, &matrix, &rect); + ok(SUCCEEDED(hr), "Failed to get geometry bounds, hr %#x.\n", hr); + match = compare_rect(&rect, 120.0f, 658.125f, 220.0f, 992.875f, 0); + ok(match, "Got unexpected rectangle {%.8e, %.8e, %.8e, %.8e}.\n", + rect.left, rect.top, rect.right, rect.bottom); + + ID2D1PathGeometry_Release(geometry); + + /* GetBounds() with cubic bezier segments and some hollow components. */ + hr = ID2D1Factory_CreatePathGeometry(factory, &geometry); + ok(SUCCEEDED(hr), "Failed to create path geometry, hr %#x.\n", hr); + hr = ID2D1PathGeometry_Open(geometry, &sink); + ok(SUCCEEDED(hr), "Failed to open geometry sink, hr %#x.\n", hr); + fill_geometry_sink_cubic_bezier(sink, 2); + hr = ID2D1GeometrySink_Close(sink); + ok(SUCCEEDED(hr), "Failed to close geometry sink, hr %#x.\n", hr); + ID2D1GeometrySink_Release(sink); + + set_rect(&rect, 0.0f, 0.0f, 0.0f, 0.0f); + hr = ID2D1PathGeometry_GetBounds(geometry, NULL, &rect); + ok(SUCCEEDED(hr), "Failed to get geometry bounds, hr %#x.\n", hr); + match = compare_rect(&rect, 20.0f, 488.25f, 70.0f, 705.75f, 0); + ok(match, "Got unexpected rectangle {%.8e, %.8e, %.8e, %.8e}.\n", + rect.left, rect.top, rect.right, rect.bottom); + + set_matrix_identity(&matrix); + translate_matrix(&matrix, 80.0f, 640.0f); + scale_matrix(&matrix, 2.0f, 0.5f); + set_rect(&rect, 0.0f, 0.0f, 0.0f, 0.0f); + hr = ID2D1PathGeometry_GetBounds(geometry, &matrix, &rect); + ok(SUCCEEDED(hr), "Failed to get geometry bounds, hr %#x.\n", hr); + match = compare_rect(&rect, 120.0f, 884.125f, 220.0f, 992.875f, 0); + ok(match, "Got unexpected rectangle {%.8e, %.8e, %.8e, %.8e}.\n", + rect.left, rect.top, rect.right, rect.bottom); + + ID2D1PathGeometry_Release(geometry); + + /* GetBounds() with bezier segments and all hollow components. */ + hr = ID2D1Factory_CreatePathGeometry(factory, &geometry); + ok(SUCCEEDED(hr), "Failed to create path geometry, hr %#x.\n", hr); + hr = ID2D1PathGeometry_Open(geometry, &sink); + ok(SUCCEEDED(hr), "Failed to open geometry sink, hr %#x.\n", hr); + fill_geometry_sink_cubic_bezier(sink, 4); + hr = ID2D1GeometrySink_Close(sink); + ok(SUCCEEDED(hr), "Failed to close geometry sink, hr %#x.\n", hr); + ID2D1GeometrySink_Release(sink); + + set_rect(&rect, 0.0f, 0.0f, 0.0f, 0.0f); + hr = ID2D1PathGeometry_GetBounds(geometry, NULL, &rect); + ok(SUCCEEDED(hr), "Failed to get geometry bounds, hr %#x.\n", hr); + match = compare_rect(&rect, INFINITY, INFINITY, FLT_MAX, FLT_MAX, 0); + ok(match, "Got unexpected rectangle {%.8e, %.8e, %.8e, %.8e}.\n", + rect.left, rect.top, rect.right, rect.bottom); + + set_matrix_identity(&matrix); + translate_matrix(&matrix, 80.0f, 640.0f); + scale_matrix(&matrix, 2.0f, 0.5f); + set_rect(&rect, 0.0f, 0.0f, 0.0f, 0.0f); + hr = ID2D1PathGeometry_GetBounds(geometry, &matrix, &rect); + ok(SUCCEEDED(hr), "Failed to get geometry bounds, hr %#x.\n", hr); + match = compare_rect(&rect, INFINITY, INFINITY, FLT_MAX, FLT_MAX, 0); + ok(match, "Got unexpected rectangle {%.8e, %.8e, %.8e, %.8e}.\n", + rect.left, rect.top, rect.right, rect.bottom); + + ID2D1PathGeometry_Release(geometry); + + /* Cubic bezier simplify tests. */ + hr = ID2D1Factory_CreatePathGeometry(factory, &geometry); + ok(SUCCEEDED(hr), "Failed to create path geometry, hr %#x.\n", hr); + hr = ID2D1PathGeometry_Open(geometry, &sink); + ok(SUCCEEDED(hr), "Failed to open geometry sink, hr %#x.\n", hr); + fill_geometry_sink_cubic_bezier(sink, 0); + hr = ID2D1GeometrySink_Close(sink); + ok(SUCCEEDED(hr), "Failed to close geometry sink, hr %#x.\n", hr); + hr = ID2D1PathGeometry_GetFigureCount(geometry, &count); + ok(SUCCEEDED(hr), "Failed to get figure count, hr %#x.\n", hr); + ok(count == 4, "Got unexpected figure count %u.\n", count); + hr = ID2D1PathGeometry_GetSegmentCount(geometry, &count); + ok(SUCCEEDED(hr), "Failed to get segment count, hr %#x.\n", hr); + ok(count == 16, "Got unexpected segment count %u.\n", count); + ID2D1GeometrySink_Release(sink); + + geometry_sink_init(&simplify_sink); + hr = ID2D1PathGeometry_Simplify(geometry, D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + NULL, 0.0f, &simplify_sink.ID2D1SimplifiedGeometrySink_iface); + ok(SUCCEEDED(hr), "Failed to simplify geometry, hr %#x.\n", hr); + geometry_sink_check(&simplify_sink, D2D1_FILL_MODE_ALTERNATE, 4, &expected_figures[29], 1); + geometry_sink_cleanup(&simplify_sink); + geometry_sink_init(&simplify_sink); + hr = ID2D1PathGeometry_Simplify(geometry, D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES, + NULL, 100.0f, &simplify_sink.ID2D1SimplifiedGeometrySink_iface); + ok(SUCCEEDED(hr), "Failed to simplify geometry, hr %#x.\n", hr); + geometry_sink_check(&simplify_sink, D2D1_FILL_MODE_ALTERNATE, 4, &expected_figures[33], 1); + geometry_sink_cleanup(&simplify_sink); + geometry_sink_init(&simplify_sink); + hr = ID2D1PathGeometry_Simplify(geometry, D2D1_GEOMETRY_SIMPLIFICATION_OPTION_LINES, + NULL, 10.0f, &simplify_sink.ID2D1SimplifiedGeometrySink_iface); + ok(SUCCEEDED(hr), "Failed to simplify geometry, hr %#x.\n", hr); + geometry_sink_check(&simplify_sink, D2D1_FILL_MODE_ALTERNATE, 4, &expected_figures[37], 1); + geometry_sink_cleanup(&simplify_sink);
+ set_matrix_identity(&matrix); + scale_matrix(&matrix, 0.5f, 2.0f); + translate_matrix(&matrix, 400.0f, -33.0f); + rotate_matrix(&matrix, M_PI / 4.0f); + scale_matrix(&matrix, 2.0f, 0.5f); + hr = ID2D1Factory_CreateTransformedGeometry(factory, (ID2D1Geometry *)geometry, &matrix, &transformed_geometry); + ok(SUCCEEDED(hr), "Failed to create transformed geometry, hr %#x.\n", hr); + + geometry_sink_init(&simplify_sink); + hr = ID2D1TransformedGeometry_Simplify(transformed_geometry, D2D1_GEOMETRY_SIMPLIFICATION_OPTION_CUBICS_AND_LINES, + NULL, 0.0f, &simplify_sink.ID2D1SimplifiedGeometrySink_iface); + ok(SUCCEEDED(hr), "Failed to simplify geometry, hr %#x.\n", hr); + geometry_sink_check(&simplify_sink, D2D1_FILL_MODE_ALTERNATE, 4, &expected_figures[41], 4); + geometry_sink_cleanup(&simplify_sink); + ID2D1TransformedGeometry_Release(transformed_geometry); ID2D1PathGeometry_Release(geometry);
ID2D1SolidColorBrush_Release(brush);
Signed-off-by: Connor McAdams conmanx360@gmail.com --- dlls/d2d1/tests/d2d1.c | 72 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+)
diff --git a/dlls/d2d1/tests/d2d1.c b/dlls/d2d1/tests/d2d1.c index a3fe5f10a8..7426a793c0 100644 --- a/dlls/d2d1/tests/d2d1.c +++ b/dlls/d2d1/tests/d2d1.c @@ -8256,6 +8256,78 @@ static void test_bezier_intersect(void) "c9EBcdQBb9YBbNkBatsBaN0BZeEBYuQBX+gBW+0BVvEBUvUBTvwBR4QCQIoCOZgCK6oCGQIA"); ok(match, "Figure does not match.\n");
+ /* Cubic bezier figure with a single line-bezier intersection and multiple + * double line-bezier intersections. */ + hr = ID2D1Factory_CreatePathGeometry(factory, &geometry); + ok(SUCCEEDED(hr), "Failed to create path geometry, hr %#x.\n", hr); + hr = ID2D1PathGeometry_Open(geometry, &sink); + ok(SUCCEEDED(hr), "Failed to open geometry sink, hr %#x.\n", hr); + + set_point(&point, 50.0f, 120.0f); + ID2D1GeometrySink_BeginFigure(sink, point, D2D1_FIGURE_BEGIN_FILLED); + cubic_to(sink, 100.0f, 20.0f, 150.0f, 220.0f, 200.0f, 120.0f); + cubic_to(sink, 150.0f, 320.0f, 250.0f, 520.0f, 200.0f, 720.0f); + cubic_to(sink, 150.0f, 620.0f, 100.0f, 820.0f, 50.0f, 720.0f); + cubic_to(sink, 0.0f, 520.0f, 100.0f, 320.0f, 50.0f, 120.0f); + ID2D1GeometrySink_EndFigure(sink, D2D1_FIGURE_END_CLOSED); + + set_point(&point, 40.0f, 80.0f); + ID2D1GeometrySink_BeginFigure(sink, point, D2D1_FIGURE_BEGIN_FILLED); + line_to(sink, 180.0f, 80.0f); + line_to(sink, 180.0f, 720.0f); + line_to(sink, 40.0f, 720.0f); + + ID2D1GeometrySink_EndFigure(sink, D2D1_FIGURE_END_CLOSED); + + hr = ID2D1GeometrySink_Close(sink); + ok(SUCCEEDED(hr), "Failed to close geometry sink, hr %#x.\n", hr); + ID2D1GeometrySink_Release(sink); + + ID2D1RenderTarget_BeginDraw(rt); + set_color(&color, 1.0f, 1.0f, 1.0f, 1.0f); + set_color(&color, 0.396f, 0.180f, 0.537f, 1.0f); + ID2D1RenderTarget_Clear(rt, &color); + ID2D1RenderTarget_FillGeometry(rt, (ID2D1Geometry *)geometry, (ID2D1Brush *)brush, NULL); + hr = ID2D1RenderTarget_EndDraw(rt, NULL, NULL); + ok(SUCCEEDED(hr), "Failed to end draw, hr %#x.\n", hr); + ID2D1PathGeometry_Release(geometry); + + /* Cubic bezier figure with a line-bezier intersection of 3 points within + * one line and one bezier segment. */ + hr = ID2D1Factory_CreatePathGeometry(factory, &geometry); + ok(SUCCEEDED(hr), "Failed to create path geometry, hr %#x.\n", hr); + hr = ID2D1PathGeometry_Open(geometry, &sink); + ok(SUCCEEDED(hr), "Failed to open geometry sink, hr %#x.\n", hr); + + set_point(&point, 50.0f, 120.0f); + ID2D1GeometrySink_BeginFigure(sink, point, D2D1_FIGURE_BEGIN_FILLED); + cubic_to(sink, 100.0f, 20.0f, 150.0f, 220.0f, 200.0f, 120.0f); + cubic_to(sink, 150.0f, 320.0f, 250.0f, 520.0f, 200.0f, 720.0f); + cubic_to(sink, 150.0f, 620.0f, 100.0f, 820.0f, 50.0f, 720.0f); + cubic_to(sink, 0.0f, 520.0f, 100.0f, 320.0f, 50.0f, 120.0f); + ID2D1GeometrySink_EndFigure(sink, D2D1_FIGURE_END_CLOSED); + + set_point(&point, 20.0f, 20.0f); + ID2D1GeometrySink_BeginFigure(sink, point, D2D1_FIGURE_BEGIN_FILLED); + line_to(sink, 180.0f, 80.0f); + line_to(sink, 220.0f, 900.0f); + line_to(sink, 20.0f, 900.0f); + + ID2D1GeometrySink_EndFigure(sink, D2D1_FIGURE_END_CLOSED); + + hr = ID2D1GeometrySink_Close(sink); + ok(SUCCEEDED(hr), "Failed to close geometry sink, hr %#x.\n", hr); + ID2D1GeometrySink_Release(sink); + + ID2D1RenderTarget_BeginDraw(rt); + set_color(&color, 1.0f, 1.0f, 1.0f, 1.0f); + set_color(&color, 0.396f, 0.180f, 0.537f, 1.0f); + ID2D1RenderTarget_Clear(rt, &color); + ID2D1RenderTarget_FillGeometry(rt, (ID2D1Geometry *)geometry, (ID2D1Brush *)brush, NULL); + hr = ID2D1RenderTarget_EndDraw(rt, NULL, NULL); + ok(SUCCEEDED(hr), "Failed to end draw, hr %#x.\n", hr); + ID2D1PathGeometry_Release(geometry); + ID2D1SolidColorBrush_Release(brush); ID2D1RenderTarget_Release(rt); refcount = ID2D1Factory_Release(factory);