| /************************************************************************** |
| * |
| * 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 <assert.h> |
| |
| #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; |
| } |
| |
| 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); |
| array_destroy(p->fill_polys.polygon_array.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); |
| assert(polys->num_elements); |
| 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 { |
| array_destroy( p->fill_polys.polygon_array.array ); |
| p->fill_polys.polygon_array.array = NULL; |
| } |
| } |
| |
| array = array_create(sizeof(struct array*)); |
| |
| sx = sy = px = py = ox = oy = 0.f; |
| |
| 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 vg_context *ctx = vg_current_context(); |
| struct matrix *mat = &ctx->state.vg.path_user_to_surface_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) { |
| /* First the fill */ |
| shader_set_paint(ctx->shader, ctx->state.vg.fill_paint); |
| shader_bind(ctx->shader); |
| path_fill(p, mat); |
| } |
| |
| if (paintModes & VG_STROKE_PATH){ |
| /* 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_paint(ctx->shader, ctx->state.vg.stroke_paint); |
| shader_bind(ctx->shader); |
| path_stroke(p); |
| } |
| } |
| |
| void path_fill(struct path *p, struct matrix *mat) |
| { |
| struct vg_context *ctx = vg_current_context(); |
| { |
| struct polygon_array *polygon_array = path_get_fill_polygons(p, mat); |
| 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(); |
| struct matrix *mat = &ctx->state.vg.path_user_to_surface_matrix; |
| 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, mat); |
| |
| 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; |
| } |