diff --git a/TestFont.ttf b/TestFont.ttf new file mode 100644 index 00000000..6055b9ac Binary files /dev/null and b/TestFont.ttf differ diff --git a/demo/demo-font.cc b/demo/demo-font.cc index 443a0348..2bebc8e8 100644 --- a/demo/demo-font.cc +++ b/demo/demo-font.cc @@ -154,7 +154,7 @@ encode_ft_glyph (demo_font_t *font, assert (glyphy_arc_accumulator_get_error (font->acc) <= tolerance); -#if 0 +#if 1 /* Technically speaking, we want the following code, * however, crappy fonts have crappy flags. So we just * fixup unconditionally... */ diff --git a/demo/demo-fshader.glsl b/demo/demo-fshader.glsl index 597799ac..ff2e587c 100644 --- a/demo/demo-fshader.glsl +++ b/demo/demo-fshader.glsl @@ -58,12 +58,27 @@ main() /* isotropic antialiasing */ vec2 dpdx = dFdx (p); vec2 dpdy = dFdy (p); - float m = length (vec2 (length (dpdx), length (dpdy))) * SQRT2_2; - float w = abs (normalize (dpdx).x) + abs (normalize (dpdy).x); - - vec4 color = vec4 (0,0,0,1); + + float det = dpdx.x * dpdy.y - dpdx.y * dpdy.x; + mat2 P_inv = mat2(dpdy.y, -dpdx.y, -dpdy.x, dpdx.x) * (1. / det); + vec2 sdf_vector; + + /* gdist is signed distance to nearest contour; sdf_vector is the shortest vector version. */ + float gsdist = glyphy_sdf (p, gi.nominal_size, sdf_vector GLYPHY_DEMO_EXTRA_ARGS); + + + + + if (glyphy_iszero (det)) { + gl_FragColor = vec4(1,0,0,1); + return; + } + + float m = 1.;//SQRT2;//length (vec2 (length (dpdx), length (dpdy))); //1.0; + gsdist = sign (gsdist) * length (P_inv * sdf_vector); - float gsdist = glyphy_sdf (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS); + float w = abs (normalize (dpdx).x) + abs (normalize (dpdy).x); + vec4 color = vec4 (0,0,0,1); float sdist = gsdist / m * u_contrast; if (!u_debug) { @@ -84,19 +99,32 @@ main() float udist = abs (sdist); float gudist = abs (gsdist); - // Color the outline red + float pdist = glyphy_point_dist (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS); +/* // Color the outline red color += vec4 (1,0,0,1) * smoothstep (2., 1., udist); // Color the distance field in green if (!glyphy_isinf (udist)) color += vec4 (0,.3,0,(1. + sin (sdist)) * abs(1. - gsdist * 3.) / 3.); - float pdist = glyphy_point_dist (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS); + // Color points green color = mix (vec4 (0,1,0,.5), color, smoothstep (.05, .06, pdist)); glyphy_arc_list_t arc_list = glyphy_arc_list (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS); // Color the number of endpoints per cell blue color += vec4 (0,0,1,.1) * float(arc_list.num_endpoints) * 32./255.; +*/ + if (glyphy_isinf (sdf_vector.x) || glyphy_isinf (sdf_vector.y)) + color = vec4 (0, 0, 0, 1); + + else + color = vec4 (0.5*(sdf_vector.x)+0.5, 0.5*(sdf_vector.y)+0.5, 0.4, 1); + + + color += vec4 (1,1,1,1) * smoothstep (1.6, 1.4, udist); + color = mix (vec4 (0,0.2,0.2,.5), color, smoothstep (.04, .06, pdist)); + // else + // color = vec4 (0,0,0,1); } gl_FragColor = color; diff --git a/demo/jabberwocky.h b/demo/jabberwocky.h index c24f210e..8cebef52 100644 --- a/demo/jabberwocky.h +++ b/demo/jabberwocky.h @@ -5,7 +5,7 @@ static const char *jabberwocky = " And the mome raths outgrabe.\n" "\n" "\"Beware the Jabberwock, my son!\n" -" The jaws that bite, the claws that catch!\n" +" The jaws ţhąt bite, thę çląwş that ©atch!\n" "Beware the Jubjub bird, and shun\n" " The frumious Bandersnatch!\"\n" "He took his vorpal sword in hand:\n" diff --git a/src/glyphy-blob.cc b/src/glyphy-blob.cc index 90beb6f4..9b351a03 100644 --- a/src/glyphy-blob.cc +++ b/src/glyphy-blob.cc @@ -27,6 +27,14 @@ using namespace GLyphy::Geometry; +typedef struct vertex glyphy_contour_vertex_t; +struct vertex { + unsigned int start_posn; + unsigned int end_posn; + unsigned int index; /* index in whichever list of contours is most currently relevant */ + std::vector dotted_edges; /* in the contour relationship graph */ + std::vector solid_edges; /* in the contour relationship graph */ +}; #define UPPER_BITS(v,bits,total_bits) ((v) >> ((total_bits) - (bits))) #define LOWER_BITS(v,bits,total_bits) ((v) & ((1 << (bits)) - 1)) @@ -59,10 +67,10 @@ arc_endpoint_encode (unsigned int ix, unsigned int iy, double d) } static inline glyphy_rgba_t -arc_list_encode (unsigned int offset, unsigned int num_points, int side) +arc_list_encode (unsigned int first_contours_length, unsigned int offset, unsigned int num_points, int side) { glyphy_rgba_t v; - v.r = 0; // unused for arc-list encoding + v.r = LOWER_BITS (first_contours_length, 7, 8); /* store the number of contours in the first part of the partition */ v.g = UPPER_BITS (offset, 8, 16); v.b = LOWER_BITS (offset, 8, 16); v.a = LOWER_BITS (num_points, 8, 8); @@ -106,23 +114,36 @@ closest_arcs_to_cell (Point c0, Point c1, /* corners */ double faraway, const glyphy_arc_endpoint_t *endpoints, unsigned int num_endpoints, + unsigned int cutoff, /* how many endpoints are from the first contour group? */ + unsigned int *num_group_1_arcs, std::vector &near_endpoints, int *side) { - // Find distance between cell center + /* Find distance between arcs and cell center */ Point c = c0.midpoint (c1); - double min_dist = glyphy_sdf_from_arc_list (endpoints, num_endpoints, &c, NULL); - *side = min_dist >= 0 ? +1 : -1; - min_dist = fabs (min_dist); + double min_dist1 = glyphy_sdf_from_arc_list (endpoints, cutoff, &c, NULL); + double min_dist2 = glyphy_sdf_from_arc_list (endpoints + cutoff, num_endpoints - cutoff, &c, NULL); + double min_dist = fabs (glyphy_sdf_from_arc_list (endpoints, num_endpoints, &c, NULL)); + + *side = min_dist1 >= 0 ? +1 : -1; + if (min_dist2 < 0) + *side = -1; + std::vector near_arcs; - // If d is the distance from the center of the square to the nearest arc, then - // all nearest arcs to the square must be at most almost [d + half_diagonal] from the center. + /* If d is the distance from the center of the square to the nearest arc, then + * all nearest arcs to the square must be at most almost [d + half_diagonal] from the center. + */ double half_diagonal = (c - c0).len (); - double radius_squared = pow (min_dist + half_diagonal, 2); - if (min_dist - half_diagonal <= faraway) { + double radius_squared = (min_dist + half_diagonal) * (min_dist + half_diagonal); + unsigned int main_contour_arcs = 0; + + if (0 || (min_dist - half_diagonal <= faraway && /// RANDOM FUN. 0 for correct. Change 0 to 1 to get cooler debug images. /// + (min_dist1 > -1 * half_diagonal && min_dist2 > -1 * half_diagonal)) ) { Point p0 (0, 0); + + for (unsigned int i = 0; i < num_endpoints; i++) { const glyphy_arc_endpoint_t &endpoint = endpoints[i]; if (endpoint.d == GLYPHY_INFINITY) { @@ -132,20 +153,29 @@ closest_arcs_to_cell (Point c0, Point c1, /* corners */ Arc arc (p0, endpoint.p, endpoint.d); p0 = endpoint.p; - if (arc.squared_distance_to_point (c) <= radius_squared) + if (arc.squared_distance_to_point (c) <= radius_squared) { near_arcs.push_back (arc); + if (i < cutoff) { + main_contour_arcs++; + } + } } } + + *num_group_1_arcs = main_contour_arcs; Point p1 = Point (0, 0); for (unsigned i = 0; i < near_arcs.size (); i++) { Arc arc = near_arcs[i]; - - if (i == 0 || p1 != arc.p0) { + + if (i == 0 || p1 != arc.p0 || i == main_contour_arcs) { glyphy_arc_endpoint_t endpoint = {arc.p0, GLYPHY_INFINITY}; near_endpoints.push_back (endpoint); p1 = arc.p0; + if (i < main_contour_arcs) { + (*num_group_1_arcs)++; + } } glyphy_arc_endpoint_t endpoint = {arc.p1, arc.d}; @@ -155,6 +185,301 @@ closest_arcs_to_cell (Point c0, Point c1, /* corners */ } + +/* Returns true if an arc in contour1 intersects any arc in contour2. */ +glyphy_bool_t +contours_intersect (const glyphy_arc_endpoint_t *endpoints, + const glyphy_contour_vertex_t *contour_1, + const glyphy_contour_vertex_t *contour_2) +{ + /* Find the smallest box around these contours. */ + glyphy_extents_t extents_1; + glyphy_extents_clear (&extents_1); + glyphy_arc_list_extents (endpoints + contour_1->start_posn, contour_1->end_posn - contour_1->start_posn, &extents_1); + + glyphy_extents_t extents_2; + glyphy_extents_clear (&extents_2); + glyphy_arc_list_extents (endpoints + contour_2->start_posn, contour_2->end_posn - contour_2->start_posn, &extents_2); + + glyphy_bool_t feasible = false; + feasible = (extents_1.min_x <= extents_2.max_x && + extents_1.max_x >= extents_2.min_x && + extents_1.max_y >= extents_2.min_y && + extents_1.min_y <= extents_2.max_y); + + if (!feasible) + return false; + + /* If it seems feasible that these contours might intersect, then check carefully. */ + for (unsigned int j = contour_1->start_posn + 1; j < contour_1->end_posn; j++) { + const glyphy_arc_endpoint_t ethis1 = endpoints[j - 1]; + const glyphy_arc_endpoint_t enext1 = endpoints[j]; + Arc a1 (ethis1.p, enext1.p, enext1.d); + + for (unsigned int i = contour_2->start_posn + 1; i < contour_2->end_posn; i++) { + const glyphy_arc_endpoint_t ethis2 = endpoints[i - 1]; + const glyphy_arc_endpoint_t enext2 = endpoints[i]; + Arc a2 (ethis2.p, enext2.p, enext2.d); + + if (a1.intersects_arc (a2) != Point (GLYPHY_INFINITY, GLYPHY_INFINITY)) { + return true; + } + } + } + return false; +} + + +/** Used to generate a list of contours that have dotted line + * connections in the contour relationship graph; that is, + * these contours surround each other, but do not intersect. + * NOTE: the contours may not all be nested, as in the contours + * that outline the letter B (all three contours are returned). + */ +void +populate_connected_component (const std::vector contours, + const unsigned int current_contour, + std::vector *connected_contours, + bool *contours_seen) +{ + if (contours_seen [current_contour]) + return; + contours_seen [current_contour] = true; + + /* Depth-first search, essentially. */ + connected_contours->push_back (current_contour); + for (unsigned int k = 0; k < contours[current_contour].dotted_edges.size (); k++) + populate_connected_component (contours, + contours[contours[current_contour].dotted_edges[k]].index, /* o_O */ + connected_contours, contours_seen); +} + +/** To form a bipartition of the contour relationship graph, + * assign each vertex a "level" based on DFS reachability, + * and derive the bipartition using parities of these levels. + */ +void +assign_contour_levels (const std::vector new_contours, + const unsigned int current_contour, + const unsigned int projected_level, + int* contour_levels) +{ + if (contour_levels [current_contour] != -1) + return; + + contour_levels [current_contour] = projected_level; + for (unsigned int i = 0; i < new_contours [current_contour].solid_edges.size (); i++) + assign_contour_levels (new_contours, new_contours [current_contour].solid_edges [i], projected_level + 1, contour_levels); +} + + +/* Rearranges contours into two groups that don't intersect, based on a bipartite graph partition. */ +unsigned int +rearrange_contours (const glyphy_arc_endpoint_t *endpoints, + unsigned int num_endpoints, + glyphy_arc_endpoint_t *rerearranged_endpoints) +{ + + if (num_endpoints == 0) + return 0; + + std::vector contours; + unsigned int previous_index = 0; + + /* Create a list of vertices, where each vertex is a contour. Edges are still empty for now. */ + unsigned int i = 0; + unsigned int num_contours = 0; + while (i < num_endpoints) { + + /* Find the next contour and create its corresponding vertex. */ + while (i + 1 < num_endpoints && endpoints[i + 1].d != GLYPHY_INFINITY) { + i++; + } + i++; + glyphy_contour_vertex_t current_contour; + + current_contour.start_posn = previous_index; + current_contour.end_posn = i; + current_contour.index = num_contours; + current_contour.dotted_edges = std::vector (); + current_contour.solid_edges = std::vector (); + + contours.push_back (current_contour); + num_contours++; + previous_index = i; + } + + /* Set up edges for vertices, based on intersections and inclusions. */ + for (unsigned int k = 0; k < num_contours; k++) { + for (unsigned int j = 0; j < k; j++) { + + + /* If contours intersect, we place a solid edge between them. */ + if (contours_intersect (endpoints, &contours[k], &contours[j])) { + contours[k].solid_edges.push_back (j); + contours[j].solid_edges.push_back (k); + } + else + /** If one contour contains the other, we place a dotted edge between them. + * To check if a contour contains another, it is sufficient to check + * if contour_1 contains a point from contour_2, or vice versa, since we already + * know that these contours don't intersect. (For the same reason, we can be sure that + * the point from the first contour will not lie on the second contour.) + * Here we can use some code from glyphy-outline::even_odd. + */ + + if (!even_odd (endpoints + contours[k].start_posn, 1, + endpoints + contours[j].start_posn, contours[j].end_posn - contours[j].start_posn) || + !even_odd (endpoints + contours[j].start_posn, 1, + endpoints + contours[k].start_posn, contours[k].end_posn - contours[k].start_posn)) { + contours[k].dotted_edges.push_back (j); + contours[j].dotted_edges.push_back (k); + + } + } + } + + + + + /* Collapse all dotted edges by "merging" vertices together. + * Note: We are assuming quite a bit about the graph structure here. + * Hopefully it all works in the cases that ever come up. */ + bool contours_seen [contours.size ()]; + // TODO: Is there a better way to initialize the array to all false? + for (int j = 0; j < contours.size(); j++) { + contours_seen[j] = false; + } + + unsigned int current_end = 0; + std::vector new_contours; + unsigned int num_new_contours = 0; + unsigned int new_endpoint_list_index = 0; + + /* For each entry in the vector of contour vertices, make a list of contours that it has a dot-line connection with. */ + std::vector connected_contours; + glyphy_arc_endpoint_t rearranged_endpoints [num_endpoints]; + + + for (unsigned int j = 0; j < contours.size (); j++) { + if (contours_seen [j]) + continue; + + connected_contours.clear (); + populate_connected_component (contours, j, &connected_contours, contours_seen); + + /* Create a new vertex representing the merged contours. */ + glyphy_contour_vertex_t merged_contour; + merged_contour.start_posn = new_endpoint_list_index; + merged_contour.index = num_new_contours; + merged_contour.dotted_edges = std::vector (); + merged_contour.solid_edges = std::vector (); + + + for (unsigned int k = 0; k < connected_contours.size (); k++) { + + merged_contour.dotted_edges.push_back (contours [connected_contours [k]].index); + + /* Update the old contour vertex to tell us which new vertex it is now part of. */ + contours [connected_contours [k]].index = merged_contour.index; + + + /* Add all the endpoints to the rearranged endpoint array. */ + for (unsigned int m = contours [connected_contours [k]].start_posn; m < contours [connected_contours [k]].end_posn; m++) { + rearranged_endpoints [new_endpoint_list_index] = endpoints [m]; + new_endpoint_list_index++; + } + } + merged_contour.end_posn = new_endpoint_list_index; + new_contours.push_back (merged_contour); + num_new_contours++; + + } + + for (unsigned int j = 0; j < new_contours.size (); j++) + + /* Merge the lists of solid edges. */ + for (unsigned int j = 0; j < new_contours.size (); j++) { + new_contours[j].solid_edges.clear (); + + for (unsigned int m = 0; m < new_contours[j].dotted_edges.size (); m++) { + + for (unsigned int k = 0; k < contours[new_contours[j].dotted_edges[m]].solid_edges.size (); k++) { + + + unsigned int solid_edge_to_add = contours[contours[new_contours[j].dotted_edges[m]].solid_edges[k]].index; + bool is_original_edge = true; + for (unsigned int existing_edge = 0; existing_edge < new_contours[j].solid_edges.size (); existing_edge++) { + if (solid_edge_to_add == new_contours[j].solid_edges [existing_edge]) + is_original_edge = false; + } + if (is_original_edge) { + new_contours[j].solid_edges.push_back (solid_edge_to_add); + } + } + } + + } + + +/* Print out a list of contours and the contours they intersect. + for (int j = 0; j < new_contours.size (); j++) { + printf ("Contour %d: ", j); + for (int k = 0; k < new_contours[j].solid_edges.size (); k++) + printf("It intersects contour #%d. ", new_contours[j].solid_edges[k]); + printf("\n"); + for (int k = 0; k < new_contours[j].dotted_edges.size (); k++) + printf(" It includes the old contour #%d. ", new_contours[j].dotted_edges[k]); + printf("\n"); + + }*/ + + /* Time to bipartition the graph, which should contain only solid edges at this point. */ + + int contour_levels [new_contours.size ()]; + // TODO: Is there a better way to initialize the array to all -1? + for (int j = 0; j < new_contours.size(); j++) { + contour_levels [j] = -1; + } + + for (unsigned int j = 0; j < new_contours.size (); j++) { + if (contour_levels [j] != -1) + continue; + assign_contour_levels (new_contours, j, 0, contour_levels); + } + + /* Add new contours one-by-one. + * If the new contour has an even level, add it to the top of the list. + * Otherwise, add it to the bottom. + */ + unsigned int top = 0; + unsigned int bottom = num_endpoints; + + for (i = 0; i < new_contours.size (); i++) { + if (contour_levels [i] % 2 == 0) { + for (unsigned int j = new_contours [i].start_posn; j < new_contours [i].end_posn; j++) { + rerearranged_endpoints[top + j - new_contours [i].start_posn] = rearranged_endpoints[j]; + } + top = top + (new_contours [i].end_posn - new_contours [i].start_posn); + } else { + for (unsigned int j = new_contours [i].start_posn; j < new_contours [i].end_posn; j++) { + rerearranged_endpoints[bottom + j - new_contours [i].end_posn] = rearranged_endpoints[j]; + } + bottom = bottom - (new_contours [i].end_posn - new_contours [i].start_posn); + } + } + + /* Now "top" represents the partition index in the endpoint list, + * separating endpoints of contour group 1 and contour group 2. + */ + return top; + +} + + + + + glyphy_bool_t glyphy_arc_list_encode_blob (const glyphy_arc_endpoint_t *endpoints, unsigned int num_endpoints, @@ -177,7 +502,7 @@ glyphy_arc_list_encode_blob (const glyphy_arc_endpoint_t *endpoints, *pextents = extents; if (!blob_size) return false; - *blob = arc_list_encode (0, 0, +1); + *blob = arc_list_encode (0, 0, 0, +1); *avg_fetch_achieved = 1; *output_len = 1; *nominal_width = *nominal_height = 1; @@ -220,6 +545,12 @@ glyphy_arc_list_encode_blob (const glyphy_arc_endpoint_t *endpoints, Point origin = Point (extents.min_x, extents.min_y); unsigned int total_arcs = 0; + + /* Here is where we divide the arc list into two, based on intersecting contours. */ + glyphy_arc_endpoint_t rearranged_endpoints [num_endpoints]; + unsigned int cutoff = rearrange_contours (endpoints, num_endpoints, rearranged_endpoints); + endpoints = rearranged_endpoints; + for (int row = 0; row < grid_h; row++) for (int col = 0; col < grid_w; col++) { @@ -228,12 +559,14 @@ glyphy_arc_list_encode_blob (const glyphy_arc_endpoint_t *endpoints, near_endpoints.clear (); int side; + unsigned int num_group_1_arcs = 0; closest_arcs_to_cell (cp0, cp1, faraway, - endpoints, num_endpoints, - near_endpoints, + endpoints, num_endpoints, cutoff, + &num_group_1_arcs, near_endpoints, &side); + #define QUANTIZE_X(X) (lround (MAX_X * ((X - extents.min_x) / glyph_width ))) #define QUANTIZE_Y(Y) (lround (MAX_Y * ((Y - extents.min_y) / glyph_height))) #define DEQUANTIZE_X(X) (double (X) / MAX_X * glyph_width + extents.min_x) @@ -280,7 +613,8 @@ glyphy_arc_list_encode_blob (const glyphy_arc_endpoint_t *endpoints, unsigned int haystack_len = offset - header_length; bool found = false; - if (needle_len) + + if (needle_len) while (haystack_len >= needle_len) { /* Trick: we don't care about first endpoint's d value, so skip one * byte in comparison. This works because arc_encode() packs the @@ -298,8 +632,9 @@ glyphy_arc_list_encode_blob (const glyphy_arc_endpoint_t *endpoints, tex_data.resize (offset); offset = haystack - &tex_data[0]; } + - tex_data[row * grid_w + col] = arc_list_encode (offset, current_endpoints, side); + tex_data[row * grid_w + col] = arc_list_encode (num_group_1_arcs, offset, current_endpoints, side); offset = tex_data.size (); total_arcs += current_endpoints; diff --git a/src/glyphy-common.glsl b/src/glyphy-common.glsl index 2277bacd..8d361cac 100644 --- a/src/glyphy-common.glsl +++ b/src/glyphy-common.glsl @@ -65,6 +65,9 @@ struct glyphy_arc_list_t { * Will be zero if we're far away inside or outside, in which case side is set. * Will be -1 if this arc-list encodes a single line, in which case line_* are set. */ int num_endpoints; + + /* Number of endpoints corresponding to the arcs in the first set of contours in the list. */ + int first_contours_length; /* If num_endpoints is zero, this specifies whether we are inside (-1) * or outside (+1). Otherwise we're unsure (0). */ @@ -121,6 +124,13 @@ glyphy_tan2atan (float d) return 2. * d / (1. - d * d); } +/* returns sin (2 * atan (d)) */ +float +glyphy_sin2atan (float d) +{ + return 2. * d / (1. + d * d); +} + glyphy_arc_endpoint_t glyphy_arc_endpoint_decode (const vec4 v, ivec2 nominal_size) { @@ -142,6 +152,19 @@ glyphy_arc_center (glyphy_arc_t a) glyphy_perpendicular (a.p1 - a.p0) / (2. * glyphy_tan2atan (a.d)); } +float +glyphy_arc_radius (glyphy_arc_t a) +{ + return distance (a.p0, a.p1) / (2. * glyphy_sin2atan (a.d)); +} + +vec2 +glyphy_segment_normal (glyphy_arc_t a) +{ + float unit = -1.;//sign (a.d); + return vec2 (unit * (a.p0.y - a.p1.y), unit * (a.p1.x - a.p0.x)); +} + bool glyphy_arc_wedge_contains (const glyphy_arc_t a, const vec2 p) { @@ -179,6 +202,7 @@ glyphy_arc_wedge_signed_dist (const glyphy_arc_t a, const vec2 p) return sign (a.d) * (distance (a.p0, c) - distance (p, c)); } + float glyphy_arc_extended_dist (const glyphy_arc_t a, const vec2 p) { @@ -204,9 +228,11 @@ glyphy_arc_list_decode (const vec4 v, ivec2 nominal_size) glyphy_arc_list_t l; ivec4 iv = glyphy_vec4_to_bytes (v); l.side = 0; /* unsure */ - if (iv.r == 0) { /* arc-list encoded */ + l.first_contours_length = 0; + if (iv.r < 128) { /* arc-list encoded */ l.offset = (iv.g * 256) + iv.b; l.num_endpoints = iv.a; + l.first_contours_length = iv.r; if (l.num_endpoints == 255) { l.num_endpoints = 0; l.side = -1; diff --git a/src/glyphy-geometry.hh b/src/glyphy-geometry.hh index f78104ed..31c56ba4 100644 --- a/src/glyphy-geometry.hh +++ b/src/glyphy-geometry.hh @@ -137,6 +137,8 @@ struct Segment { inline double squared_distance_to_point (const Point &p) const; /* shortest distance squared from point to segment */ inline bool contains_in_span (const Point &p) const; /* is p in the stripe formed by sliding this segment? */ inline double max_distance_to_arc (const Arc &a) const; + inline bool contains_point (const Point &p) const; + inline Point intersects_segment (const Segment &s) const; Point p0; @@ -173,6 +175,8 @@ struct Arc { inline double distance_to_point (const Point &p) const; inline double squared_distance_to_point (const Point &p) const; inline double extended_dist (const Point &p) const; + inline Point intersects_arc (const Arc &a) const; + inline Point intersects_segment (const Segment &s) const; inline void extents (glyphy_extents_t &extents) const; @@ -372,9 +376,10 @@ inline const Point Line::operator+ (const Line &l) const { return Point ((c * l.n.dy - n.dy * l.c) / det, (n.dx * l.c - c * l.n.dx) / det); } + inline const SignedVector Line::operator- (const Point &p) const { double mag = -(n * Vector (p) - c) / n.len (); - return SignedVector (n.normalized () * mag, mag < 0); /******************************************************************************************* FIX. *************************************/ + return SignedVector (n.normalized () * mag, mag < 0); /******************************************************* FIX. *************************************/ } inline const SignedVector operator- (const Point &p, const Line &l) { @@ -392,7 +397,7 @@ inline const Vector Line::normal (void) const { /* Segment */ inline const SignedVector Segment::operator- (const Point &p) const { /* shortest vector from point to line */ - return p - Line (p1, p0); /************************************************************************************************** Should the order (p1, p0) depend on d?? ***********************/ + return p - Line (p1, p0); /*********************************************************************** Should the order (p1, p0) depend on d?? ***********************/ } /* Segment */ @@ -409,12 +414,12 @@ inline bool Segment::contains_in_span (const Point &p) const { // Check if z is between p0 and p1. if (fabs (p1.y - p0.y) > fabs (p1.x - p0.x)) { - return ((z.y - p0.y > 0 && p1.y - p0.y > z.y - p0.y) || - (z.y - p0.y < 0 && p1.y - p0.y < z.y - p0.y)); + return ((z.y - p0.y >= 0 && p1.y - p0.y >= z.y - p0.y) || + (z.y - p0.y <= 0 && p1.y - p0.y <= z.y - p0.y)); } else { - return ((0 < z.x - p0.x && z.x - p0.x < p1.x - p0.x) || - (0 > z.x - p0.x && z.x - p0.x > p1.x - p0.x)); + return ((0 <= z.x - p0.x && z.x - p0.x <= p1.x - p0.x) || + (0 >= z.x - p0.x && z.x - p0.x >= p1.x - p0.x)); } } @@ -453,6 +458,50 @@ inline double Segment::max_distance_to_arc (const Arc &a) const { return max_distance > fabs(a.distance_to_point(p1)) ? max_distance : fabs(a.distance_to_point(p1)) ; } +inline bool Segment::contains_point (const Point &p) const { + return (distance_to_point (p) == 0); +} + +inline Point Segment::intersects_segment (const Segment &s) const { + /* Can't make lines if segments are degenerate. Handle this case with triangle equality. */ + if (s.contains_point (p0)) // Includes case p0 == p1 + return p0; + if (contains_point (s.p0)) // Includes case s.p0 == s.p1 + return s.p0; + if (p0 == p1 || s.p0 == s.p1) + return Point (GLYPHY_INFINITY, GLYPHY_INFINITY); + + Line line1 (p0, p1); + Line line2 (s.p0, s.p1); + + /* If segments are parallel, we have another special case. */ + Vector normal = line1.normal (); + + if (normal * line2.normal ().perpendicular () == 0) { + + /* This is NOT a great check. However, it might do for now - maybe it's not necessary. */ + if (normal.dx * p0.x + normal.dy + p0.y != normal.dx + s.p0.x + normal.dy + s.p0.y) /* TODO: Can we write using dot product? */ + return Point (GLYPHY_INFINITY, GLYPHY_INFINITY); //false; + + /* Lines are coincident. */ + if (contains_point (s.p0)) + return s.p0; + if (contains_point (s.p1)) + return s.p1; + if (s.contains_point (p0)) + return p0; + if (s.contains_point (p1)) // Is this case necessary? I don't think so. + return p1; + return Point (GLYPHY_INFINITY, GLYPHY_INFINITY); //(contains_point (s.p0) || contains_point (s.p1) || s.contains_point (p0) || s.contains_point (p1)); + } + + + Point p = line1 + line2; + if (contains_in_span (p) && s.contains_in_span (p)) + return p; + return Point (GLYPHY_INFINITY, GLYPHY_INFINITY); //contains_in_span (p) && s.contains_in_span (p); + +} /* Arc */ @@ -531,6 +580,9 @@ inline Bezier Arc::approximate_bezier (double *error) const inline bool Arc::wedge_contains_point (const Point &p) const { // TODO this doesn't handle fabs(d) > 1. + if (p == p0 || p == p1) + return true; + Pair t = tangents (); return (p - p0) * t.first >= 0 && (p - p1) * t.second <= 0; @@ -582,6 +634,69 @@ inline double Arc::extended_dist (const Point &p) const { return (p - p1) * (pp - dp * d2).normalized (); } +/* TODO: Handle possibility that there are two intersection points. */ +inline Point Arc::intersects_segment (const Segment &s) const { + if (fabs(d) < 1e-5) { + Segment arc_segment (p0, p1); + return s.intersects_segment (arc_segment); + } + + if (s.contains_point (p0)) + return p0; + if (s.contains_point (p1)) + return p1; + + Point c1 = center (); + double a = (s.p0.x - s.p1.x) * (s.p0.x - s.p1.x) + (s.p0.y - s.p1.y) * (s.p0.y - s.p1.y); + double b = 2 * ((s.p1.x - s.p0.x) * (s.p0.x - c1.x) + (s.p1.y - s.p0.y) * (s.p0.y - c1.y)); + double c = (c1.x * c1.x) + (c1.y * c1.y) + (s.p0.x * s.p0.x) + (s.p0.y * s.p0.y) - 2 * (c1.x * s.p0.x + c1.y * s.p0.y) - radius () * radius (); + double disc = b * b - 4 * a * c; + if (disc < 0) + return Point (GLYPHY_INFINITY, GLYPHY_INFINITY); + + double u = /* W... get it?! */ (-1 * b + sqrt (disc)) / (2 * a); + + Point p (s.p0.x + u * (s.p1.x - s.p0.x), s.p0.y + u * (s.p1.y - s.p0.y)); + if ((0 <= u && u <= 1) && wedge_contains_point(p)) + return p; + + u = -1 * b / a - u; + p = Point (s.p0.x + u * (s.p1.x - s.p0.x), s.p0.y + u * (s.p1.y - s.p0.y)); + if ((0 <= u && u <= 1) && wedge_contains_point(p)) + return p; + + return Point (GLYPHY_INFINITY, GLYPHY_INFINITY); +} + +/* TODO: Handle possibility that there are two intersection points. */ +inline Point Arc::intersects_arc (const Arc &a) const { + + if (fabs (d) < 1e-5) { + Segment arc_segment (p0, p1); + return a.intersects_segment (arc_segment); + } + + if (fabs (a.d) < 1e-5) { + Segment arc_segment (a.p0, a.p1); + return intersects_segment (arc_segment); + } + + Point c1 = center (); + Point c2 = a.center (); + double d = c1.distance_to_point (c2); + double b = (radius () * radius () - a.radius () * a.radius () + d * d) / (2 * d); + double h = sqrt( radius () * radius () - b * b); + Point p (c1.x + b * (c2.x - c1.x) / d, c1.y + b * (c2.y - c1.y) / d); + Point p1 (p.x + h * (c2.y - c1.y) / d, p.y - h * (c2.x - c1.x) / d); + Point p2 (p.x - h * (c2.y - c1.y) / d, p.y + h * (c2.x - c1.x) / d); + + if (wedge_contains_point (p1) && a.wedge_contains_point (p1)) + return p1; + if (wedge_contains_point (p2) && a.wedge_contains_point (p2)) + return p2; + return Point (GLYPHY_INFINITY, GLYPHY_INFINITY); +} + inline void Arc::extents (glyphy_extents_t &extents) const { glyphy_extents_clear (&extents); glyphy_extents_add (&extents, &p0); diff --git a/src/glyphy-outline.cc b/src/glyphy-outline.cc index bc612e00..f9e9628d 100644 --- a/src/glyphy-outline.cc +++ b/src/glyphy-outline.cc @@ -126,7 +126,8 @@ is_zero (double v) return fabs (v) < GLYPHY_EPSILON; } -static bool +/*static bool*/ +glyphy_bool_t even_odd (const glyphy_arc_endpoint_t *c_endpoints, unsigned int num_c_endpoints, const glyphy_arc_endpoint_t *endpoints, @@ -192,6 +193,7 @@ even_odd (const glyphy_arc_endpoint_t *c_endpoints, */ const Point p = c_endpoints[0].p; +// printf("EVEN_ODD! p is (%f,%f).\n", p.x, p.y); double count = 0; Point p0 (0, 0); @@ -207,7 +209,7 @@ even_odd (const glyphy_arc_endpoint_t *c_endpoints, /* * Skip our own contour */ - if (&endpoint >= c_endpoints && &endpoint < c_endpoints + num_c_endpoints) + if (&endpoint >= c_endpoints && &endpoint < c_endpoints + num_c_endpoints) continue; /* End-point y's compared to the ref point; lt, eq, or gt */ @@ -217,6 +219,7 @@ even_odd (const glyphy_arc_endpoint_t *c_endpoints, if (is_zero (arc.d)) { /* Line */ + // printf(" Checking a line segment...\n"); if (!s0 || !s1) { @@ -241,15 +244,18 @@ even_odd (const glyphy_arc_endpoint_t *c_endpoints, if (x >= p.x - GLYPHY_EPSILON) continue; // Does not intersect halfline +// printf(" Crossing the line segment.\n"); count++; // Add one for full crossing continue; } else { /* Arc */ +// printf(" Checking an arc... (%f,%f) to (%f,%f) with %f.\n", arc.p0.x,arc.p0.y,arc.p1.x,arc.p1.y,arc.d); if (!s0 || !s1) { +// printf (" We hit a vertex.\n"); /* * Add +.5 / -.5 for each endpoint on the halfline, depending on * crossing direction. @@ -269,7 +275,13 @@ even_odd (const glyphy_arc_endpoint_t *c_endpoints, count += .5 * categorize (t.first.dy, 0); if (!s1 && arc.p1.x < p.x + GLYPHY_EPSILON) count += .5 * categorize (t.second.dy, 0); + +// printf(" Arc-specific execution status: %d, %d, %d, %d.\n", +// is_zero (t.first.dy) ? 1 : 0, is_zero (t.second.dy) ? 1 : 0, !s0 && arc.p0.x < p.x + GLYPHY_EPSILON ? 1 : 0, !s1 && arc.p1.x < p.x + GLYPHY_EPSILON ? 1 : 0); } + + + Point c = arc.center (); double r = arc.radius (); @@ -285,19 +297,23 @@ even_odd (const glyphy_arc_endpoint_t *c_endpoints, * ref point. */ Point pp[2] = { Point (c.x - dx, p.y), Point (c.x + dx, p.y) }; - +// printf(" Nontrivial arc... \n"); #define POINTS_EQ(a,b) (is_zero (a.x - b.x) && is_zero (a.y - b.y)) for (unsigned int i = 0; i < ARRAY_LENGTH (pp); i++) { /* Make sure we don't double-count endpoints that fall on the * halfline as we already accounted for those above */ if (!POINTS_EQ (pp[i], arc.p0) && !POINTS_EQ (pp[i], arc.p1) && - pp[i].x < p.x - GLYPHY_EPSILON && arc.wedge_contains_point (pp[i])) + pp[i].x < p.x - GLYPHY_EPSILON && arc.wedge_contains_point (pp[i])) { +// printf(" A cross! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); count++; // Add one for full crossing + } } #undef POINTS_EQ } } + +// printf ("Count is %d.\n", int (floor (count))); return !(int (floor (count)) & 1); } @@ -315,7 +331,6 @@ process_contour (glyphy_arc_endpoint_t *endpoints, * - Find the winding direction and even-odd number, * - If the two disagree, reverse the contour, inplace. */ - if (!num_endpoints) return false; @@ -339,6 +354,30 @@ process_contour (glyphy_arc_endpoint_t *endpoints, return false; } +/* Returns true if given arc intersects a previously seen contour. // *************************************** NOT USED. +Point +arc_intersects_contour (Arc &a, + glyphy_arc_endpoint_t *endpoints, + unsigned int start, + unsigned int end) +{ + for (unsigned int i = start; i < end; i++) { + const glyphy_arc_endpoint_t ethis = endpoints[i - 1]; + const glyphy_arc_endpoint_t enext = endpoints[i]; + Arc current_arc (ethis.p, enext.p, enext.d); + if (current_arc.intersects_arc (a) != Point (GLYPHY_INFINITY, GLYPHY_INFINITY)) { + printf("The arc [(%f,%f), (%f,%f), %f], center (%f,%f), radius=%f, on a previous contour ", current_arc.p0.x, current_arc.p0.y, current_arc.p1.x, current_arc.p1.y, current_arc.d, current_arc.center ().x, current_arc.center ().y, current_arc.radius ()); + return current_arc.intersects_arc (a); //true; + } + } + return Point (GLYPHY_INFINITY, GLYPHY_INFINITY);//false; +} */ + + + + + + /* Returns true if outline was modified */ glyphy_bool_t glyphy_outline_winding_from_even_odd (glyphy_arc_endpoint_t *endpoints, @@ -361,5 +400,13 @@ glyphy_outline_winding_from_even_odd (glyphy_arc_endpoint_t *endpoints, } } ret = ret | process_contour (endpoints + start, num_endpoints - start, endpoints, num_endpoints, bool (inverse)); + + //glyphy_arc_endpoint_t rearranged_endpoints [num_endpoints]; + //rearrange_contours (endpoints, num_endpoints, rearranged_endpoints); + return ret; } + + + + diff --git a/src/glyphy-sdf.glsl b/src/glyphy-sdf.glsl index 73ad26e0..db56ceed 100644 --- a/src/glyphy-sdf.glsl +++ b/src/glyphy-sdf.glsl @@ -51,37 +51,29 @@ glyphy_arc_list (vec2 p, ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) return glyphy_arc_list_decode (arc_list_data, nominal_size); } -float -glyphy_sdf (vec2 p, ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) -{ - glyphy_arc_list_t arc_list = glyphy_arc_list (p, nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS); - - /* Short-circuits */ - if (arc_list.num_endpoints == 0) { - /* far-away cell */ - return GLYPHY_INFINITY * float(arc_list.side); - } if (arc_list.num_endpoints == -1) { - /* single-line */ - float angle = arc_list.line_angle; - vec2 n = vec2 (cos (angle), sin (angle)); - return dot (p - (vec2(nominal_size) * .5), n) - arc_list.line_distance; - } +float find_min_dist (glyphy_arc_list_t arc_list, vec2 p, bool isContourGroup1, + ivec2 nominal_size, out vec2 min_dist_vector GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) +{ float side = float(arc_list.side); float min_dist = GLYPHY_INFINITY; - glyphy_arc_t closest_arc; - + glyphy_arc_t closest_arc; glyphy_arc_endpoint_t endpoint_prev, endpoint; - endpoint_prev = glyphy_arc_endpoint_decode (GLYPHY_SDF_TEXTURE1D (arc_list.offset), nominal_size); - for (int i = 1; i < GLYPHY_MAX_NUM_ENDPOINTS; i++) + + endpoint_prev = glyphy_arc_endpoint_decode (GLYPHY_SDF_TEXTURE1D + (arc_list.offset + (isContourGroup1 ? 0 : arc_list.first_contours_length)), nominal_size); + int start_index = (isContourGroup1 ? 1 : arc_list.first_contours_length + 1); /*TODO: Should we subtract 0? #uncertain */ + + for (int i = start_index; i < GLYPHY_MAX_NUM_ENDPOINTS; i++) { - if (i >= arc_list.num_endpoints) { + if (i >= arc_list.num_endpoints || (isContourGroup1 && i > arc_list.first_contours_length)) { break; } + endpoint = glyphy_arc_endpoint_decode (GLYPHY_SDF_TEXTURE1D (arc_list.offset + i), nominal_size); glyphy_arc_t a = glyphy_arc_t (endpoint_prev.p, endpoint.p, endpoint.d); endpoint_prev = endpoint; - if (glyphy_isinf (a.d)) continue; + if (glyphy_isinf (a.d)) continue; if (glyphy_arc_wedge_contains (a, p)) { @@ -89,14 +81,40 @@ glyphy_sdf (vec2 p, ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) float udist = abs (sdist) * (1. - GLYPHY_EPSILON); if (udist <= min_dist) { min_dist = udist; - side = sdist <= 0. ? -1. : +1.; + side = (sdist <= 0. ? -1. : +1.); + side = (float(arc_list.side) == -1. ? -1. : side); + + /* TODO: Handle case where d=0 (and a has no center). CHECK VECTOR DIRECTION (+/-) */ + if (a.d == 0.) { + min_dist_vector = udist * normalize (glyphy_segment_normal (a)); + if (distance (p + min_dist_vector, mix(a.p0, a.p1, 0.5)) >= distance (p - min_dist_vector, mix(a.p0, a.p1, 0.5))) + min_dist_vector = -1. * min_dist_vector; + /* TODO: please make glyphy_segment_normal point the correct way to begin with.. */ + } + else { + vec2 center = glyphy_arc_center (a); + min_dist_vector = udist * normalize (center - p); + /** Is there no better way to do this? + * I would like to have // if (distance (center, p) < glyphy_arc_radius (a)) // + * but that doesn't work. + */ + if (abs(glyphy_arc_wedge_signed_dist(a, p + min_dist_vector)) >= abs(glyphy_arc_wedge_signed_dist(a, p - min_dist_vector))) + min_dist_vector = -1. * min_dist_vector; + } } + } else { - float udist = min (distance (p, a.p0), distance (p, a.p1)); + float dist0 = distance (p, a.p0); + float dist1 = distance (p, a.p1); + float udist = min (dist0, dist1); if (udist < min_dist) { min_dist = udist; side = 0.; /* unsure */ closest_arc = a; + if (dist0 < dist1) + min_dist_vector = normalize (a.p0 - p) * udist ; + else + min_dist_vector = normalize (a.p1 - p) * udist ; } else if (side == 0. && udist == min_dist) { /* If this new distance is the same as the current minimum, * compare extended distances. Take the sign from the arc @@ -112,19 +130,90 @@ glyphy_sdf (vec2 p, ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) min_dist = abs (ext_dist); #endif side = sign (ext_dist); + + if (abs (old_ext_dist) < abs (new_ext_dist)) { + float dist0 = distance (p, a.p0); + float dist1 = distance (p, a.p1); + if (dist0 < dist1) + min_dist_vector = udist * normalize (a.p0 - p); + else + min_dist_vector = udist * normalize (a.p1 - p); + } } } - } - - if (side == 0.) { + + } + if (side == 0. && (isContourGroup1 && arc_list.first_contours_length > 0 || !isContourGroup1)) { // Technically speaking this should not happen, but it does. So try to fix it. float ext_dist = glyphy_arc_extended_dist (closest_arc, p); side = sign (ext_dist); + } + return min_dist * side; +} + + + + +float +glyphy_sdf (vec2 p, ivec2 nominal_size, out vec2 min_dist_vector GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) +{ + glyphy_arc_list_t arc_list = glyphy_arc_list (p, nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS); + + /* Short-circuits: 0 to 1 arcs near cell*/ + if (arc_list.num_endpoints == 0) { + /* far-away cell */ + min_dist_vector = vec2 (GLYPHY_INFINITY, GLYPHY_INFINITY); + return GLYPHY_INFINITY * float(arc_list.side); + } if (arc_list.num_endpoints == -1) { + /* single-line */ + float angle = arc_list.line_angle; + vec2 n = vec2 (cos (angle), sin (angle)); + float dist = dot (p - (vec2(nominal_size) * .5), n) - arc_list.line_distance; + min_dist_vector = -1. * n * dist; + return dist; } - return min_dist * side; + vec2 min_dist_vector2; + float min_dist_first = find_min_dist (arc_list, p, true, nominal_size, min_dist_vector GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS); + float min_dist_last = find_min_dist (arc_list, p, false, nominal_size, min_dist_vector2 GLYPHY_SDF_TEXTURE1D_EXTRA_ARGS); + float min_dist = abs (min_dist_first); + float min_dist2 = abs (min_dist_last); + float side = sign (min_dist_first); + float side2 = sign (min_dist_last); + + /** If the two minimum distances are the same, but the sides are different, don't anti-alias. + * We are fully contained in one contour region. + */ + if (glyphy_iszero (min_dist - min_dist2) && side * side2 == -1.) { + return -1. * GLYPHY_INFINITY; + } + + /* Update the distance to use as min_dist to outline, based on which contours we are in. */ + if (side == 0. || (side == 1. && side2 == -1.)) { + min_dist = min_dist2; + min_dist_vector = min_dist_vector2; + } + else if (side == 1. && side2 == 1.) { + if (min_dist2 < min_dist) { + min_dist = min_dist2; + min_dist_vector = min_dist_vector2; + } + } + else if (side == -1. && side2 == -1.) { + if (min_dist < min_dist2) { + min_dist = min_dist2; + min_dist_vector = min_dist_vector2; + } + } + /* Update side to reflect which side of the overall outline we are at: inside or outside the glyph. */ + if (side2 < 0. || side == 0.) { + side = side2; + } + + return min_dist * side; } + float glyphy_point_dist (vec2 p, ivec2 nominal_size GLYPHY_SDF_TEXTURE1D_EXTRA_DECLS) { diff --git a/src/glyphy.h b/src/glyphy.h index 7f94ed2c..217936ef 100644 --- a/src/glyphy.h +++ b/src/glyphy.h @@ -33,6 +33,7 @@ extern "C" { typedef int glyphy_bool_t; + typedef struct { double x; double y; @@ -271,6 +272,14 @@ glyphy_outline_winding_from_even_odd (glyphy_arc_endpoint_t *endpoints, glyphy_bool_t inverse); +/* Returns true if a half line from the contour in c_endpoints + * crosses an even number of features of other contours. + */ +glyphy_bool_t +even_odd (const glyphy_arc_endpoint_t *c_endpoints, + unsigned int num_c_endpoints, + const glyphy_arc_endpoint_t *endpoints, + unsigned int num_endpoints); /* * Encode an arc outline into binary blob for fast SDF calculation