On 8 September 2017 at 16:11, Nikolay Sivov <nsivov(a)codeweavers.com> wrote:
+static BOOL d2d_point_on_segment(const D2D1_POINT_2F *q, const D2D1_POINT_2F *p0, + const D2D1_POINT_2F *p1, float tolerance) +{ + float dot, edge_length, distance; + D2D1_POINT_2F v_p, v_q, n; + + d2d_point_subtract(&v_p, p1, p0); + if ((edge_length = d2d_point_length(&v_p)) == 0.0f) + return FALSE; + + /* Edge distance. */ + d2d_point_subtract(&v_q, q, p0); + + n.x = -v_p.y; + n.y = v_p.x; Unfortunately it's not that easy when "transform" is a world transformation instead of a geometry transformation. I.e., when you have for example a shear transformation, the "normal" is no longer necessarily perpendicular to the line segment. Put a different way, you're potentially testing against a parallelogram instead of a rectangle. In a sense you account for that with stroke_widths[], but see below.
+ distance = fabsf(d2d_point_dot(&v_q, &n)) / edge_length; + if (distance >= tolerance) + return FALSE; + + /* Test if normal and edge intersect. */ + dot = d2d_point_dot(&v_q, &v_p); + return dot >= 0.0f && dot <= edge_length * edge_length; +} Likewise, this doesn't do the right thing under all transformations.
+ d2d_point_transform(&v_s[0], transform, stroke_width, 0.0f); + d2d_point_transform(&v_s[1], transform, stroke_width, 0.0f); + d2d_point_transform(&origin, transform, 0.0f, 0.0f); + + d2d_point_subtract(&v_s[0], &v_s[0], &origin); + d2d_point_subtract(&v_s[1], &v_s[1], &origin); + + stroke_widths[0] = d2d_point_length(&v_s[0]); + stroke_widths[1] = d2d_point_length(&v_s[1]); This ends up with the same value for stroke_widths[0] and stroke_widths[1].
+ d2d_point_subtract(&v, &point, &vertices[i]); + + if (d2d_point_dot(&v, &v) < tolerance * tolerance) + { + *contains = TRUE; + break; + } This tests around the corner of the joins, but note that there's potentially a space between that corner and the start of the line segment that this would miss.
+ else + { + D2D1_POINT_2F d, s; + + s.x = rect->right - rect->left; + s.y = rect->bottom - rect->top; + d.x = fabsf((rect->right + rect->left) * 0.5f - point.x); + d.y = fabsf((rect->bottom + rect->top) * 0.5f - point.y); + + /* Inside test. */ + if (d.x <= (s.x - stroke_width) * 0.5f - tolerance && d.y <= (s.y - stroke_width) * 0.5f - tolerance) + { + *contains = FALSE; + return S_OK; + } + + if (tolerance == 0.0f) + { + *contains = d.x < (s.x + stroke_width) * 0.5f && d.y < (s.y + stroke_width) * 0.5f; + } + else + { + d.x = max(d.x - (s.x + stroke_width) * 0.5f, 0.0f); + d.y = max(d.y - (s.y + stroke_width) * 0.5f, 0.0f); + + *contains = d2d_point_dot(&d, &d) < tolerance * tolerance; + } + } Ultimately it's not that important, but I think there's something to be said for handling the "!transform" case first as a special case. I.e.,
if (!transform) { ... return S_OK; } /* General case. */
diff --git a/dlls/d2d1/tests/d2d1.c b/dlls/d2d1/tests/d2d1.c index f6261295b6..95a010b13e 100644 --- a/dlls/d2d1/tests/d2d1.c +++ b/dlls/d2d1/tests/d2d1.c @@ -76,6 +76,15 @@ struct expected_geometry_figure const struct geometry_segment *segments; };
+struct contains_point_test +{ + D2D1_MATRIX_3X2_F transform; + D2D1_POINT_2F point; + float tolerance; + float stroke_width; + BOOL contains; +}; Unless these are reused between tests, we tend to keep them local to the function.
+ static const struct contains_point_test stroke_contains[] = + { + {{0.0f, 0.0f, 0.0f, 1.0f}, {0.1f, 10.0f}, 0.0f, 1.0f, FALSE}, + {{0.0f, 0.0f, 0.0f, 1.0f}, {5.0f, 10.0f}, 5.0f, 1.0f, FALSE}, + {{0.0f, 0.0f, 0.0f, 1.0f}, {4.9f, 10.0f}, 5.0f, 1.0f, TRUE}, + {{0.0f, 0.0f, 0.0f, 1.0f}, {5.0f, 10.0f}, -5.0f, 1.0f, FALSE}, + {{0.0f, 0.0f, 0.0f, 1.0f}, {4.9f, 10.0f}, -5.0f, 1.0f, TRUE}, + + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 10.0f}, 0.0f, 1.0f, TRUE}, + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.1f, 10.0f}, 0.0f, 1.0f, TRUE}, + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.5f, 10.0f}, 0.0f, 1.0f, FALSE}, + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 10.0f}, 1.0f, 1.0f, TRUE}, + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.59f, 10.0f}, 1.0f, 1.0f, TRUE}, + {{1.0f, 0.0f, 0.0f, 1.0f}, {-0.59f, 10.0f}, 1.0f, 1.0f, TRUE}, + {{1.0f, 0.0f, 0.0f, 1.0f}, {0.59f, 10.0f}, -1.0f, 1.0f, TRUE}, + {{1.0f, 0.0f, 0.0f, 1.0f}, {-0.59f, 10.0f}, -1.0f, 1.0f, TRUE}, + }; Rotations and shears are interesting.
@@ -2766,6 +2793,108 @@ static void test_rectangle_geometry(void) ok(SUCCEEDED(hr), "FillContainsPoint() failed, hr %#x.\n", hr); ok(!!contains, "Got wrong hit test result %d.\n", contains);
+ /* Stroked area hittesting. Edge. */ + contains = FALSE; + set_point(&point, 0.4f, 10.0f); + hr = ID2D1RectangleGeometry_StrokeContainsPoint(geometry, point, 1.0f, NULL, NULL, 0.0f, &contains); + ok(SUCCEEDED(hr), "StrokeContainsPoint() failed, hr %#x.\n", hr); + ok(!!contains, "Got wrong hit test result %d.\n", contains); We tend to use %#x for BOOLs. And well, you might as well make these array based as well.