d2d1: Implement bezier/line intersections.

Signed-off-by: Henri Verbeet <hverbeet@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Henri Verbeet 2017-08-16 21:58:23 +02:00 committed by Alexandre Julliard
parent a5f02132d3
commit a4f23ea148
2 changed files with 243 additions and 23 deletions

View File

@ -54,12 +54,14 @@ enum d2d_vertex_type
D2D_VERTEX_TYPE_NONE, D2D_VERTEX_TYPE_NONE,
D2D_VERTEX_TYPE_LINE, D2D_VERTEX_TYPE_LINE,
D2D_VERTEX_TYPE_BEZIER, D2D_VERTEX_TYPE_BEZIER,
D2D_VERTEX_TYPE_SPLIT_BEZIER,
}; };
struct d2d_segment_idx struct d2d_segment_idx
{ {
size_t figure_idx; size_t figure_idx;
size_t vertex_idx; size_t vertex_idx;
size_t control_idx;
}; };
struct d2d_figure struct d2d_figure
@ -74,6 +76,9 @@ struct d2d_figure
size_t bezier_controls_size; size_t bezier_controls_size;
size_t bezier_control_count; size_t bezier_control_count;
D2D1_POINT_2F *original_bezier_controls;
size_t original_bezier_control_count;
D2D1_RECT_F bounds; D2D1_RECT_F bounds;
unsigned int flags; unsigned int flags;
}; };
@ -105,6 +110,7 @@ struct d2d_geometry_intersection
{ {
size_t figure_idx; size_t figure_idx;
size_t vertex_idx; size_t vertex_idx;
size_t control_idx;
float t; float t;
D2D1_POINT_2F p; D2D1_POINT_2F p;
}; };
@ -391,6 +397,15 @@ static void d2d_point_lerp(D2D1_POINT_2F *out,
out->y = a->y * (1.0f - t) + b->y * t; 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,
const D2D1_POINT_2F *p1, const D2D1_POINT_2F *p2, float t)
{
float t_c = 1.0f - t;
out->x = t_c * (t_c * p0->x + t * p1->x) + t * (t_c * p1->x + t * p2->x);
out->y = t_c * (t_c * p0->y + t * p1->y) + t * (t_c * p1->y + t * p2->y);
}
static float d2d_point_dot(const D2D1_POINT_2F *p0, const D2D1_POINT_2F *p1) static float d2d_point_dot(const D2D1_POINT_2F *p0, const D2D1_POINT_2F *p1)
{ {
return p0->x * p1->x + p0->y * p1->y; return p0->x * p1->x + p0->y * p1->y;
@ -576,6 +591,23 @@ static BOOL d2d_figure_add_vertex(struct d2d_figure *figure, D2D1_POINT_2F verte
return TRUE; return TRUE;
} }
static BOOL d2d_figure_insert_bezier_control(struct d2d_figure *figure, size_t idx, const D2D1_POINT_2F *p)
{
if (!d2d_array_reserve((void **)&figure->bezier_controls, &figure->bezier_controls_size,
figure->bezier_control_count + 1, sizeof(*figure->bezier_controls)))
{
ERR("Failed to grow bezier controls array.\n");
return FALSE;
}
memmove(&figure->bezier_controls[idx + 1], &figure->bezier_controls[idx],
(figure->bezier_control_count - idx) * sizeof(*figure->bezier_controls));
figure->bezier_controls[idx] = *p;
++figure->bezier_control_count;
return TRUE;
}
static BOOL d2d_figure_add_bezier_control(struct d2d_figure *figure, const D2D1_POINT_2F *p) static BOOL d2d_figure_add_bezier_control(struct d2d_figure *figure, const D2D1_POINT_2F *p)
{ {
if (!d2d_array_reserve((void **)&figure->bezier_controls, &figure->bezier_controls_size, if (!d2d_array_reserve((void **)&figure->bezier_controls, &figure->bezier_controls_size,
@ -1562,6 +1594,7 @@ static BOOL d2d_geometry_intersections_add(struct d2d_geometry_intersections *i,
intersection = &i->intersections[i->intersection_count++]; intersection = &i->intersections[i->intersection_count++];
intersection->figure_idx = segment_idx->figure_idx; intersection->figure_idx = segment_idx->figure_idx;
intersection->vertex_idx = segment_idx->vertex_idx; intersection->vertex_idx = segment_idx->vertex_idx;
intersection->control_idx = segment_idx->control_idx;
intersection->t = t; intersection->t = t;
intersection->p = p; intersection->p = p;
@ -1632,6 +1665,172 @@ static BOOL d2d_geometry_intersect_line_line(struct d2d_geometry *geometry,
return TRUE; return TRUE;
} }
static BOOL d2d_geometry_add_bezier_line_intersections(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 s)
{
D2D1_POINT_2F intersection;
float t;
d2d_point_calculate_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
t = (intersection.y - q[0]->y) / (q[1]->y - q[0]->y);
if (t < 0.0f || t > 1.0f)
return TRUE;
d2d_point_lerp(&intersection, q[0], q[1], t);
if (s > 0.0f && s < 1.0f && !d2d_geometry_intersections_add(intersections, idx_p, s, intersection))
return FALSE;
if (t > 0.0f && t < 1.0f && !d2d_geometry_intersections_add(intersections, idx_q, t, intersection))
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[3], *q[2];
const struct d2d_figure *figure;
float y[3], root, theta, d, e;
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];
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);
/* Intersect the transformed curve with the x-axis.
*
* f(t) = (1 - t)²P + 2(1 - t)tP + t²P
* = (P - 2P + P)t² + 2(P - P)t + P
*
* a = P - 2P + P
* b = 2(P - P)
* c = P
*
* f(t) = 0
* t = (-b ± (b² - 4ac)) / 2a
* = (-2(P - P) ± ((2(P - P))² - 4((P - 2P + P)P))) / 2(P - 2P + P)
* = (2P - 2P ± (4P² + 4P² - 8PP - 4P² + 8PP - 4PP)) / (2P - 4P + 2P)
* = (P - P ± (P² - PP)) / (P - 2P + P) */
d = y[0] - 2 * y[1] + y[2];
if (d == 0.0f)
{
/* P₀ - 2P₁ + P₂ = 0
* f(t) = (P - 2P + P)t² + 2(P - P)t + P = 0
* t = -P / 2(P - P) */
root = -y[0] / (2.0f * (y[1] - y[0]));
if (root < 0.0f || root > 1.0f)
return TRUE;
return d2d_geometry_add_bezier_line_intersections(geometry, intersections, idx_p, p, idx_q, q, root);
}
e = y[1] * y[1] - y[0] * y[2];
if (e < 0.0f)
return TRUE;
root = (y[0] - y[1] + sqrtf(e)) / d;
if (root >= 0.0f && root <= 1.0f && !d2d_geometry_add_bezier_line_intersections(geometry,
intersections, idx_p, p, idx_q, q, root))
return FALSE;
root = (y[0] - y[1] - sqrtf(e)) / d;
if (root >= 0.0f && root <= 1.0f && !d2d_geometry_add_bezier_line_intersections(geometry,
intersections, idx_p, p, idx_q, q, root))
return FALSE;
return TRUE;
}
static BOOL d2d_geometry_apply_intersections(struct d2d_geometry *geometry,
struct d2d_geometry_intersections *intersections)
{
size_t vertex_offset, control_offset, next, i;
struct d2d_geometry_intersection *inter;
enum d2d_vertex_type vertex_type;
const D2D1_POINT_2F *p[3];
struct d2d_figure *figure;
D2D1_POINT_2F q[2];
float t, t_prev;
for (i = 0; i < intersections->intersection_count; ++i)
{
inter = &intersections->intersections[i];
if (!i || inter->figure_idx != intersections->intersections[i - 1].figure_idx)
vertex_offset = control_offset = 0;
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_figure_insert_vertex(&geometry->u.path.figures[inter->figure_idx],
inter->vertex_idx + vertex_offset + 1, inter->p))
return FALSE;
++vertex_offset;
continue;
}
t = inter->t;
if (i && inter->figure_idx == intersections->intersections[i - 1].figure_idx
&& inter->vertex_idx == intersections->intersections[i - 1].vertex_idx)
{
t_prev = intersections->intersections[i - 1].t;
if (t - t_prev < 1e-3f)
{
inter->t = intersections->intersections[i - 1].t;
continue;
}
t = (t - t_prev) / (1.0f - t_prev);
}
p[0] = &figure->vertices[inter->vertex_idx + vertex_offset];
p[1] = &figure->bezier_controls[inter->control_idx + control_offset];
next = inter->vertex_idx + vertex_offset + 1;
if (next == figure->vertex_count)
next = 0;
p[2] = &figure->vertices[next];
d2d_point_lerp(&q[0], p[0], p[1], t);
d2d_point_lerp(&q[1], p[1], p[2], 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;
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;
++vertex_offset;
}
return TRUE;
}
/* Intersect the geometry's segments with themselves. This uses the /* Intersect the geometry's segments with themselves. This uses the
* straightforward approach of testing everything against everything, but * straightforward approach of testing everything against everything, but
* there certainly exist more scalable algorithms for this. */ * there certainly exist more scalable algorithms for this. */
@ -1642,8 +1841,8 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry)
const struct d2d_figure *figure_p, *figure_q; const struct d2d_figure *figure_p, *figure_q;
struct d2d_segment_idx idx_p, idx_q; struct d2d_segment_idx idx_p, idx_q;
enum d2d_vertex_type type_p, type_q; enum d2d_vertex_type type_p, type_q;
size_t i, j, max_q;
BOOL ret = FALSE; BOOL ret = FALSE;
size_t max_q;
if (!geometry->u.path.figure_count) if (!geometry->u.path.figure_count)
return TRUE; return TRUE;
@ -1651,6 +1850,7 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry)
for (idx_p.figure_idx = 0; idx_p.figure_idx < geometry->u.path.figure_count; ++idx_p.figure_idx) for (idx_p.figure_idx = 0; idx_p.figure_idx < geometry->u.path.figure_count; ++idx_p.figure_idx)
{ {
figure_p = &geometry->u.path.figures[idx_p.figure_idx]; figure_p = &geometry->u.path.figures[idx_p.figure_idx];
idx_p.control_idx = 0;
for (idx_p.vertex_idx = 0; idx_p.vertex_idx < figure_p->vertex_count; ++idx_p.vertex_idx) for (idx_p.vertex_idx = 0; idx_p.vertex_idx < figure_p->vertex_count; ++idx_p.vertex_idx)
{ {
type_p = figure_p->vertex_types[idx_p.vertex_idx]; type_p = figure_p->vertex_types[idx_p.vertex_idx];
@ -1671,33 +1871,40 @@ static BOOL d2d_geometry_intersect_self(struct d2d_geometry *geometry)
max_q = idx_p.vertex_idx; max_q = idx_p.vertex_idx;
} }
idx_q.control_idx = 0;
for (idx_q.vertex_idx = 0; idx_q.vertex_idx < max_q; ++idx_q.vertex_idx) 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]; type_q = figure_q->vertex_types[idx_q.vertex_idx];
if (type_p != D2D_VERTEX_TYPE_BEZIER && type_q != D2D_VERTEX_TYPE_BEZIER if (type_q == D2D_VERTEX_TYPE_BEZIER)
&& !d2d_geometry_intersect_line_line(geometry, &intersections, &idx_p, &idx_q)) {
if (type_p != D2D_VERTEX_TYPE_BEZIER
&& !d2d_geometry_intersect_bezier_line(geometry, &intersections, &idx_q, &idx_p))
goto done;
++idx_q.control_idx;
}
else
{
if (type_p == D2D_VERTEX_TYPE_BEZIER)
{
if (!d2d_geometry_intersect_bezier_line(geometry, &intersections, &idx_p, &idx_q))
goto done;
}
else
{
if (!d2d_geometry_intersect_line_line(geometry, &intersections, &idx_p, &idx_q))
goto done; goto done;
} }
} }
} }
} }
if (type_p == D2D_VERTEX_TYPE_BEZIER)
++idx_p.control_idx;
}
}
qsort(intersections.intersections, intersections.intersection_count, qsort(intersections.intersections, intersections.intersection_count,
sizeof(*intersections.intersections), d2d_geometry_intersections_compare); sizeof(*intersections.intersections), d2d_geometry_intersections_compare);
for (i = 0; i < intersections.intersection_count; ++i) ret = d2d_geometry_apply_intersections(geometry, &intersections);
{
const struct d2d_geometry_intersection *inter = &intersections.intersections[i];
if (!i || inter->figure_idx != intersections.intersections[i - 1].figure_idx)
j = 0;
if (!d2d_figure_insert_vertex(&geometry->u.path.figures[inter->figure_idx],
inter->vertex_idx + j + 1, inter->p))
goto done;
++j;
}
ret = TRUE;
done: done:
HeapFree(GetProcessHeap(), 0, intersections.intersections); HeapFree(GetProcessHeap(), 0, intersections.intersections);
@ -2263,6 +2470,7 @@ static void d2d_path_geometry_free_figures(struct d2d_geometry *geometry)
for (i = 0; i < geometry->u.path.figure_count; ++i) for (i = 0; i < geometry->u.path.figure_count; ++i)
{ {
HeapFree(GetProcessHeap(), 0, geometry->u.path.figures[i].bezier_controls); HeapFree(GetProcessHeap(), 0, geometry->u.path.figures[i].bezier_controls);
HeapFree(GetProcessHeap(), 0, geometry->u.path.figures[i].original_bezier_controls);
HeapFree(GetProcessHeap(), 0, geometry->u.path.figures[i].vertices); HeapFree(GetProcessHeap(), 0, geometry->u.path.figures[i].vertices);
} }
HeapFree(GetProcessHeap(), 0, geometry->u.path.figures); HeapFree(GetProcessHeap(), 0, geometry->u.path.figures);
@ -2298,7 +2506,8 @@ static HRESULT d2d_geometry_resolve_beziers(struct d2d_geometry *geometry)
struct d2d_bezier_vertex *b; struct d2d_bezier_vertex *b;
float sign = -1.0f; float sign = -1.0f;
if (figure->vertex_types[j] != D2D_VERTEX_TYPE_BEZIER) if (figure->vertex_types[j] != D2D_VERTEX_TYPE_BEZIER
&& figure->vertex_types[j] != D2D_VERTEX_TYPE_SPLIT_BEZIER)
continue; continue;
b = &geometry->fill.bezier_vertices[bezier_idx * 3]; b = &geometry->fill.bezier_vertices[bezier_idx * 3];
@ -2334,6 +2543,7 @@ static HRESULT STDMETHODCALLTYPE d2d_geometry_sink_Close(ID2D1GeometrySink *ifac
{ {
struct d2d_geometry *geometry = impl_from_ID2D1GeometrySink(iface); struct d2d_geometry *geometry = impl_from_ID2D1GeometrySink(iface);
HRESULT hr = E_FAIL; HRESULT hr = E_FAIL;
size_t i;
TRACE("iface %p.\n", iface); TRACE("iface %p.\n", iface);
@ -2345,6 +2555,15 @@ static HRESULT STDMETHODCALLTYPE d2d_geometry_sink_Close(ID2D1GeometrySink *ifac
} }
geometry->u.path.state = D2D_GEOMETRY_STATE_CLOSED; geometry->u.path.state = D2D_GEOMETRY_STATE_CLOSED;
for (i = 0; i < geometry->u.path.figure_count; ++i)
{
struct d2d_figure *figure = &geometry->u.path.figures[i];
size_t size = figure->bezier_control_count * sizeof(*figure->original_bezier_controls);
if (!(figure->original_bezier_controls = HeapAlloc(GetProcessHeap(), 0, size)))
goto done;
memcpy(figure->original_bezier_controls, figure->bezier_controls, size);
}
if (!d2d_geometry_intersect_self(geometry)) if (!d2d_geometry_intersect_self(geometry))
goto done; goto done;
if (FAILED(hr = d2d_geometry_resolve_beziers(geometry))) if (FAILED(hr = d2d_geometry_resolve_beziers(geometry)))
@ -2678,7 +2897,8 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *i
for (bezier_idx = 0, ++j; j < figure->vertex_count; ++j) for (bezier_idx = 0, ++j; j < figure->vertex_count; ++j)
{ {
if (figure->vertex_types[j] == D2D_VERTEX_TYPE_NONE) if (figure->vertex_types[j] == D2D_VERTEX_TYPE_NONE
|| figure->vertex_types[j] == D2D_VERTEX_TYPE_SPLIT_BEZIER)
continue; continue;
switch (type) switch (type)
@ -2691,7 +2911,7 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *i
break; break;
case D2D_VERTEX_TYPE_BEZIER: case D2D_VERTEX_TYPE_BEZIER:
p1 = figure->bezier_controls[bezier_idx++]; p1 = figure->original_bezier_controls[bezier_idx++];
if (transform) if (transform)
d2d_point_transform(&p1, transform, p1.x, p1.y); d2d_point_transform(&p1, transform, p1.x, p1.y);
p2 = figure->vertices[j]; p2 = figure->vertices[j];
@ -2715,7 +2935,7 @@ static HRESULT STDMETHODCALLTYPE d2d_path_geometry_Simplify(ID2D1PathGeometry *i
if (type == D2D_VERTEX_TYPE_BEZIER) if (type == D2D_VERTEX_TYPE_BEZIER)
{ {
p1 = figure->bezier_controls[bezier_idx++]; p1 = figure->original_bezier_controls[bezier_idx++];
if (transform) if (transform)
d2d_point_transform(&p1, transform, p1.x, p1.y); d2d_point_transform(&p1, transform, p1.x, p1.y);
p2 = figure->vertices[0]; p2 = figure->vertices[0];

View File

@ -5653,7 +5653,7 @@ static void test_bezier_intersect(void)
"cHAwMm5vMTNtbjI0bG0zNWtrNTdpajY4aGk3OmZnOTtlZjo8ZGQ8PmJjPT9hYj5BX2BAQl5eQkRc" "cHAwMm5vMTNtbjI0bG0zNWtrNTdpajY4aGk3OmZnOTtlZjo8ZGQ8PmJjPT9hYj5BX2BAQl5eQkRc"
"XUNGWltFR1lZR0lXWEhLVVZKTVNUTE9RUk5RT1BQUk5OUlRMTFRWSkpWWUdIWFtFRVteQkNdYEBA" "XUNGWltFR1lZR0lXWEhLVVZKTVNUTE9RUk5RT1BQUk5OUlRMTFRWSkpWWUdIWFtFRVteQkNdYEBA"
"YGI+PmJlOztlaDg4aGs1NWtuMjJuci4vcXUrK3V6JiZ6fiIifoMBHR2DAYsBFRWLAZUBCwuVAQAA"); "YGI+PmJlOztlaDg4aGs1NWtuMjJuci4vcXUrK3V6JiZ6fiIifoMBHR2DAYsBFRWLAZUBCwuVAQAA");
todo_wine ok(match, "Figure does not match.\n"); ok(match, "Figure does not match.\n");
hr = ID2D1Factory_CreatePathGeometry(factory, &geometry); hr = ID2D1Factory_CreatePathGeometry(factory, &geometry);
ok(SUCCEEDED(hr), "Failed to create path geometry, hr %#x.\n", hr); ok(SUCCEEDED(hr), "Failed to create path geometry, hr %#x.\n", hr);