#include "triangulator_stroker.h" #include #define PI 3.1415 #define CURVE_FLATNESS PI / 8 Triangulator_Stroker * triangulator_stroker_new(void) { Triangulator_Stroker *stroker = calloc(1, sizeof(Triangulator_Stroker)); stroker->vertices = eina_inarray_new(sizeof(float), 0); stroker->arc_pts = eina_inarray_new(sizeof(float), 0); stroker->miter_limit = 2; return stroker; } void triangulator_stroker_free(Triangulator_Stroker *stroker) { eina_inarray_free(stroker->vertices); eina_inarray_free(stroker->arc_pts); } void triangulator_stroker_stroke_set(Triangulator_Stroker *stroker, float width, Efl_Gfx_Cap cap_style, Efl_Gfx_Join join_style, Eina_Matrix3 *m) { float scale_factor = 1.0; if (m) { // get the minimum scale factor from matrix scale_factor = m->xx < m->yy ? m->xx : m->yy; } stroker->width = (width * scale_factor)/2; stroker->join_style = join_style; stroker->cap_style = cap_style; } // calculate the normal vector static void normal_vector(float x1, float y1, float x2, float y2, float width, float *nx, float *ny) { float pw; float dx = x2 - x1; float dy = y2 - y1; if (EINA_FLT_EQ(dx, 0)) pw = width / fabsf(dy); else if (EINA_FLT_EQ(dy, 0)) pw = width / fabsf(dx); else pw = width / sqrtf(dx*dx + dy*dy); *nx = -dy * pw; *ny = dx * pw; } // add a line segment static void add_line_segment(Triangulator_Stroker *stroker, float x, float y, float vx, float vy) { float *ptr; ptr = eina_inarray_grow(stroker->vertices, 4); ptr[0] = x + vx; ptr[1] = y + vy; ptr[2] = x - vx; ptr[3] = y - vy; } static void add_arc_points(Triangulator_Stroker *stroker, float cx, float cy, float from_x, float from_y, float to_x, float to_y) { float tmp_x, tmp_y, *ptr; float dx1 = from_x - cx; float dy1 = from_y - cy; float dx2 = to_x - cx; float dy2 = to_y - cy; int size; eina_inarray_resize(stroker->arc_pts, 0); #define ADD_NEW_POINT \ tmp_x = dx1 * stroker->cos_theta - dy1 * stroker->sin_theta; \ tmp_y = dx1 * stroker->sin_theta + dy1 * stroker->cos_theta; \ dx1 = tmp_x; \ dy1 = tmp_y; \ ptr = eina_inarray_grow(stroker->arc_pts, 2); \ ptr[0] = cx + dx1; \ ptr[1] = cy + dy1; // while more than 180 degrees left: while (dx1 * dy2 - dx2 * dy1 < 0) { ADD_NEW_POINT } // while more than 90 degrees left: while (dx1 * dx2 + dy1 * dy2 < 0) { ADD_NEW_POINT } // while more than 0 degrees left: while (dx1 * dy2 - dx2 * dy1 > 0) { ADD_NEW_POINT } // remove last point which was rotated beyond [to_x, to_y]. size = eina_inarray_count(stroker->arc_pts); if (size) eina_inarray_resize(stroker->arc_pts, size - 2); } static void move_to(Triangulator_Stroker *stroker, const double *pts) { float x2,y2, sx, sy, *ptr=NULL, *ptr1=NULL; int pts_count, arc_pts_count, front, end, i=0; Eina_Bool jump; stroker->cx = pts[0]; stroker->cy = pts[1]; x2 = pts[2]; y2 = pts[3]; normal_vector(stroker->cx, stroker->cy, x2, y2, stroker->width, &stroker->nvx, &stroker->nvy); // To acheive jumps we insert zero-area tringles. This is done by // adding two identical points in both the end of previous strip // and beginning of next strip jump = eina_inarray_count(stroker->vertices); switch (stroker->cap_style) { case EFL_GFX_CAP_BUTT: if (jump) { ptr = eina_inarray_grow(stroker->vertices, 2); ptr[0] = stroker->cx + stroker->nvx; ptr[1] = stroker->cy + stroker->nvy; } break; case EFL_GFX_CAP_SQUARE: { sx = stroker->cx - stroker->nvy; sy = stroker->cy + stroker->nvx; if (jump) { ptr = eina_inarray_grow(stroker->vertices, 2); ptr[0] = sx + stroker->nvx; ptr[1] = sy + stroker->nvy; } add_line_segment(stroker, sx, sy, stroker->nvx, stroker->nvy); break; } case EFL_GFX_CAP_ROUND: { add_arc_points(stroker, stroker->cx, stroker->cy, stroker->cx + stroker->nvx, stroker->cy + stroker->nvy, stroker->cx - stroker->nvx, stroker->cy - stroker->nvy); arc_pts_count = eina_inarray_count(stroker->arc_pts); front = 0; end = arc_pts_count / 2; if (arc_pts_count) { eina_inarray_grow(stroker->vertices, eina_inarray_count(stroker->arc_pts) + 2 * jump); pts_count = eina_inarray_count(stroker->vertices); ptr1 = eina_inarray_nth(stroker->arc_pts, 0); ptr = eina_inarray_nth(stroker->vertices, 0); i = pts_count; while (front != end) { ptr[--i] = ptr1[2 * end - 1]; ptr[--i] = ptr1[2 * end - 2]; --end; if (front == end) break; ptr[--i] = ptr1[2 * front + 1]; ptr[--i] = ptr1[2 * front + 0]; ++front; } if (jump) { ptr[i - 1] = ptr[i + 1]; ptr[i - 2] = ptr[i + 0]; } } break; } default: break; } add_line_segment(stroker, stroker->cx, stroker->cy, stroker->nvx, stroker->nvy); } static void line_to(Triangulator_Stroker *stroker, const double *pts) { add_line_segment(stroker, pts[0], pts[1], stroker->nvx, stroker->nvy); stroker->cx = pts[0]; stroker->cy = pts[1]; } static void cubic_to(Triangulator_Stroker *stroker, const double *pts) { Eina_Bezier b; float rad, vx, vy, cx, cy, threshold_minus_1, t; double bw, bh, x, y; int i, threshold; eina_bezier_values_set(&b, stroker->cx, stroker->cy, pts[0], pts[1], pts[2], pts[3], pts[4], pts[5]); eina_bezier_bounds_get(&b, NULL, NULL, &bw, &bh); rad = fmaxf(bw, bh); threshold = fminf(64, (rad + stroker->curvyness_add) * stroker->curvyness_mul); if (threshold < 4) threshold = 4; threshold_minus_1 = threshold - 1; cx = stroker->cx; cy = stroker->cy; for (i = 1; i < threshold; ++i) { t = i / threshold_minus_1; eina_bezier_point_at(&b, t, &x, &y); normal_vector(cx, cy, x, y, stroker->width, &vx, &vy); add_line_segment(stroker, x, y, vx, vy); cx = x; cy = y; } stroker->cx = cx; stroker->cy = cy; stroker->nvx = vx; stroker->nvy = vy; } static void add_join(Triangulator_Stroker *stroker, float x , float y) { int arc_pts_count, pts_count, i; float prev_nvx, prev_nvy, xprod, px, py, qx, qy, pu, qv, ix, iy, *ptr; // Creates a join to the next segment (cx, cy) -> (x, y) normal_vector(stroker->cx, stroker->cy, x, y, stroker->width, &stroker->nvx, &stroker->nvy); switch (stroker->join_style) { case EFL_GFX_JOIN_BEVEL: break; case EFL_GFX_JOIN_MITER: { // Find out on which side the join should be. pts_count = eina_inarray_count(stroker->vertices); ptr = eina_inarray_nth(stroker->vertices, pts_count - 2); prev_nvx = ptr[0] - stroker->cx; prev_nvy = ptr[1] - stroker->cy; xprod = prev_nvx * stroker->nvy - prev_nvy * stroker->nvx; // If the segments are parallel, use bevel join. if (xprod < 0.001) break; // Find the corners of the previous and next segment to join. if (xprod < 0) { ptr = eina_inarray_nth(stroker->vertices, pts_count - 2); px = ptr[0]; py = ptr[1]; qx = stroker->cx - stroker->nvx; qy = stroker->cy - stroker->nvy; } else { ptr = eina_inarray_nth(stroker->vertices, pts_count - 4); px = ptr[0]; py = ptr[1]; qx = stroker->cx + stroker->nvx; qy = stroker->cy - stroker->nvy; } // Find intersection point. pu = px * prev_nvx + py * prev_nvy; qv = qx * stroker->nvx + qy * stroker->nvy; ix = (stroker->nvx * pu - prev_nvy * qv) / xprod; iy = (prev_nvx * qv - stroker->nvx * pu) / xprod; // Check that the distance to the intersection point is less than the miter limit. if ((ix - px) * (ix - px) + (iy - py) * (iy - py) <= stroker->miter_limit * stroker->miter_limit) { ptr = eina_inarray_grow(stroker->vertices, 4); ptr[0] = ix; ptr[1] = iy; ptr[2] = ix; ptr[3] = iy; } break; } case EFL_GFX_JOIN_ROUND: { pts_count = eina_inarray_count(stroker->vertices); ptr = eina_inarray_nth(stroker->vertices, pts_count - 2); prev_nvx = ptr[0] - stroker->cx; prev_nvy = ptr[1] - stroker->cy; if (stroker->nvx * prev_nvx - stroker->nvy * prev_nvy < 0) { add_arc_points(stroker, 0, 0, stroker->nvx, stroker->nvy, -prev_nvx, -prev_nvy); arc_pts_count = eina_inarray_count(stroker->arc_pts); if (arc_pts_count) ptr = eina_inarray_nth(stroker->arc_pts, 0); for (i = arc_pts_count / 2; i > 0; --i) add_line_segment(stroker, stroker->cx, stroker->cy, ptr[2 * i - 2], ptr[2 * i - 1]); } else { add_arc_points(stroker, 0, 0, -prev_nvx, -prev_nvy, stroker->nvx, stroker->nvy); arc_pts_count = eina_inarray_count(stroker->arc_pts) / 2; if (arc_pts_count) ptr = eina_inarray_nth(stroker->arc_pts, 0); for (i = 0; i < arc_pts_count / 2; ++i) add_line_segment(stroker, stroker->cx, stroker->cy, ptr[2 * i + 0], ptr[2 * i + 1]); } break; } default: break; } add_line_segment(stroker, stroker->cx, stroker->cy, stroker->nvx, stroker->nvy); } static void end_cap(Triangulator_Stroker *stroker) { float *ptr, *ptr1; int front, end, pts_count, arc_pts_count, i; switch (stroker->cap_style) { case EFL_GFX_CAP_BUTT: break; case EFL_GFX_CAP_SQUARE: add_line_segment(stroker, stroker->cx + stroker->nvy, stroker->cy - stroker->nvx, stroker->nvx, stroker->nvy); break; case EFL_GFX_CAP_ROUND: { pts_count = eina_inarray_count(stroker->vertices); ptr = eina_inarray_nth(stroker->vertices, pts_count-4); add_arc_points(stroker, stroker->cx, stroker->cy, ptr[2], ptr[3], ptr[0], ptr[1]); arc_pts_count = eina_inarray_count(stroker->arc_pts); if (arc_pts_count) { ptr = eina_inarray_grow(stroker->vertices, arc_pts_count); ptr1 = eina_inarray_nth(stroker->arc_pts, 0); } front = 0; end = arc_pts_count / 2; i = 0; while (front != end) { ptr[i++] = ptr1[2 * end - 2]; ptr[i++] = ptr1[2 * end - 1]; --end; if (front == end) break; ptr[i++] = ptr1[2 * front + 0]; ptr[i++] = ptr1[2 * front + 1]; ++front; } break; } default: break; } } static void _end_cap_or_join_closed(Triangulator_Stroker *stroker, const double *start, Eina_Bool implicit_close, Eina_Bool ends_at_start) { int count; float x, y, *ptr; if (ends_at_start) { add_join(stroker, start[2], start[3]); } else if (implicit_close) { add_join(stroker, start[0], start[1]); line_to(stroker, start); add_join(stroker, start[2], start[3]); } else { end_cap(stroker); } // add the invisible triangle count = eina_inarray_count(stroker->vertices); ptr = eina_inarray_nth(stroker->vertices, 0); x = ptr[count-2]; y = ptr[count-1]; ptr = eina_inarray_grow(stroker->vertices, 2); ptr[0] = x; ptr[1] = y; } static inline void _skip_duplicate_points(const double **pts, const double *end_pts) { while ((*pts + 2) < end_pts && EINA_DBL_EQ((*pts)[0], (*pts)[2]) && EINA_DBL_EQ((*pts)[1], (*pts)[3])) { *pts += 2; } } static void _path_info_get(const Efl_Gfx_Path_Command *cmds, const double *pts, Eina_Bool *implicit_close, Eina_Bool *ends_at_start) { int i = 0; *implicit_close = EINA_FALSE; *ends_at_start = EINA_FALSE; for (++cmds; *cmds != EFL_GFX_PATH_COMMAND_TYPE_END ; ++cmds) { switch (*cmds) { case EFL_GFX_PATH_COMMAND_TYPE_LINE_TO: i += 2; break; case EFL_GFX_PATH_COMMAND_TYPE_CUBIC_TO: i += 6; break; case EFL_GFX_PATH_COMMAND_TYPE_CLOSE: // this path has a implicit close *implicit_close = EINA_TRUE; // fall through case EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO: if (EINA_FLT_EQ(pts[0], pts[i]) && EINA_FLT_EQ(pts[1], pts[i+1])) *ends_at_start = EINA_TRUE; return; default: break; } } // this path is the last path with out implicit close. *ends_at_start = EINA_FLT_EQ(pts[0], pts[i]) && EINA_FLT_EQ(pts[1], pts[i+1]); } void triangulator_stroker_process(Triangulator_Stroker *stroker, const Efl_Gfx_Path_Command *cmds, const double *pts, int cmd_count, int pt_count) { const double *end_pts = pts + pt_count; const double *start_pts = 0; Eina_Bool ends_at_start, implicit_close; Efl_Gfx_Cap cap; Efl_Gfx_Path_Command previous_type; if (cmd_count < 2) return; eina_inarray_resize(stroker->vertices, 0); stroker->curvyness_add = stroker->width; stroker->curvyness_mul = CURVE_FLATNESS; stroker->roundness = fmax(4, 2 * stroker->width * stroker->curvyness_mul); // Over this level of segmentation, there doesn't seem to be any // benefit, even for huge penWidth if (stroker->roundness > 24) stroker->roundness = 24; stroker->sin_theta = sinf(PI / stroker->roundness); stroker->cos_theta = cosf(PI / stroker->roundness); cap = stroker->cap_style; ends_at_start = EINA_FALSE; implicit_close = EINA_FALSE; previous_type = EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO; for (; *cmds != EFL_GFX_PATH_COMMAND_TYPE_END; cmds++) { switch (*cmds) { case EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO: { if (previous_type != EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO) _end_cap_or_join_closed(stroker, start_pts, implicit_close, ends_at_start); // get the sub path deatils like closed path or start at end info. _path_info_get(cmds, pts, &implicit_close, &ends_at_start); start_pts = pts; _skip_duplicate_points(&start_pts, end_pts); // Skip duplicates to find correct normal. if (start_pts + 2 >= end_pts) return; // Nothing to see here... if (ends_at_start || implicit_close) stroker->cap_style = EFL_GFX_CAP_BUTT; move_to(stroker, start_pts); stroker->cap_style = cap; previous_type = EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO; pts+=2; break; } case EFL_GFX_PATH_COMMAND_TYPE_LINE_TO: if (!EINA_FLT_EQ(stroker->cx, pts[0]) || !EINA_FLT_EQ(stroker->cy, (float)pts[1])) { if (previous_type != EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO) add_join(stroker, pts[0], pts[1]); line_to(stroker, pts); previous_type = EFL_GFX_PATH_COMMAND_TYPE_LINE_TO; } pts+=2; break; case EFL_GFX_PATH_COMMAND_TYPE_CUBIC_TO: if (!EINA_FLT_EQ(stroker->cx, pts[0]) || !EINA_FLT_EQ(stroker->cy, pts[1]) || !EINA_FLT_EQ(pts[0], pts[2]) || !EINA_FLT_EQ(pts[1], pts[3]) || !EINA_FLT_EQ(pts[2], pts[4]) || !EINA_FLT_EQ(pts[3], pts[5])) { if (!EINA_FLT_EQ(stroker->cx, pts[0]) || !EINA_FLT_EQ(stroker->cy, pts[1])) { if (previous_type != EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO) add_join(stroker, pts[0], pts[1]); } cubic_to(stroker, pts); previous_type = EFL_GFX_PATH_COMMAND_TYPE_CUBIC_TO; } pts+=6; break; default: break; } } if (previous_type != EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO) _end_cap_or_join_closed(stroker, start_pts, implicit_close, ends_at_start); }