/************************************************************************** * * Copyright 2009 VMware, Inc. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * **************************************************************************/ #include "path.h" #include "stroker.h" #include "polygon.h" #include "bezier.h" #include "matrix.h" #include "vg_context.h" #include "util_array.h" #include "arc.h" #include "path_utils.h" #include "paint.h" #include "shader.h" #include "util/u_memory.h" #include #define DEBUG_PATH 0 struct path { struct vg_object base; VGbitfield caps; VGboolean dirty; VGboolean dirty_stroke; VGPathDatatype datatype; VGfloat scale; VGfloat bias; VGint num_segments; struct array * segments; struct array * control_points; struct { struct polygon_array polygon_array; struct matrix matrix; } fill_polys; struct { struct path *path; struct matrix matrix; VGfloat stroke_width; VGfloat miter_limit; VGCapStyle cap_style; VGJoinStyle join_style; } stroked; }; static INLINE void data_at(void **data, struct path *p, VGint start, VGint count, VGfloat *out) { VGPathDatatype dt = p->datatype; VGint i; VGint end = start + count; VGfloat *itr = out; switch(dt) { case VG_PATH_DATATYPE_S_8: { VGbyte **bdata = (VGbyte **)data; for (i = start; i < end; ++i) { *itr = (*bdata)[i]; ++itr; } *bdata += count; } break; case VG_PATH_DATATYPE_S_16: { VGshort **bdata = (VGshort **)data; for (i = start; i < end; ++i) { *itr = (*bdata)[i]; ++itr; } *bdata += count; } break; case VG_PATH_DATATYPE_S_32: { VGint **bdata = (VGint **)data; for (i = start; i < end; ++i) { *itr = (*bdata)[i]; ++itr; } *bdata += count; } break; case VG_PATH_DATATYPE_F: { VGfloat **fdata = (VGfloat **)data; for (i = start; i < end; ++i) { *itr = (*fdata)[i]; ++itr; } *fdata += count; } break; default: debug_assert(!"Unknown path datatype!"); } } void vg_float_to_datatype(VGPathDatatype datatype, VGubyte *common_data, const VGfloat *data, VGint num_coords) { VGint i; switch(datatype) { case VG_PATH_DATATYPE_S_8: { for (i = 0; i < num_coords; ++i) { common_data[i] = (VGubyte)data[i]; } } break; case VG_PATH_DATATYPE_S_16: { VGshort *buf = (VGshort*)common_data; for (i = 0; i < num_coords; ++i) { buf[i] = (VGshort)data[i]; } } break; case VG_PATH_DATATYPE_S_32: { VGint *buf = (VGint*)common_data; for (i = 0; i < num_coords; ++i) { buf[i] = (VGint)data[i]; } } break; case VG_PATH_DATATYPE_F: { memcpy(common_data, data, sizeof(VGfloat) * num_coords); } break; default: debug_assert(!"Unknown path datatype!"); } } static void coords_adjust_by_scale_bias(struct path *p, void *pdata, VGint num_coords, VGfloat scale, VGfloat bias, VGPathDatatype datatype) { VGfloat data[8]; void *coords = (VGfloat *)pdata; VGubyte *common_data = (VGubyte *)pdata; VGint size_dst = size_for_datatype(datatype); VGint i; for (i = 0; i < num_coords; ++i) { data_at(&coords, p, 0, 1, data); data[0] = data[0] * scale + bias; vg_float_to_datatype(datatype, common_data, data, 1); common_data += size_dst; } } struct path * path_create(VGPathDatatype dt, VGfloat scale, VGfloat bias, VGint segmentCapacityHint, VGint coordCapacityHint, VGbitfield capabilities) { struct path *path = CALLOC_STRUCT(path); vg_init_object(&path->base, vg_current_context(), VG_OBJECT_PATH); path->caps = capabilities & VG_PATH_CAPABILITY_ALL; vg_context_add_object(vg_current_context(), VG_OBJECT_PATH, path); path->datatype = dt; path->scale = scale; path->bias = bias; path->segments = array_create(size_for_datatype(VG_PATH_DATATYPE_S_8)); path->control_points = array_create(size_for_datatype(dt)); path->dirty = VG_TRUE; path->dirty_stroke = VG_TRUE; return path; } static void polygon_array_cleanup(struct polygon_array *polyarray) { if (polyarray->array) { VGint i; for (i = 0; i < polyarray->array->num_elements; i++) { struct polygon *p = ((struct polygon **) polyarray->array->data)[i]; polygon_destroy(p); } array_destroy(polyarray->array); polyarray->array = NULL; } } void path_destroy(struct path *p) { vg_context_remove_object(vg_current_context(), VG_OBJECT_PATH, p); array_destroy(p->segments); array_destroy(p->control_points); polygon_array_cleanup(&p->fill_polys.polygon_array); if (p->stroked.path) path_destroy(p->stroked.path); FREE(p); } VGbitfield path_capabilities(struct path *p) { return p->caps; } void path_set_capabilities(struct path *p, VGbitfield bf) { p->caps = (bf & VG_PATH_CAPABILITY_ALL); } void path_append_data(struct path *p, VGint numSegments, const VGubyte * pathSegments, const void * pathData) { VGint old_segments = p->num_segments; VGint num_new_coords = num_elements_for_segments(pathSegments, numSegments); array_append_data(p->segments, pathSegments, numSegments); array_append_data(p->control_points, pathData, num_new_coords); p->num_segments += numSegments; if (!floatsEqual(p->scale, 1.f) || !floatsEqual(p->bias, 0.f)) { VGubyte *coords = (VGubyte*)p->control_points->data; coords_adjust_by_scale_bias(p, coords + old_segments * p->control_points->datatype_size, num_new_coords, p->scale, p->bias, p->datatype); } p->dirty = VG_TRUE; p->dirty_stroke = VG_TRUE; } VGint path_num_segments(struct path *p) { return p->num_segments; } static INLINE void map_if_relative(VGfloat ox, VGfloat oy, VGboolean relative, VGfloat *x, VGfloat *y) { if (relative) { if (x) *x += ox; if (y) *y += oy; } } static INLINE void close_polygon(struct polygon *current, VGfloat sx, VGfloat sy, VGfloat ox, VGfloat oy, struct matrix *matrix) { if (!floatsEqual(sx, ox) || !floatsEqual(sy, oy)) { VGfloat x0 = sx; VGfloat y0 = sy; matrix_map_point(matrix, x0, y0, &x0, &y0); polygon_vertex_append(current, x0, y0); } } static void convert_path(struct path *p, VGPathDatatype to, void *dst, VGint num_coords) { VGfloat data[8]; void *coords = (VGfloat *)p->control_points->data; VGubyte *common_data = (VGubyte *)dst; VGint size_dst = size_for_datatype(to); VGint i; for (i = 0; i < num_coords; ++i) { data_at(&coords, p, 0, 1, data); vg_float_to_datatype(to, common_data, data, 1); common_data += size_dst; } } static void polygon_array_calculate_bounds( struct polygon_array *polyarray ) { struct array *polys = polyarray->array; VGfloat min_x, max_x; VGfloat min_y, max_y; VGfloat bounds[4]; unsigned i; assert(polys); if (!polys->num_elements) { polyarray->min_x = 0.0f; polyarray->min_y = 0.0f; polyarray->max_x = 0.0f; polyarray->max_y = 0.0f; return; } polygon_bounding_rect((((struct polygon**)polys->data)[0]), bounds); min_x = bounds[0]; min_y = bounds[1]; max_x = bounds[0] + bounds[2]; max_y = bounds[1] + bounds[3]; for (i = 1; i < polys->num_elements; ++i) { struct polygon *p = (((struct polygon**)polys->data)[i]); polygon_bounding_rect(p, bounds); min_x = MIN2(min_x, bounds[0]); min_y = MIN2(min_y, bounds[1]); max_x = MAX2(max_x, bounds[0] + bounds[2]); max_y = MAX2(max_y, bounds[1] + bounds[3]); } polyarray->min_x = min_x; polyarray->min_y = min_y; polyarray->max_x = max_x; polyarray->max_y = max_y; } static struct polygon_array * path_get_fill_polygons(struct path *p, struct matrix *matrix) { VGint i; struct polygon *current = 0; VGfloat sx, sy, px, py, ox, oy; VGfloat x0, y0, x1, y1, x2, y2, x3, y3; VGfloat data[8]; void *coords = (VGfloat *)p->control_points->data; struct array *array; if (p->fill_polys.polygon_array.array) { if (memcmp( &p->fill_polys.matrix, matrix, sizeof *matrix ) == 0 && p->dirty == VG_FALSE) { return &p->fill_polys.polygon_array; } else { polygon_array_cleanup(&p->fill_polys.polygon_array); } } /* an array of pointers to polygons */ array = array_create(sizeof(struct polygon *)); sx = sy = px = py = ox = oy = 0.f; if (p->num_segments) current = polygon_create(32); for (i = 0; i < p->num_segments; ++i) { VGubyte segment = ((VGubyte*)(p->segments->data))[i]; VGint command = SEGMENT_COMMAND(segment); VGboolean relative = SEGMENT_ABS_REL(segment); switch(command) { case VG_CLOSE_PATH: close_polygon(current, sx, sy, ox, oy, matrix); ox = sx; oy = sy; break; case VG_MOVE_TO: if (current && polygon_vertex_count(current) > 0) { /* add polygon */ close_polygon(current, sx, sy, ox, oy, matrix); array_append_data(array, ¤t, 1); current = polygon_create(32); } data_at(&coords, p, 0, 2, data); x0 = data[0]; y0 = data[1]; map_if_relative(ox, oy, relative, &x0, &y0); sx = x0; sy = y0; ox = x0; oy = y0; px = x0; py = y0; matrix_map_point(matrix, x0, y0, &x0, &y0); polygon_vertex_append(current, x0, y0); break; case VG_LINE_TO: data_at(&coords, p, 0, 2, data); x0 = data[0]; y0 = data[1]; map_if_relative(ox, oy, relative, &x0, &y0); ox = x0; oy = y0; px = x0; py = y0; matrix_map_point(matrix, x0, y0, &x0, &y0); polygon_vertex_append(current, x0, y0); break; case VG_HLINE_TO: data_at(&coords, p, 0, 1, data); x0 = data[0]; y0 = oy; map_if_relative(ox, oy, relative, &x0, 0); ox = x0; px = x0; py = y0; matrix_map_point(matrix, x0, y0, &x0, &y0); polygon_vertex_append(current, x0, y0); break; case VG_VLINE_TO: data_at(&coords, p, 0, 1, data); x0 = ox; y0 = data[0]; map_if_relative(ox, oy, relative, 0, &y0); oy = y0; px = x0; py = y0; matrix_map_point(matrix, x0, y0, &x0, &y0); polygon_vertex_append(current, x0, y0); break; case VG_CUBIC_TO: { struct bezier bezier; data_at(&coords, p, 0, 6, data); x0 = ox; y0 = oy; x1 = data[0]; y1 = data[1]; x2 = data[2]; y2 = data[3]; x3 = data[4]; y3 = data[5]; map_if_relative(ox, oy, relative, &x1, &y1); map_if_relative(ox, oy, relative, &x2, &y2); map_if_relative(ox, oy, relative, &x3, &y3); ox = x3; oy = y3; px = x2; py = y2; assert(matrix_is_affine(matrix)); matrix_map_point(matrix, x0, y0, &x0, &y0); matrix_map_point(matrix, x1, y1, &x1, &y1); matrix_map_point(matrix, x2, y2, &x2, &y2); matrix_map_point(matrix, x3, y3, &x3, &y3); bezier_init(&bezier, x0, y0, x1, y1, x2, y2, x3, y3); bezier_add_to_polygon(&bezier, current); } break; case VG_QUAD_TO: { struct bezier bezier; data_at(&coords, p, 0, 4, data); x0 = ox; y0 = oy; x1 = data[0]; y1 = data[1]; x3 = data[2]; y3 = data[3]; map_if_relative(ox, oy, relative, &x1, &y1); map_if_relative(ox, oy, relative, &x3, &y3); px = x1; py = y1; { /* form a cubic out of it */ x2 = (x3 + 2*x1) / 3.f; y2 = (y3 + 2*y1) / 3.f; x1 = (x0 + 2*x1) / 3.f; y1 = (y0 + 2*y1) / 3.f; } ox = x3; oy = y3; assert(matrix_is_affine(matrix)); matrix_map_point(matrix, x0, y0, &x0, &y0); matrix_map_point(matrix, x1, y1, &x1, &y1); matrix_map_point(matrix, x2, y2, &x2, &y2); matrix_map_point(matrix, x3, y3, &x3, &y3); bezier_init(&bezier, x0, y0, x1, y1, x2, y2, x3, y3); bezier_add_to_polygon(&bezier, current); } break; case VG_SQUAD_TO: { struct bezier bezier; data_at(&coords, p, 0, 2, data); x0 = ox; y0 = oy; x1 = 2*ox-px; y1 = 2*oy-py; x3 = data[0]; y3 = data[1]; map_if_relative(ox, oy, relative, &x3, &y3); px = x1; py = y1; { /* form a cubic out of it */ x2 = (x3 + 2*x1) / 3.f; y2 = (y3 + 2*y1) / 3.f; x1 = (x0 + 2*x1) / 3.f; y1 = (y0 + 2*y1) / 3.f; } ox = x3; oy = y3; assert(matrix_is_affine(matrix)); matrix_map_point(matrix, x0, y0, &x0, &y0); matrix_map_point(matrix, x1, y1, &x1, &y1); matrix_map_point(matrix, x2, y2, &x2, &y2); matrix_map_point(matrix, x3, y3, &x3, &y3); bezier_init(&bezier, x0, y0, x1, y1, x2, y2, x3, y3); bezier_add_to_polygon(&bezier, current); } break; case VG_SCUBIC_TO: { struct bezier bezier; data_at(&coords, p, 0, 4, data); x0 = ox; y0 = oy; x1 = 2*ox-px; y1 = 2*oy-py; x2 = data[0]; y2 = data[1]; x3 = data[2]; y3 = data[3]; map_if_relative(ox, oy, relative, &x2, &y2); map_if_relative(ox, oy, relative, &x3, &y3); ox = x3; oy = y3; px = x2; py = y2; assert(matrix_is_affine(matrix)); matrix_map_point(matrix, x0, y0, &x0, &y0); matrix_map_point(matrix, x1, y1, &x1, &y1); matrix_map_point(matrix, x2, y2, &x2, &y2); matrix_map_point(matrix, x3, y3, &x3, &y3); bezier_init(&bezier, x0, y0, x1, y1, x2, y2, x3, y3); bezier_add_to_polygon(&bezier, current); } break; case VG_SCCWARC_TO: case VG_SCWARC_TO: case VG_LCCWARC_TO: case VG_LCWARC_TO: { VGfloat rh, rv, rot; struct arc arc; data_at(&coords, p, 0, 5, data); x0 = ox; y0 = oy; rh = data[0]; rv = data[1]; rot = data[2]; x1 = data[3]; y1 = data[4]; map_if_relative(ox, oy, relative, &x1, &y1); #if 0 debug_printf("------- ARC (%f, %f), (%f, %f) %f, %f, %f\n", x0, y0, x1, y1, rh, rv, rot); #endif arc_init(&arc, command, x0, y0, x1, y1, rh, rv, rot); arc_add_to_polygon(&arc, current, matrix); ox = x1; oy = y1; px = x1; py = y1; } break; default: abort(); assert(!"Unknown segment!"); } } if (current) { if (polygon_vertex_count(current) > 0) { close_polygon(current, sx, sy, ox, oy, matrix); array_append_data(array, ¤t, 1); } else polygon_destroy(current); } p->fill_polys.polygon_array.array = array; p->fill_polys.matrix = *matrix; polygon_array_calculate_bounds( &p->fill_polys.polygon_array ); p->dirty = VG_FALSE; return &p->fill_polys.polygon_array; } VGbyte path_datatype_size(struct path *p) { return size_for_datatype(p->datatype); } VGPathDatatype path_datatype(struct path *p) { return p->datatype; } VGfloat path_scale(struct path *p) { return p->scale; } VGfloat path_bias(struct path *p) { return p->bias; } VGint path_num_coords(struct path *p) { return num_elements_for_segments((VGubyte*)p->segments->data, p->num_segments); } void path_modify_coords(struct path *p, VGint startIndex, VGint numSegments, const void * pathData) { VGubyte *segments = (VGubyte*)(p->segments->data); VGint count = num_elements_for_segments(&segments[startIndex], numSegments); VGint start_cp = num_elements_for_segments(segments, startIndex); array_change_data(p->control_points, pathData, start_cp, count); coords_adjust_by_scale_bias(p, ((VGubyte*)p->control_points->data) + (startIndex * p->control_points->datatype_size), path_num_coords(p), p->scale, p->bias, p->datatype); p->dirty = VG_TRUE; p->dirty_stroke = VG_TRUE; } void path_for_each_segment(struct path *path, path_for_each_cb cb, void *user_data) { VGint i; struct path_for_each_data p; VGfloat data[8]; void *coords = (VGfloat *)path->control_points->data; p.coords = data; p.sx = p.sy = p.px = p.py = p.ox = p.oy = 0.f; p.user_data = user_data; for (i = 0; i < path->num_segments; ++i) { VGint command; VGboolean relative; p.segment = ((VGubyte*)(path->segments->data))[i]; command = SEGMENT_COMMAND(p.segment); relative = SEGMENT_ABS_REL(p.segment); switch(command) { case VG_CLOSE_PATH: cb(path, &p); break; case VG_MOVE_TO: data_at(&coords, path, 0, 2, data); map_if_relative(p.ox, p.oy, relative, &data[0], &data[1]); cb(path, &p); p.sx = data[0]; p.sy = data[1]; p.ox = data[0]; p.oy = data[1]; p.px = data[0]; p.py = data[1]; break; case VG_LINE_TO: data_at(&coords, path, 0, 2, data); map_if_relative(p.ox, p.oy, relative, &data[0], &data[1]); cb(path, &p); p.ox = data[0]; p.oy = data[1]; p.px = data[0]; p.py = data[1]; break; case VG_HLINE_TO: data_at(&coords, path, 0, 1, data); map_if_relative(p.ox, p.oy, relative, &data[0], 0); p.segment = VG_LINE_TO; data[1] = p.oy; cb(path, &p); p.ox = data[0]; p.oy = data[1]; p.px = data[0]; p.py = data[1]; break; case VG_VLINE_TO: data_at(&coords, path, 0, 1, data); map_if_relative(p.ox, p.oy, relative, 0, &data[0]); p.segment = VG_LINE_TO; data[1] = data[0]; data[0] = p.ox; cb(path, &p); p.ox = data[0]; p.oy = data[1]; p.px = data[0]; p.py = data[1]; break; case VG_CUBIC_TO: { data_at(&coords, path, 0, 6, data); map_if_relative(p.ox, p.oy, relative, &data[0], &data[1]); map_if_relative(p.ox, p.oy, relative, &data[2], &data[3]); map_if_relative(p.ox, p.oy, relative, &data[4], &data[5]); cb(path, &p); p.px = data[2]; p.py = data[3]; p.ox = data[4]; p.oy = data[5]; } break; case VG_QUAD_TO: { data_at(&coords, path, 0, 4, data); map_if_relative(p.ox, p.oy, relative, &data[0], &data[1]); map_if_relative(p.ox, p.oy, relative, &data[2], &data[3]); cb(path, &p); p.px = data[0]; p.py = data[1]; p.ox = data[2]; p.oy = data[3]; } break; case VG_SQUAD_TO: { data_at(&coords, path, 0, 2, data); map_if_relative(p.ox, p.oy, relative, &data[0], &data[1]); cb(path, &p); p.px = 2*p.ox-p.px; p.py = 2*p.oy-p.py; p.ox = data[2]; p.oy = data[3]; } break; case VG_SCUBIC_TO: { data_at(&coords, path, 0, 4, data); map_if_relative(p.ox, p.oy, relative, &data[0], &data[1]); map_if_relative(p.ox, p.oy, relative, &data[2], &data[3]); cb(path, &p); p.px = data[0]; p.py = data[1]; p.ox = data[2]; p.oy = data[3]; } break; case VG_SCCWARC_TO: case VG_SCWARC_TO: case VG_LCCWARC_TO: case VG_LCWARC_TO: { data_at(&coords, path, 0, 5, data); map_if_relative(p.ox, p.oy, relative, &data[3], &data[4]); #if 0 debug_printf("------- ARC (%f, %f), (%f, %f) %f, %f, %f\n", p.ox, p.oy, data[3], data[4], data[0], data[1], data[2]); #endif cb(path, &p); p.ox = data[3]; p.oy = data[4]; p.px = data[3]; p.py = data[4]; } break; default: abort(); assert(!"Unknown segment!"); } } } struct transform_data { struct array *segments; struct array *coords; struct matrix *matrix; VGPathDatatype datatype; }; static VGboolean transform_cb(struct path *p, struct path_for_each_data *pd) { struct transform_data *td = (struct transform_data *)pd->user_data; VGint num_coords = num_elements_for_segments(&pd->segment, 1); VGubyte segment = SEGMENT_COMMAND(pd->segment);/* abs bit is 0 */ VGfloat data[8]; VGubyte common_data[sizeof(VGfloat)*8]; memcpy(data, pd->coords, sizeof(VGfloat) * num_coords); switch(segment) { case VG_CLOSE_PATH: break; case VG_MOVE_TO: matrix_map_point(td->matrix, data[0], data[1], &data[0], &data[1]); break; case VG_LINE_TO: matrix_map_point(td->matrix, data[0], data[1], &data[0], &data[1]); break; case VG_HLINE_TO: case VG_VLINE_TO: assert(0); break; case VG_QUAD_TO: matrix_map_point(td->matrix, data[0], data[1], &data[0], &data[1]); matrix_map_point(td->matrix, data[2], data[3], &data[2], &data[3]); break; case VG_CUBIC_TO: matrix_map_point(td->matrix, data[0], data[1], &data[0], &data[1]); matrix_map_point(td->matrix, data[2], data[3], &data[2], &data[3]); matrix_map_point(td->matrix, data[4], data[5], &data[4], &data[5]); break; case VG_SQUAD_TO: matrix_map_point(td->matrix, data[0], data[1], &data[0], &data[1]); break; case VG_SCUBIC_TO: matrix_map_point(td->matrix, data[0], data[1], &data[0], &data[1]); matrix_map_point(td->matrix, data[2], data[3], &data[2], &data[3]); break; case VG_SCCWARC_TO: case VG_SCWARC_TO: case VG_LCCWARC_TO: case VG_LCWARC_TO: { struct arc arc; struct path *path = path_create(td->datatype, 1, 0, 0, 0, VG_PATH_CAPABILITY_ALL); arc_init(&arc, segment, pd->ox, pd->oy, data[3], data[4], data[0], data[1], data[2]); arc_to_path(&arc, path, td->matrix); num_coords = path_num_coords(path); array_append_data(td->segments, path->segments->data, path->num_segments); array_append_data(td->coords, path->control_points->data, num_coords); path_destroy(path); return VG_TRUE; } break; default: break; } vg_float_to_datatype(td->datatype, common_data, data, num_coords); array_append_data(td->segments, &pd->segment, 1); array_append_data(td->coords, common_data, num_coords); return VG_TRUE; } void path_transform(struct path *dst, struct path *src) { struct transform_data data; struct vg_context *ctx = dst->base.ctx; data.segments = dst->segments; data.coords = dst->control_points; data.matrix = &ctx->state.vg.path_user_to_surface_matrix; data.datatype = dst->datatype; path_for_each_segment(src, transform_cb, (void*)&data); dst->num_segments = dst->segments->num_elements; dst->dirty = VG_TRUE; dst->dirty_stroke = VG_TRUE; } void path_append_path(struct path *dst, struct path *src) { VGint num_coords = path_num_coords(src); void *dst_data = malloc(size_for_datatype(dst->datatype) * num_coords); array_append_data(dst->segments, src->segments->data, src->num_segments); convert_path(src, dst->datatype, dst_data, num_coords); array_append_data(dst->control_points, dst_data, num_coords); free(dst_data); dst->num_segments += src->num_segments; dst->dirty = VG_TRUE; dst->dirty_stroke = VG_TRUE; } static INLINE VGboolean is_segment_arc(VGubyte segment) { VGubyte scommand = SEGMENT_COMMAND(segment); return (scommand == VG_SCCWARC_TO || scommand == VG_SCWARC_TO || scommand == VG_LCCWARC_TO || scommand == VG_LCWARC_TO); } struct path_iter_data { struct path *path; VGubyte segment; void *coords; VGfloat px, py, ox, oy, sx, sy; }; static INLINE VGubyte normalize_coords(struct path_iter_data *pd, VGint *num_coords, VGfloat *data) { VGint command = SEGMENT_COMMAND(pd->segment); VGboolean relative = SEGMENT_ABS_REL(pd->segment); switch(command) { case VG_CLOSE_PATH: *num_coords = 0; pd->ox = pd->sx; pd->oy = pd->sy; return VG_CLOSE_PATH; break; case VG_MOVE_TO: data_at(&pd->coords, pd->path, 0, 2, data); map_if_relative(pd->ox, pd->oy, relative, &data[0], &data[1]); pd->sx = data[0]; pd->sy = data[1]; pd->ox = data[0]; pd->oy = data[1]; pd->px = data[0]; pd->py = data[1]; *num_coords = 2; return VG_MOVE_TO_ABS; break; case VG_LINE_TO: data_at(&pd->coords, pd->path, 0, 2, data); map_if_relative(pd->ox, pd->oy, relative, &data[0], &data[1]); pd->ox = data[0]; pd->oy = data[1]; pd->px = data[0]; pd->py = data[1]; *num_coords = 2; return VG_LINE_TO_ABS; break; case VG_HLINE_TO: data_at(&pd->coords, pd->path, 0, 1, data); map_if_relative(pd->ox, pd->oy, relative, &data[0], 0); data[1] = pd->oy; pd->ox = data[0]; pd->oy = data[1]; pd->px = data[0]; pd->py = data[1]; *num_coords = 2; return VG_LINE_TO_ABS; break; case VG_VLINE_TO: data_at(&pd->coords, pd->path, 0, 1, data); map_if_relative(pd->ox, pd->oy, relative, 0, &data[0]); data[1] = data[0]; data[0] = pd->ox; pd->ox = data[0]; pd->oy = data[1]; pd->px = data[0]; pd->py = data[1]; *num_coords = 2; return VG_LINE_TO_ABS; break; case VG_CUBIC_TO: { data_at(&pd->coords, pd->path, 0, 6, data); map_if_relative(pd->ox, pd->oy, relative, &data[0], &data[1]); map_if_relative(pd->ox, pd->oy, relative, &data[2], &data[3]); map_if_relative(pd->ox, pd->oy, relative, &data[4], &data[5]); pd->px = data[2]; pd->py = data[3]; pd->ox = data[4]; pd->oy = data[5]; *num_coords = 6; return VG_CUBIC_TO_ABS; } break; case VG_QUAD_TO: { VGfloat x0, y0, x1, y1, x2, y2, x3, y3; data_at(&pd->coords, pd->path, 0, 4, data); x0 = pd->ox; y0 = pd->oy; x1 = data[0]; y1 = data[1]; x3 = data[2]; y3 = data[3]; map_if_relative(pd->ox, pd->oy, relative, &x1, &y1); map_if_relative(pd->ox, pd->oy, relative, &x3, &y3); pd->px = x1; pd->py = y1; { /* form a cubic out of it */ x2 = (x3 + 2*x1) / 3.f; y2 = (y3 + 2*y1) / 3.f; x1 = (x0 + 2*x1) / 3.f; y1 = (y0 + 2*y1) / 3.f; } pd->ox = x3; pd->oy = y3; data[0] = x1; data[1] = y1; data[2] = x2; data[3] = y2; data[4] = x3; data[5] = y3; *num_coords = 6; return VG_CUBIC_TO_ABS; } break; case VG_SQUAD_TO: { VGfloat x0, y0, x1, y1, x2, y2, x3, y3; data_at(&pd->coords, pd->path, 0, 2, data); x0 = pd->ox; y0 = pd->oy; x1 = 2 * pd->ox - pd->px; y1 = 2 * pd->oy - pd->py; x3 = data[0]; y3 = data[1]; map_if_relative(pd->ox, pd->oy, relative, &x3, &y3); pd->px = x1; pd->py = y1; { /* form a cubic out of it */ x2 = (x3 + 2*x1) / 3.f; y2 = (y3 + 2*y1) / 3.f; x1 = (x0 + 2*x1) / 3.f; y1 = (y0 + 2*y1) / 3.f; } pd->ox = x3; pd->oy = y3; data[0] = x1; data[1] = y1; data[2] = x2; data[3] = y2; data[4] = x3; data[5] = y3; *num_coords = 6; return VG_CUBIC_TO_ABS; } break; case VG_SCUBIC_TO: { VGfloat x0, y0, x1, y1, x2, y2, x3, y3; data_at(&pd->coords, pd->path, 0, 4, data); x0 = pd->ox; y0 = pd->oy; x1 = 2*pd->ox-pd->px; y1 = 2*pd->oy-pd->py; x2 = data[0]; y2 = data[1]; x3 = data[2]; y3 = data[3]; map_if_relative(pd->ox, pd->oy, relative, &x2, &y2); map_if_relative(pd->ox, pd->oy, relative, &x3, &y3); pd->ox = x3; pd->oy = y3; pd->px = x2; pd->py = y2; data[0] = x1; data[1] = y1; data[2] = x2; data[3] = y2; data[4] = x3; data[5] = y3; *num_coords = 6; return VG_CUBIC_TO_ABS; } break; case VG_SCCWARC_TO: case VG_SCWARC_TO: case VG_LCCWARC_TO: case VG_LCWARC_TO: { data_at(&pd->coords, pd->path, 0, 5, data); map_if_relative(pd->ox, pd->oy, relative, &data[3], &data[4]); pd->ox = data[3]; pd->oy = data[4]; pd->px = data[3]; pd->py = data[4]; *num_coords = 5; return command | VG_ABSOLUTE; } break; default: abort(); assert(!"Unknown segment!"); } } static void linearly_interpolate(VGfloat *result, const VGfloat *start, const VGfloat *end, VGfloat amount, VGint number) { VGint i; for (i = 0; i < number; ++i) { result[i] = start[i] + (end[i] - start[i]) * amount; } } VGboolean path_interpolate(struct path *dst, struct path *start, struct path *end, VGfloat amount) { /* temporary path that we can discard if it will turn * out that start is not compatible with end */ struct path *res_path = path_create(dst->datatype, 1.0, 0.0, 0, 0, dst->caps); VGint i; VGfloat start_coords[8]; VGfloat end_coords[8]; VGfloat results[8]; VGubyte common_data[sizeof(VGfloat)*8]; struct path_iter_data start_iter, end_iter; memset(&start_iter, 0, sizeof(struct path_iter_data)); memset(&end_iter, 0, sizeof(struct path_iter_data)); start_iter.path = start; start_iter.coords = start->control_points->data; end_iter.path = end; end_iter.coords = end->control_points->data; for (i = 0; i < start->num_segments; ++i) { VGubyte segment; VGubyte ssegment, esegment; VGint snum_coords, enum_coords; start_iter.segment = ((VGubyte*)(start->segments->data))[i]; end_iter.segment = ((VGubyte*)(end->segments->data))[i]; ssegment = normalize_coords(&start_iter, &snum_coords, start_coords); esegment = normalize_coords(&end_iter, &enum_coords, end_coords); if (is_segment_arc(ssegment)) { if (!is_segment_arc(esegment)) { path_destroy(res_path); return VG_FALSE; } if (amount > 0.5) segment = esegment; else segment = ssegment; } else if (is_segment_arc(esegment)) { path_destroy(res_path); return VG_FALSE; } else if (ssegment != esegment) { path_destroy(res_path); return VG_FALSE; } else segment = ssegment; linearly_interpolate(results, start_coords, end_coords, amount, snum_coords); vg_float_to_datatype(dst->datatype, common_data, results, snum_coords); path_append_data(res_path, 1, &segment, common_data); } path_append_path(dst, res_path); path_destroy(res_path); dst->dirty = VG_TRUE; dst->dirty_stroke = VG_TRUE; return VG_TRUE; } void path_clear(struct path *p, VGbitfield capabilities) { path_set_capabilities(p, capabilities); array_destroy(p->segments); array_destroy(p->control_points); p->segments = array_create(size_for_datatype(VG_PATH_DATATYPE_S_8)); p->control_points = array_create(size_for_datatype(p->datatype)); p->num_segments = 0; p->dirty = VG_TRUE; p->dirty_stroke = VG_TRUE; } struct path * path_create_stroke(struct path *p, struct matrix *matrix) { VGint i; VGfloat sx, sy, px, py, ox, oy; VGfloat x0, y0, x1, y1, x2, y2, x3, y3; VGfloat data[8]; void *coords = (VGfloat *)p->control_points->data; int dashed = (p->base.ctx->state.vg.stroke.dash_pattern_num ? 1 : 0); struct dash_stroker stroker; struct vg_state *vg_state = &p->base.ctx->state.vg; if (p->stroked.path) { /* ### compare the dash patterns to see if we can cache them. * for now we simply always bail out if the path is dashed. */ if (memcmp( &p->stroked.matrix, matrix, sizeof *matrix ) == 0 && !dashed && !p->dirty_stroke && floatsEqual(p->stroked.stroke_width, vg_state->stroke.line_width.f) && floatsEqual(p->stroked.miter_limit, vg_state->stroke.miter_limit.f) && p->stroked.cap_style == vg_state->stroke.cap_style && p->stroked.join_style == vg_state->stroke.join_style) { return p->stroked.path; } else { path_destroy( p->stroked.path ); p->stroked.path = NULL; } } sx = sy = px = py = ox = oy = 0.f; if (dashed) dash_stroker_init((struct stroker *)&stroker, vg_state); else stroker_init((struct stroker *)&stroker, vg_state); stroker_begin((struct stroker *)&stroker); for (i = 0; i < p->num_segments; ++i) { VGubyte segment = ((VGubyte*)(p->segments->data))[i]; VGint command = SEGMENT_COMMAND(segment); VGboolean relative = SEGMENT_ABS_REL(segment); switch(command) { case VG_CLOSE_PATH: { VGfloat x0 = sx; VGfloat y0 = sy; matrix_map_point(matrix, x0, y0, &x0, &y0); stroker_line_to((struct stroker *)&stroker, x0, y0); } break; case VG_MOVE_TO: data_at(&coords, p, 0, 2, data); x0 = data[0]; y0 = data[1]; map_if_relative(ox, oy, relative, &x0, &y0); sx = x0; sy = y0; ox = x0; oy = y0; px = x0; py = y0; matrix_map_point(matrix, x0, y0, &x0, &y0); stroker_move_to((struct stroker *)&stroker, x0, y0); break; case VG_LINE_TO: data_at(&coords, p, 0, 2, data); x0 = data[0]; y0 = data[1]; map_if_relative(ox, oy, relative, &x0, &y0); ox = x0; oy = y0; px = x0; py = y0; matrix_map_point(matrix, x0, y0, &x0, &y0); stroker_line_to((struct stroker *)&stroker, x0, y0); break; case VG_HLINE_TO: data_at(&coords, p, 0, 1, data); x0 = data[0]; y0 = oy; map_if_relative(ox, oy, relative, &x0, 0); ox = x0; px = x0; py = y0; matrix_map_point(matrix, x0, y0, &x0, &y0); stroker_line_to((struct stroker *)&stroker, x0, y0); break; case VG_VLINE_TO: data_at(&coords, p, 0, 1, data); x0 = ox; y0 = data[0]; map_if_relative(ox, oy, relative, 0, &y0); oy = y0; px = x0; py = y0; matrix_map_point(matrix, x0, y0, &x0, &y0); stroker_line_to((struct stroker *)&stroker, x0, y0); break; case VG_CUBIC_TO: { data_at(&coords, p, 0, 6, data); x0 = ox; y0 = oy; x1 = data[0]; y1 = data[1]; x2 = data[2]; y2 = data[3]; x3 = data[4]; y3 = data[5]; map_if_relative(ox, oy, relative, &x1, &y1); map_if_relative(ox, oy, relative, &x2, &y2); map_if_relative(ox, oy, relative, &x3, &y3); if (floatsEqual(x1, ox) && floatsEqual(y1, oy) && floatsEqual(x1, x2) && floatsEqual(y1, y2) && floatsEqual(x2, x3) && floatsEqual(y2, y3)) { /*ignore the empty segment */ continue; } else if (floatsEqual(x3, ox) && floatsEqual(y3, oy)) { /* if dup vertex, emit a line */ ox = x3; oy = y3; matrix_map_point(matrix, x3, y3, &x3, &y3); stroker_line_to((struct stroker *)&stroker, x3, y3); continue; } ox = x3; oy = y3; px = x2; py = y2; assert(matrix_is_affine(matrix)); matrix_map_point(matrix, x0, y0, &x0, &y0); matrix_map_point(matrix, x1, y1, &x1, &y1); matrix_map_point(matrix, x2, y2, &x2, &y2); matrix_map_point(matrix, x3, y3, &x3, &y3); stroker_curve_to((struct stroker *)&stroker, x1, y1, x2, y2, x3, y3); } break; case VG_QUAD_TO: { data_at(&coords, p, 0, 4, data); x0 = ox; y0 = oy; x1 = data[0]; y1 = data[1]; x3 = data[2]; y3 = data[3]; map_if_relative(ox, oy, relative, &x1, &y1); map_if_relative(ox, oy, relative, &x3, &y3); px = x1; py = y1; { /* form a cubic out of it */ x2 = (x3 + 2*x1) / 3.f; y2 = (y3 + 2*y1) / 3.f; x1 = (x0 + 2*x1) / 3.f; y1 = (y0 + 2*y1) / 3.f; } if (floatsEqual(x1, ox) && floatsEqual(y1, oy) && floatsEqual(x1, x2) && floatsEqual(y1, y2) && floatsEqual(x2, x3) && floatsEqual(y2, y3)) { /*ignore the empty segment */ continue; } else if (floatsEqual(x3, ox) && floatsEqual(y3, oy)) { /* if dup vertex, emit a line */ ox = x3; oy = y3; matrix_map_point(matrix, x3, y3, &x3, &y3); stroker_line_to((struct stroker *)&stroker, x3, y3); continue; } ox = x3; oy = y3; assert(matrix_is_affine(matrix)); matrix_map_point(matrix, x0, y0, &x0, &y0); matrix_map_point(matrix, x1, y1, &x1, &y1); matrix_map_point(matrix, x2, y2, &x2, &y2); matrix_map_point(matrix, x3, y3, &x3, &y3); stroker_curve_to((struct stroker *)&stroker, x1, y1, x2, y2, x3, y3); } break; case VG_SQUAD_TO: { data_at(&coords, p, 0, 2, data); x0 = ox; y0 = oy; x1 = 2*ox-px; y1 = 2*oy-py; x3 = data[0]; y3 = data[1]; map_if_relative(ox, oy, relative, &x3, &y3); px = x1; py = y1; { /* form a cubic out of it */ x2 = (x3 + 2*x1) / 3.f; y2 = (y3 + 2*y1) / 3.f; x1 = (x0 + 2*x1) / 3.f; y1 = (y0 + 2*y1) / 3.f; } if (floatsEqual(x1, ox) && floatsEqual(y1, oy) && floatsEqual(x1, x2) && floatsEqual(y1, y2) && floatsEqual(x2, x3) && floatsEqual(y2, y3)) { /*ignore the empty segment */ continue; } else if (floatsEqual(x3, ox) && floatsEqual(y3, oy)) { /* if dup vertex, emit a line */ ox = x3; oy = y3; matrix_map_point(matrix, x3, y3, &x3, &y3); stroker_line_to((struct stroker *)&stroker, x3, y3); continue; } ox = x3; oy = y3; assert(matrix_is_affine(matrix)); matrix_map_point(matrix, x0, y0, &x0, &y0); matrix_map_point(matrix, x1, y1, &x1, &y1); matrix_map_point(matrix, x2, y2, &x2, &y2); matrix_map_point(matrix, x3, y3, &x3, &y3); stroker_curve_to((struct stroker *)&stroker, x1, y1, x2, y2, x3, y3); } break; case VG_SCUBIC_TO: { data_at(&coords, p, 0, 4, data); x0 = ox; y0 = oy; x1 = 2*ox-px; y1 = 2*oy-py; x2 = data[0]; y2 = data[1]; x3 = data[2]; y3 = data[3]; map_if_relative(ox, oy, relative, &x2, &y2); map_if_relative(ox, oy, relative, &x3, &y3); if (floatsEqual(x1, ox) && floatsEqual(y1, oy) && floatsEqual(x1, x2) && floatsEqual(y1, y2) && floatsEqual(x2, x3) && floatsEqual(y2, y3)) { /*ignore the empty segment */ continue; } else if (floatsEqual(x3, ox) && floatsEqual(y3, oy)) { /* if dup vertex, emit a line */ ox = x3; oy = y3; matrix_map_point(matrix, x3, y3, &x3, &y3); stroker_line_to((struct stroker *)&stroker, x3, y3); continue; } ox = x3; oy = y3; px = x2; py = y2; assert(matrix_is_affine(matrix)); matrix_map_point(matrix, x0, y0, &x0, &y0); matrix_map_point(matrix, x1, y1, &x1, &y1); matrix_map_point(matrix, x2, y2, &x2, &y2); matrix_map_point(matrix, x3, y3, &x3, &y3); stroker_curve_to((struct stroker *)&stroker, x1, y1, x2, y2, x3, y3); } break; case VG_SCCWARC_TO: case VG_SCWARC_TO: case VG_LCCWARC_TO: case VG_LCWARC_TO: { VGfloat rh, rv, rot; struct arc arc; data_at(&coords, p, 0, 5, data); x0 = ox; y0 = oy; rh = data[0]; rv = data[1]; rot = data[2]; x1 = data[3]; y1 = data[4]; map_if_relative(ox, oy, relative, &x1, &y1); if (floatsEqual(x1, ox) && floatsEqual(y1, oy)) { /* if dup vertex, emit a line */ ox = x1; oy = y1; matrix_map_point(matrix, x1, y1, &x1, &y1); stroker_line_to((struct stroker *)&stroker, x1, y1); continue; } arc_init(&arc, command, x0, y0, x1, y1, rh, rv, rot); arc_stroke_cb(&arc, (struct stroker *)&stroker, matrix); ox = x1; oy = y1; px = x1; py = y1; } break; default: abort(); assert(!"Unknown segment!"); } } stroker_end((struct stroker *)&stroker); if (dashed) dash_stroker_cleanup((struct dash_stroker *)&stroker); else stroker_cleanup((struct stroker *)&stroker); p->stroked.path = stroker.base.path; p->stroked.matrix = *matrix; p->dirty_stroke = VG_FALSE; p->stroked.stroke_width = vg_state->stroke.line_width.f; p->stroked.miter_limit = vg_state->stroke.miter_limit.f; p->stroked.cap_style = vg_state->stroke.cap_style; p->stroked.join_style = vg_state->stroke.join_style; return stroker.base.path; } void path_render(struct path *p, VGbitfield paintModes, struct matrix *mat) { struct vg_context *ctx = vg_current_context(); struct matrix paint_matrix; vg_validate_state(ctx); shader_set_drawing_image(ctx->shader, VG_FALSE); shader_set_image(ctx->shader, 0); #if 0 fprintf(stderr, "Matrix(11=%f 12=%f 13=%f 21=%f 22=%f 23=%f 31=%f 32=%f 33=%f)\n", mat->m[0], mat->m[1], mat->m[2], mat->m[3], mat->m[4], mat->m[5], mat->m[6], mat->m[7], mat->m[8]); #endif if ((paintModes & VG_FILL_PATH) && vg_get_paint_matrix(ctx, &ctx->state.vg.fill_paint_to_user_matrix, mat, &paint_matrix)) { /* First the fill */ shader_set_surface_matrix(ctx->shader, mat); shader_set_paint(ctx->shader, ctx->state.vg.fill_paint); shader_set_paint_matrix(ctx->shader, &paint_matrix); shader_bind(ctx->shader); path_fill(p); } if ((paintModes & VG_STROKE_PATH) && vg_get_paint_matrix(ctx, &ctx->state.vg.stroke_paint_to_user_matrix, mat, &paint_matrix)) { /* 8.7.5: "line width less than or equal to 0 prevents stroking from * taking place."*/ if (ctx->state.vg.stroke.line_width.f <= 0) return; shader_set_surface_matrix(ctx->shader, mat); shader_set_paint(ctx->shader, ctx->state.vg.stroke_paint); shader_set_paint_matrix(ctx->shader, &paint_matrix); shader_bind(ctx->shader); path_stroke(p); } } void path_fill(struct path *p) { struct vg_context *ctx = vg_current_context(); struct matrix identity; matrix_load_identity(&identity); { struct polygon_array *polygon_array = path_get_fill_polygons(p, &identity); struct array *polys = polygon_array->array; if (!polygon_array || !polys || !polys->num_elements) { return; } polygon_array_fill(polygon_array, ctx); } } void path_stroke(struct path *p) { struct vg_context *ctx = vg_current_context(); VGFillRule old_fill = ctx->state.vg.fill_rule; struct matrix identity; struct path *stroke; matrix_load_identity(&identity); stroke = path_create_stroke(p, &identity); if (stroke && !path_is_empty(stroke)) { ctx->state.vg.fill_rule = VG_NON_ZERO; path_fill(stroke); ctx->state.vg.fill_rule = old_fill; } } void path_move_to(struct path *p, float x, float y) { VGubyte segment = VG_MOVE_TO_ABS; VGubyte common_data[sizeof(VGfloat) * 2]; VGfloat data[2] = {x, y}; vg_float_to_datatype(p->datatype, common_data, data, 2); path_append_data(p, 1, &segment, common_data); } void path_line_to(struct path *p, float x, float y) { VGubyte segment = VG_LINE_TO_ABS; VGubyte common_data[sizeof(VGfloat) * 2]; VGfloat data[2] = {x, y}; vg_float_to_datatype(p->datatype, common_data, data, 2); path_append_data(p, 1, &segment, common_data); } void path_cubic_to(struct path *p, float px1, float py1, float px2, float py2, float x, float y) { VGubyte segment = VG_CUBIC_TO_ABS; VGubyte common_data[sizeof(VGfloat) * 6]; VGfloat data[6]; data[0] = px1; data[1] = py1; data[2] = px2; data[3] = py2; data[4] = x; data[5] = y; vg_float_to_datatype(p->datatype, common_data, data, 6); path_append_data(p, 1, &segment, common_data); } static INLINE void line_bounds(VGfloat *line /*x1,y1,x2,y2*/, VGfloat *bounds) { bounds[0] = MIN2(line[0], line[2]); bounds[1] = MIN2(line[1], line[3]); bounds[2] = MAX2(line[0], line[2]) - bounds[0]; bounds[3] = MAX2(line[1], line[3]) - bounds[1]; } static INLINE void unite_bounds(VGfloat *bounds, VGfloat *el) { VGfloat cx1, cy1, cx2, cy2; VGfloat nx1, ny1, nx2, ny2; cx1 = bounds[0]; cy1 = bounds[1]; cx2 = bounds[0] + bounds[2]; cy2 = bounds[1] + bounds[3]; nx1 = el[0]; ny1 = el[1]; nx2 = el[0] + el[2]; ny2 = el[1] + el[3]; bounds[0] = MIN2(cx1, nx1); bounds[1] = MIN2(cy1, ny1); bounds[2] = MAX2(cx2, nx2) - bounds[0]; bounds[3] = MAX2(cy2, ny2) - bounds[1]; } static INLINE void set_bounds(VGfloat *bounds, VGfloat *element_bounds, VGboolean *initialized) { if (!(*initialized)) { memcpy(bounds, element_bounds, 4 * sizeof(VGfloat)); *initialized = VG_TRUE; } else unite_bounds(bounds, element_bounds); } void path_bounding_rect(struct path *p, float *x, float *y, float *w, float *h) { VGint i; VGfloat coords[8]; struct path_iter_data iter; VGint num_coords; VGfloat bounds[4]; VGfloat element_bounds[4]; VGfloat ox, oy; VGboolean bounds_inited = VG_FALSE; memset(&iter, 0, sizeof(struct path_iter_data)); memset(&bounds, 0, sizeof(bounds)); if (!p->num_segments) { bounds[2] = -1; bounds[3] = -1; } iter.path = p; iter.coords = p->control_points->data; for (i = 0; i < p->num_segments; ++i) { VGubyte segment; iter.segment = ((VGubyte*)(p->segments->data))[i]; ox = iter.ox; oy = iter.oy; segment = normalize_coords(&iter, &num_coords, coords); switch(segment) { case VG_CLOSE_PATH: case VG_MOVE_TO_ABS: break; case VG_LINE_TO_ABS: { VGfloat line[4] = {ox, oy, coords[0], coords[1]}; line_bounds(line, element_bounds); set_bounds(bounds, element_bounds, &bounds_inited); } break; case VG_CUBIC_TO_ABS: { struct bezier bezier; bezier_init(&bezier, ox, oy, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); bezier_exact_bounds(&bezier, element_bounds); set_bounds(bounds, element_bounds, &bounds_inited); } break; case VG_SCCWARC_TO: case VG_SCWARC_TO: case VG_LCCWARC_TO: case VG_LCWARC_TO: { struct arc arc; struct matrix identity; struct path *path = path_create(VG_PATH_DATATYPE_F, 1, 0, 0, 0, VG_PATH_CAPABILITY_ALL); matrix_load_identity(&identity); arc_init(&arc, segment, ox, oy, coords[3], coords[4], coords[0], coords[1], coords[2]); arc_to_path(&arc, path, &identity); path_bounding_rect(path, element_bounds + 0, element_bounds + 1, element_bounds + 2, element_bounds + 3); set_bounds(bounds, element_bounds, &bounds_inited); } break; default: assert(0); } } *x = bounds[0]; *y = bounds[1]; *w = bounds[2]; *h = bounds[3]; } float path_length(struct path *p, int start_segment, int num_segments) { VGint i; VGfloat coords[8]; struct path_iter_data iter; VGint num_coords; VGfloat length = 0; VGfloat ox, oy; VGboolean in_range = VG_FALSE; memset(&iter, 0, sizeof(struct path_iter_data)); iter.path = p; iter.coords = p->control_points->data; for (i = 0; i < (start_segment + num_segments); ++i) { VGubyte segment; iter.segment = ((VGubyte*)(p->segments->data))[i]; ox = iter.ox; oy = iter.oy; segment = normalize_coords(&iter, &num_coords, coords); in_range = (i >= start_segment) && i <= (start_segment + num_segments); if (!in_range) continue; switch(segment) { case VG_MOVE_TO_ABS: break; case VG_CLOSE_PATH: { VGfloat line[4] = {ox, oy, iter.sx, iter.sy}; length += line_lengthv(line); } break; case VG_LINE_TO_ABS: { VGfloat line[4] = {ox, oy, coords[0], coords[1]}; length += line_lengthv(line); } break; case VG_CUBIC_TO_ABS: { struct bezier bezier; bezier_init(&bezier, ox, oy, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); length += bezier_length(&bezier, BEZIER_DEFAULT_ERROR); } break; case VG_SCCWARC_TO: case VG_SCWARC_TO: case VG_LCCWARC_TO: case VG_LCWARC_TO: { struct arc arc; struct matrix identity; struct path *path = path_create(VG_PATH_DATATYPE_F, 1, 0, 0, 0, VG_PATH_CAPABILITY_ALL); matrix_load_identity(&identity); arc_init(&arc, segment, ox, oy, coords[3], coords[4], coords[0], coords[1], coords[2]); arc_to_path(&arc, path, &identity); length += path_length(path, 0, path_num_segments(path)); } break; default: assert(0); } } return length; } static INLINE VGboolean point_on_current_segment(VGfloat distance, VGfloat length, VGfloat segment_length) { return (((floatIsZero(distance) || distance < 0) && floatIsZero(length)) || ((distance > length || floatsEqual(distance, length)) && (floatsEqual(distance, length + segment_length) || distance < (length + segment_length)))); } static VGboolean path_point_segment(struct path_iter_data iter, struct path_iter_data prev_iter, VGfloat coords[8], VGfloat distance, VGfloat length, VGfloat *current_length, VGfloat *point, VGfloat *normal) { switch (iter.segment) { case VG_MOVE_TO_ABS: break; case VG_CLOSE_PATH: { VGfloat line[4] = {prev_iter.ox, prev_iter.oy, iter.sx, iter.sy}; VGboolean on_current_segment = VG_FALSE; *current_length = line_lengthv(line); on_current_segment = point_on_current_segment(distance, length, *current_length); if (on_current_segment) { VGfloat at = (distance - length) / line_lengthv(line); line_normal_vector(line, normal); line_point_at(line, at, point); return VG_TRUE; } } break; case VG_LINE_TO_ABS: { VGfloat line[4] = {prev_iter.ox, prev_iter.oy, coords[0], coords[1]}; VGboolean on_current_segment = VG_FALSE; *current_length = line_lengthv(line); on_current_segment = point_on_current_segment(distance, length, *current_length); if (on_current_segment) { VGfloat at = (distance - length) / line_lengthv(line); line_normal_vector(line, normal); line_point_at(line, at, point); return VG_TRUE; } } break; case VG_CUBIC_TO_ABS: { struct bezier bezier; bezier_init(&bezier, prev_iter.ox, prev_iter.oy, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); *current_length = bezier_length(&bezier, BEZIER_DEFAULT_ERROR); if (point_on_current_segment(distance, length, *current_length)) { bezier_point_at_length(&bezier, distance - length, point, normal); return VG_TRUE; } } break; case VG_SCCWARC_TO: case VG_SCWARC_TO: case VG_LCCWARC_TO: case VG_LCWARC_TO: { struct arc arc; struct matrix identity; struct path *path = path_create(VG_PATH_DATATYPE_F, 1, 0, 0, 0, VG_PATH_CAPABILITY_ALL); matrix_load_identity(&identity); arc_init(&arc, iter.segment, prev_iter.ox, prev_iter.oy, coords[3], coords[4], coords[0], coords[1], coords[2]); arc_to_path(&arc, path, &identity); *current_length = path_length(path, 0, path_num_segments(path)); if (point_on_current_segment(distance, length, *current_length)) { path_point(path, 0, path_num_segments(path), distance - length, point, normal); return VG_TRUE; } } break; default: assert(0); } return VG_FALSE; } void path_point(struct path *p, VGint start_segment, VGint num_segments, VGfloat distance, VGfloat *point, VGfloat *normal) { VGint i; VGfloat coords[8]; struct path_iter_data iter, prev_iter; VGint num_coords; VGfloat length = 0; VGfloat current_length = 0; memset(&iter, 0, sizeof(struct path_iter_data)); memset(&prev_iter, 0, sizeof(struct path_iter_data)); point[0] = 0; point[1] = 0; normal[0] = 0; normal[1] = -1; iter.path = p; iter.coords = p->control_points->data; if (distance < 0) distance = 0; for (i = 0; i < (start_segment + num_segments); ++i) { VGboolean outside_range = (i < start_segment || i >= (start_segment + num_segments)); prev_iter = iter; iter.segment = ((VGubyte*)(p->segments->data))[i]; iter.segment = normalize_coords(&iter, &num_coords, coords); if (outside_range) continue; if (path_point_segment(iter, prev_iter, coords, distance, length, ¤t_length, point, normal)) return; length += current_length; } /* *OpenVG 1.0 - 8.6.11 vgPointAlongPath * * If distance is greater than or equal to the path length *(i.e., the value returned by vgPathLength when called with the same *startSegment and numSegments parameters), the visual ending point of *the path is used. */ { switch (iter.segment) { case VG_MOVE_TO_ABS: break; case VG_CLOSE_PATH: { VGfloat line[4] = {prev_iter.ox, prev_iter.oy, iter.sx, iter.sy}; line_normal_vector(line, normal); line_point_at(line, 1.f, point); } break; case VG_LINE_TO_ABS: { VGfloat line[4] = {prev_iter.ox, prev_iter.oy, coords[0], coords[1]}; line_normal_vector(line, normal); line_point_at(line, 1.f, point); } break; case VG_CUBIC_TO_ABS: { struct bezier bezier; bezier_init(&bezier, prev_iter.ox, prev_iter.oy, coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); bezier_point_at_t(&bezier, 1.f, point, normal); } break; case VG_SCCWARC_TO: case VG_SCWARC_TO: case VG_LCCWARC_TO: case VG_LCWARC_TO: { struct arc arc; struct matrix identity; struct path *path = path_create(VG_PATH_DATATYPE_F, 1, 0, 0, 0, VG_PATH_CAPABILITY_ALL); matrix_load_identity(&identity); arc_init(&arc, iter.segment, prev_iter.ox, prev_iter.oy, coords[3], coords[4], coords[0], coords[1], coords[2]); arc_to_path(&arc, path, &identity); path_point(path, 0, path_num_segments(path), /* to make sure we're bigger than len * 2 it */ 2 * path_length(path, 0, path_num_segments(path)), point, normal); } break; default: assert(0); } } } VGboolean path_is_empty(struct path *p) { return p->segments->num_elements == 0; }