From 1802294426297002a47e460a0b2a897a7543df67 Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Thu, 4 Jul 2024 14:09:35 +0100 Subject: [PATCH] Improve gradient parsing and generation --- src/svgtiny.c | 71 ++- src/svgtiny_gradient.c | 1222 ++++++++++++++++++++++++---------------- src/svgtiny_internal.h | 11 +- test/data/gradient.mvg | 84 +++ test/data/gradient.svg | 25 + 5 files changed, 913 insertions(+), 500 deletions(-) create mode 100644 test/data/gradient.mvg create mode 100644 test/data/gradient.svg diff --git a/src/svgtiny.c b/src/svgtiny.c index b217127..cf88544 100644 --- a/src/svgtiny.c +++ b/src/svgtiny.c @@ -36,6 +36,14 @@ #define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0) #define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI) +#if (defined(_GNU_SOURCE) && !defined(__APPLE__) || defined(__amigaos4__) || defined(__HAIKU__) || (defined(_POSIX_C_SOURCE) && ((_POSIX_C_SOURCE - 0) >= 200809L))) +#define HAVE_STRNDUP +#else +#undef HAVE_STRNDUP +char *svgtiny_strndup(const char *s, size_t n); +#define strndup svgtiny_strndup +#endif + static svgtiny_code svgtiny_parse_svg(dom_element *svg, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_path(dom_element *path, @@ -1855,14 +1863,37 @@ svgtiny_code svgtiny_add_path(float *p, unsigned int n, struct svgtiny_parse_state *state) { struct svgtiny_shape *shape; + svgtiny_code res = svgtiny_OK; + + if (state->fill == svgtiny_LINEAR_GRADIENT) { + /* adds a shape to fill the path with a linear gradient */ + res = svgtiny_gradient_add_fill_path(p, n, state); + } + if (res != svgtiny_OK) { + free(p); + return res; + } + + if (state->stroke == svgtiny_LINEAR_GRADIENT) { + /* adds a shape to stroke the path with a linear gradient */ + res = svgtiny_gradient_add_stroke_path(p, n, state); + } + if (res != svgtiny_OK) { + free(p); + return res; + } - if (state->fill == svgtiny_LINEAR_GRADIENT) - return svgtiny_add_path_linear_gradient(p, n, state); + /* if stroke and fill are transparent do not add a shape */ + if ((state->fill == svgtiny_TRANSPARENT) && + (state->stroke == svgtiny_TRANSPARENT)) { + free(p); + return res; + } svgtiny_transform_path(p, n, state); shape = svgtiny_add_shape(state); - if (!shape) { + if (shape == NULL) { free(p); return svgtiny_OUT_OF_MEMORY; } @@ -1870,6 +1901,7 @@ svgtiny_code svgtiny_add_path(float *p, unsigned int n, shape->path_length = n; state->diagram->shape_count++; + return svgtiny_OK; } @@ -1880,24 +1912,25 @@ svgtiny_code svgtiny_add_path(float *p, unsigned int n, struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state) { - struct svgtiny_shape *shape = realloc(state->diagram->shape, + struct svgtiny_shape *shape; + + shape = realloc(state->diagram->shape, (state->diagram->shape_count + 1) * sizeof (state->diagram->shape[0])); - if (!shape) - return 0; - state->diagram->shape = shape; - - shape += state->diagram->shape_count; - shape->path = 0; - shape->path_length = 0; - shape->text = 0; - shape->fill = state->fill; - shape->stroke = state->stroke; - shape->stroke_width = lroundf((float) state->stroke_width * - (state->ctm.a + state->ctm.d) / 2.0); - if (0 < state->stroke_width && shape->stroke_width == 0) - shape->stroke_width = 1; - + if (shape != NULL) { + state->diagram->shape = shape; + + shape += state->diagram->shape_count; + shape->path = 0; + shape->path_length = 0; + shape->text = 0; + shape->fill = state->fill; + shape->stroke = state->stroke; + shape->stroke_width = lroundf((float) state->stroke_width * + (state->ctm.a + state->ctm.d) / 2.0); + if (0 < state->stroke_width && shape->stroke_width == 0) + shape->stroke_width = 1; + } return shape; } diff --git a/src/svgtiny_gradient.c b/src/svgtiny_gradient.c index ed20985..9e5b7d2 100644 --- a/src/svgtiny_gradient.c +++ b/src/svgtiny_gradient.c @@ -15,87 +15,161 @@ #include "svgtiny_internal.h" #undef GRADIENT_DEBUG - -static svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear, - struct svgtiny_parse_state_gradient *grad, - struct svgtiny_parse_state *state); -static float svgtiny_parse_gradient_offset(const char *s); -static void svgtiny_path_bbox(float *p, unsigned int n, - float *x0, float *y0, float *x1, float *y1); -static void svgtiny_invert_matrix(float *m, float *inv); +/* set to add vector shape in output */ +#undef GRADIENT_DEBUG_VECTOR /** - * Find a gradient by id and parse it. + * Get the bounding box of path. */ - -svgtiny_code svgtiny_find_gradient(const char *id, - size_t idlen, - struct svgtiny_parse_state_gradient *grad, - struct svgtiny_parse_state *state) +static void +svgtiny_path_bbox(float *p, + unsigned int n, + float *x0, float *y0, float *x1, float *y1) { - dom_element *gradient; - dom_string *id_str, *name; - dom_exception exc; - svgtiny_code res = svgtiny_OK; + unsigned int j; - #ifdef GRADIENT_DEBUG - fprintf(stderr, "svgtiny_find_gradient: id \"%.*s\"\n", idlen, id); - #endif + *x0 = *x1 = p[1]; + *y0 = *y1 = p[2]; - grad->linear_gradient_stop_count = 0; - if (grad->gradient_x1 != NULL) - dom_string_unref(grad->gradient_x1); - if (grad->gradient_y1 != NULL) - dom_string_unref(grad->gradient_y1); - if (grad->gradient_x2 != NULL) - dom_string_unref(grad->gradient_x2); - if (grad->gradient_y2 != NULL) - dom_string_unref(grad->gradient_y2); - grad->gradient_x1 = dom_string_ref(state->interned_zero_percent); - grad->gradient_y1 = dom_string_ref(state->interned_zero_percent); - grad->gradient_x2 = dom_string_ref(state->interned_hundred_percent); - grad->gradient_y2 = dom_string_ref(state->interned_zero_percent); - grad->gradient_user_space_on_use = false; - grad->gradient_transform.a = 1; - grad->gradient_transform.b = 0; - grad->gradient_transform.c = 0; - grad->gradient_transform.d = 1; - grad->gradient_transform.e = 0; - grad->gradient_transform.f = 0; + for (j = 0; j != n; ) { + unsigned int points = 0; + unsigned int k; + switch ((int) p[j]) { + case svgtiny_PATH_MOVE: + case svgtiny_PATH_LINE: + points = 1; + break; + case svgtiny_PATH_CLOSE: + points = 0; + break; + case svgtiny_PATH_BEZIER: + points = 3; + break; + default: + assert(0); + } + j++; + for (k = 0; k != points; k++) { + float x = p[j], y = p[j + 1]; + if (x < *x0) + *x0 = x; + else if (*x1 < x) + *x1 = x; + if (y < *y0) + *y0 = y; + else if (*y1 < y) + *y1 = y; + j += 2; + } + } +} - exc = dom_string_create_interned((const uint8_t *) id, idlen, &id_str); - if (exc != DOM_NO_ERR) - return svgtiny_SVG_ERROR; - exc = dom_document_get_element_by_id(state->document, id_str, &gradient); - dom_string_unref(id_str); - if (exc != DOM_NO_ERR || gradient == NULL) { - #ifdef GRADIENT_DEBUG - fprintf(stderr, "gradient \"%.*s\" not found\n", idlen, id); - #endif - return svgtiny_SVG_ERROR; +/** + * Invert a transformation matrix. + * + * svg transform matrix + * | a c e | + * | b d f | + * | 0 0 1 | + */ +static inline void +svgtiny_invert_matrix(const struct svgtiny_transformation_matrix *m, + struct svgtiny_transformation_matrix *inv) +{ + float determinant = m->a * m->d - m->b * m->c; + inv->a = m->d / determinant; + inv->b = -m->b / determinant; + inv->c = -m->c / determinant; + inv->d = m->a / determinant; + inv->e = (m->c * m->f - m->d * m->e) / determinant; + inv->f = (m->b * m->e - m->a * m->f) / determinant; +} + + +static svgtiny_code +parse_gradient_stops(dom_element *linear, + struct svgtiny_parse_state_gradient *grad, + struct svgtiny_parse_state *state) +{ + unsigned int i = 0; + dom_exception exc; + dom_nodelist *stops; + uint32_t listlen, stopnr; + + exc = dom_element_get_elements_by_tag_name(linear, + state->interned_stop, + &stops); + if (exc != DOM_NO_ERR) { + return svgtiny_LIBDOM_ERROR; + } + if (stops == NULL) { + /* no stops */ + return svgtiny_OK; } - exc = dom_node_get_node_name(gradient, &name); + exc = dom_nodelist_get_length(stops, &listlen); if (exc != DOM_NO_ERR) { - dom_node_unref(gradient); - return svgtiny_SVG_ERROR; + dom_nodelist_unref(stops); + return svgtiny_LIBDOM_ERROR; } - if (dom_string_isequal(name, state->interned_linearGradient)) { - res = svgtiny_parse_linear_gradient(gradient, grad, state); + for (stopnr = 0; stopnr < listlen; ++stopnr) { + dom_element *stop; + float offset = -1; + svgtiny_colour color = svgtiny_TRANSPARENT; + svgtiny_code res; + struct svgtiny_parse_internal_operation ops[] = { + { + state->interned_stop_color, + SVGTIOP_COLOR, + NULL, + &color + }, { + state->interned_offset, + SVGTIOP_OFFSET, + NULL, + &offset + }, { + NULL, SVGTIOP_NONE, NULL, NULL + }, + }; + + + exc = dom_nodelist_item(stops, stopnr, + (dom_node **)(void *)&stop); + if (exc != DOM_NO_ERR) + continue; + + res = svgtiny_parse_attributes(stop, state, ops); + if (res != svgtiny_OK) { + /* stop attributes produced error, skip stop */ + continue; + } + ops[1].key = NULL; /* offset is not a style */ + svgtiny_parse_inline_style(stop, state, ops); + + if (offset != -1 && color != svgtiny_TRANSPARENT) { +#ifdef GRADIENT_DEBUG + fprintf(stderr, "stop %g %x\n", offset, color); +#endif + grad->gradient_stop[i].offset = offset; + grad->gradient_stop[i].color = color; + i++; + } + dom_node_unref(stop); + if (i == svgtiny_MAX_STOPS) + break; } - dom_node_unref(gradient); - dom_string_unref(name); + dom_nodelist_unref(stops); - #ifdef GRADIENT_DEBUG - fprintf(stderr, "linear_gradient_stop_count %i\n", - grad->linear_gradient_stop_count); - #endif + if (i > 0) { + grad->linear_gradient_stop_count = i; + } - return res; + return svgtiny_OK; } @@ -104,15 +178,13 @@ svgtiny_code svgtiny_find_gradient(const char *id, * * http://www.w3.org/TR/SVG11/pservers#LinearGradients */ - -svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear, - struct svgtiny_parse_state_gradient *grad, - struct svgtiny_parse_state *state) +static svgtiny_code +svgtiny_parse_linear_gradient(dom_element *linear, + struct svgtiny_parse_state_gradient *grad, + struct svgtiny_parse_state *state) { - unsigned int i = 0; dom_string *attr; dom_exception exc; - dom_nodelist *stops; exc = dom_element_get_attribute(linear, state->interned_href, &attr); if (exc == DOM_NO_ERR && attr != NULL) { @@ -173,10 +245,10 @@ svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear, dom_string_byte_length(attr), &tm); - #ifdef GRADIENT_DEBUG +#ifdef GRADIENT_DEBUG fprintf(stderr, "transform %g %g %g %g %g %g\n", tm.a, tm.b, tm.c, tm.d, tm.e, tm.f); - #endif +#endif grad->gradient_transform.a = tm.a; grad->gradient_transform.b = tm.b; grad->gradient_transform.c = tm.c; @@ -186,257 +258,115 @@ svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear, dom_string_unref(attr); } - exc = dom_element_get_elements_by_tag_name(linear, - state->interned_stop, - &stops); - if (exc == DOM_NO_ERR && stops != NULL) { - uint32_t listlen, stopnr; - exc = dom_nodelist_get_length(stops, &listlen); - if (exc != DOM_NO_ERR) { - dom_nodelist_unref(stops); - goto no_more_stops; - } - - for (stopnr = 0; stopnr < listlen; ++stopnr) { - dom_element *stop; - float offset = -1; - svgtiny_colour color = svgtiny_TRANSPARENT; - exc = dom_nodelist_item(stops, stopnr, - (dom_node **) (void *) &stop); - if (exc != DOM_NO_ERR) - continue; - exc = dom_element_get_attribute(stop, - state->interned_offset, - &attr); - if (exc == DOM_NO_ERR && attr != NULL) { - char *s = strndup(dom_string_data(attr), - dom_string_byte_length(attr)); - offset = svgtiny_parse_gradient_offset(s); - free(s); - dom_string_unref(attr); - } - exc = dom_element_get_attribute(stop, - state->interned_stop_color, - &attr); - if (exc == DOM_NO_ERR && attr != NULL) { - svgtiny_parse_color(dom_string_data(attr), - dom_string_byte_length(attr), &color); - dom_string_unref(attr); - } - exc = dom_element_get_attribute(stop, - state->interned_style, - &attr); - if (exc == DOM_NO_ERR && attr != NULL) { - struct svgtiny_parse_inline_style_op styles[]={ - { - state->interned_stop_color, - ISTYLEOP_COLOR, - NULL, - &color - },{ - NULL, ISTYLEOP_NONE, NULL, NULL - }, - }; - svgtiny_parse_inline_style(dom_string_data(attr), - dom_string_byte_length(attr), - state, - styles); - dom_string_unref(attr); - } - if (offset != -1 && color != svgtiny_TRANSPARENT) { - #ifdef GRADIENT_DEBUG - fprintf(stderr, "stop %g %x\n", offset, color); - #endif - grad->gradient_stop[i].offset = offset; - grad->gradient_stop[i].color = color; - i++; - } - dom_node_unref(stop); - if (i == svgtiny_MAX_STOPS) - break; - } - - dom_nodelist_unref(stops); - } -no_more_stops: - if (i > 0) - grad->linear_gradient_stop_count = i; - - return svgtiny_OK; + return parse_gradient_stops(linear, grad, state); } -float svgtiny_parse_gradient_offset(const char *s) -{ - int num_length = strspn(s, "0123456789+-."); - const char *unit = s + num_length; - float n = atof((const char *) s); - - if (unit[0] == 0) - ; - else if (unit[0] == '%') - n /= 100.0; - else - return -1; - - if (n < 0) - n = 0; - if (1 < n) - n = 1; - return n; -} +struct grad_point { + float x, y, r; +}; +struct grad_vector { + float x0; + float y0; + float x1; + float y1; +}; -/** - * Add a path with a linear gradient fill to the svgtiny_diagram. - */ -svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n, - struct svgtiny_parse_state *state) +static void +compute_gradient_vector(float *p, + unsigned int n, + struct svgtiny_parse_state *state, + struct svgtiny_parse_state_gradient *grad, + struct grad_vector *vector) { - struct grad_point { - float x, y, r; - }; float object_x0, object_y0, object_x1, object_y1; - float gradient_x0, gradient_y0, gradient_x1, gradient_y1, - gradient_dx, gradient_dy; - float trans[6]; - unsigned int steps = 10; - float x0 = 0, y0 = 0, x0_trans, y0_trans, r0; /* segment start point */ - float x1, y1, x1_trans, y1_trans, r1; /* segment end point */ - /* segment control points (beziers only) */ - float c0x = 0, c0y = 0, c1x = 0, c1y = 0; - float gradient_norm_squared; - struct svgtiny_list *pts; - float min_r = 1000; - unsigned int min_pt = 0; - unsigned int j; - unsigned int stop_count; - unsigned int current_stop; - float last_stop_r; - float current_stop_r; - int red0, green0, blue0, red1, green1, blue1; - unsigned int t, a, b; - struct svgtiny_parse_state_gradient *grad; - - assert(state->fill == svgtiny_LINEAR_GRADIENT); - grad = &state->fill_grad; /* determine object bounding box */ svgtiny_path_bbox(p, n, &object_x0, &object_y0, &object_x1, &object_y1); - #ifdef GRADIENT_DEBUG +#ifdef GRADIENT_DEBUG_VECTOR fprintf(stderr, "object bbox: (%g %g) (%g %g)\n", - object_x0, object_y0, object_x1, object_y1); - #endif + object_x0, object_y0, object_x1, object_y1); +#endif if (!grad->gradient_user_space_on_use) { svgtiny_parse_length(dom_string_data(grad->gradient_x1), dom_string_byte_length(grad->gradient_x1), object_x1 - object_x0, - &gradient_x0); - gradient_x0 += object_x0; + &vector->x0); svgtiny_parse_length(dom_string_data(grad->gradient_y1), dom_string_byte_length(grad->gradient_y1), object_y1 - object_y0, - &gradient_y0); - gradient_y0 += object_y0; + &vector->y0); svgtiny_parse_length(dom_string_data(grad->gradient_x2), dom_string_byte_length(grad->gradient_x2), object_x1 - object_x0, - &gradient_x1); - gradient_x1 += object_x0; + &vector->x1); svgtiny_parse_length(dom_string_data(grad->gradient_y2), dom_string_byte_length(grad->gradient_y2), object_y1 - object_y0, - &gradient_y1); - gradient_y1 += object_y0; + &vector->y1); + + vector->x0 += object_x0; + vector->y0 += object_y0; + vector->x1 += object_x0; + vector->y1 += object_y0; } else { svgtiny_parse_length(dom_string_data(grad->gradient_x1), dom_string_byte_length(grad->gradient_x1), state->viewport_width, - &gradient_x0); + &vector->x0); svgtiny_parse_length(dom_string_data(grad->gradient_y1), dom_string_byte_length(grad->gradient_y1), state->viewport_height, - &gradient_y0); + &vector->y0); svgtiny_parse_length(dom_string_data(grad->gradient_x2), dom_string_byte_length(grad->gradient_x2), state->viewport_width, - &gradient_x1); + &vector->x1); svgtiny_parse_length(dom_string_data(grad->gradient_y2), dom_string_byte_length(grad->gradient_y2), state->viewport_height, - &gradient_y1); + &vector->y1); } - gradient_dx = gradient_x1 - gradient_x0; - gradient_dy = gradient_y1 - gradient_y0; - #ifdef GRADIENT_DEBUG + +#ifdef GRADIENT_DEBUG_VECTOR fprintf(stderr, "gradient vector: (%g %g) => (%g %g)\n", - gradient_x0, gradient_y0, gradient_x1, gradient_y1); - #endif + vector->x0, vector->y0, vector->x1, vector->y1); +#endif - /* show theoretical gradient strips for debugging */ - /*unsigned int strips = 10; - for (unsigned int z = 0; z != strips; z++) { - float f0, fd, strip_x0, strip_y0, strip_dx, strip_dy; - f0 = (float) z / (float) strips; - fd = (float) 1 / (float) strips; - strip_x0 = gradient_x0 + f0 * gradient_dx; - strip_y0 = gradient_y0 + f0 * gradient_dy; - strip_dx = fd * gradient_dx; - strip_dy = fd * gradient_dy; - fprintf(stderr, "strip %i vector: (%g %g) + (%g %g)\n", - z, strip_x0, strip_y0, strip_dx, strip_dy); +} - float *p = malloc(13 * sizeof p[0]); - if (!p) - return svgtiny_OUT_OF_MEMORY; - p[0] = svgtiny_PATH_MOVE; - p[1] = strip_x0 + (strip_dy * 3); - p[2] = strip_y0 - (strip_dx * 3); - p[3] = svgtiny_PATH_LINE; - p[4] = p[1] + strip_dx; - p[5] = p[2] + strip_dy; - p[6] = svgtiny_PATH_LINE; - p[7] = p[4] - (strip_dy * 6); - p[8] = p[5] + (strip_dx * 6); - p[9] = svgtiny_PATH_LINE; - p[10] = p[7] - strip_dx; - p[11] = p[8] - strip_dy; - p[12] = svgtiny_PATH_CLOSE; - svgtiny_transform_path(p, 13, state); - struct svgtiny_shape *shape = svgtiny_add_shape(state); - if (!shape) { - free(p); - return svgtiny_OUT_OF_MEMORY; - } - shape->path = p; - shape->path_length = 13; - shape->fill = svgtiny_TRANSPARENT; - shape->stroke = svgtiny_RGB(0, 0xff, 0); - state->diagram->shape_count++; - }*/ - /* invert gradient transform for applying to vertices */ - svgtiny_invert_matrix(&grad->gradient_transform.a, trans); - #ifdef GRADIENT_DEBUG - fprintf(stderr, "inverse transform %g %g %g %g %g %g\n", - trans[0], trans[1], trans[2], trans[3], - trans[4], trans[5]); - #endif +/* compute points on the path */ +static svgtiny_code +compute_grad_points(float *p, + unsigned int n, + struct svgtiny_transformation_matrix *trans, + struct grad_vector *vector, + struct svgtiny_list *pts, + unsigned int *min_pt) +{ + float gradient_norm_squared; + float gradient_dx; + float gradient_dy; + unsigned int j; + float min_r = 1000; + float x0 = 0, y0 = 0, x0_trans, y0_trans, r0; /* segment start point */ + float x1, y1, x1_trans, y1_trans, r1; /* segment end point */ + float c0x = 0, c0y = 0, c1x = 0, c1y = 0; /* segment control points (beziers only) */ + unsigned int steps = 10; + + gradient_dx = vector->x1 - vector->x0; + gradient_dy = vector->y1 - vector->y0; + + gradient_norm_squared = gradient_dx * gradient_dx + gradient_dy * gradient_dy; - /* compute points on the path for triangle vertices */ /* r, r0, r1 are distance along gradient vector */ - gradient_norm_squared = gradient_dx * gradient_dx + - gradient_dy * gradient_dy; - pts = svgtiny_list_create(sizeof (struct grad_point)); - if (!pts) { - free(p); - return svgtiny_OUT_OF_MEMORY; - } for (j = 0; j != n; ) { int segment_type = (int) p[j]; struct grad_point *point; @@ -450,19 +380,17 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n, } assert(segment_type == svgtiny_PATH_CLOSE || - segment_type == svgtiny_PATH_LINE || - segment_type == svgtiny_PATH_BEZIER); + segment_type == svgtiny_PATH_LINE || + segment_type == svgtiny_PATH_BEZIER); /* start point (x0, y0) */ - x0_trans = trans[0]*x0 + trans[2]*y0 + trans[4]; - y0_trans = trans[1]*x0 + trans[3]*y0 + trans[5]; - r0 = ((x0_trans - gradient_x0) * gradient_dx + - (y0_trans - gradient_y0) * gradient_dy) / - gradient_norm_squared; + x0_trans = trans->a * x0 + trans->c * y0 + trans->e; + y0_trans = trans->b * x0 + trans->d * y0 + trans->f; + r0 = ((x0_trans - vector->x0) * gradient_dx + + (y0_trans - vector->y0) * gradient_dy) / + gradient_norm_squared; point = svgtiny_list_push(pts); if (!point) { - free(p); - svgtiny_list_free(pts); return svgtiny_OUT_OF_MEMORY; } point->x = x0; @@ -470,7 +398,7 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n, point->r = r0; if (r0 < min_r) { min_r = r0; - min_pt = svgtiny_list_size(pts) - 1; + *min_pt = svgtiny_list_size(pts) - 1; } /* end point (x1, y1) */ @@ -491,11 +419,11 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n, y1 = p[j + 6]; j += 7; } - x1_trans = trans[0]*x1 + trans[2]*y1 + trans[4]; - y1_trans = trans[1]*x1 + trans[3]*y1 + trans[5]; - r1 = ((x1_trans - gradient_x0) * gradient_dx + - (y1_trans - gradient_y0) * gradient_dy) / - gradient_norm_squared; + x1_trans = trans->a * x1 + trans->c * y1 + trans->e; + y1_trans = trans->b * x1 + trans->d * y1 + trans->f; + r1 = ((x1_trans - vector->x0) * gradient_dx + + (y1_trans - vector->y0) * gradient_dy) / + gradient_norm_squared; /* determine steps from change in r */ @@ -507,10 +435,9 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n, if (steps == 0) steps = 1; - #ifdef GRADIENT_DEBUG - fprintf(stderr, "r0 %g, r1 %g, steps %i\n", - r0, r1, steps); - #endif +#ifdef GRADIENT_DEBUG + fprintf(stderr, "r0 %g, r1 %g, steps %i\n", r0, r1, steps); +#endif /* loop through intermediate points */ for (z = 1; z != steps; z++) { @@ -530,18 +457,16 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n, x = (1-t) * x0 + t * x1; y = (1-t) * y0 + t * y1; } - x_trans = trans[0]*x + trans[2]*y + trans[4]; - y_trans = trans[1]*x + trans[3]*y + trans[5]; - r = ((x_trans - gradient_x0) * gradient_dx + - (y_trans - gradient_y0) * gradient_dy) / - gradient_norm_squared; - #ifdef GRADIENT_DEBUG + x_trans = trans->a * x + trans->c * y + trans->e; + y_trans = trans->b * x + trans->d * y + trans->f; + r = ((x_trans - vector->x0) * gradient_dx + + (y_trans - vector->y0) * gradient_dy) / + gradient_norm_squared; +#ifdef GRADIENT_DEBUG fprintf(stderr, "(%g %g [%g]) ", x, y, r); - #endif +#endif point = svgtiny_list_push(pts); if (!point) { - free(p); - svgtiny_list_free(pts); return svgtiny_OUT_OF_MEMORY; } point->x = x; @@ -549,148 +474,88 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n, point->r = r; if (r < min_r) { min_r = r; - min_pt = svgtiny_list_size(pts) - 1; + *min_pt = svgtiny_list_size(pts) - 1; } } - #ifdef GRADIENT_DEBUG +#ifdef GRADIENT_DEBUG fprintf(stderr, "\n"); - #endif +#endif /* next segment start point is this segment end point */ x0 = x1; y0 = y1; } - #ifdef GRADIENT_DEBUG +#ifdef GRADIENT_DEBUG fprintf(stderr, "pts size %i, min_pt %i, min_r %.3f\n", - svgtiny_list_size(pts), min_pt, min_r); - #endif + svgtiny_list_size(pts), *min_pt, min_r); +#endif + return svgtiny_OK; +} - /* There must be at least a single point for the gradient */ - if (svgtiny_list_size(pts) == 0) { - svgtiny_list_free(pts); - free(p); - return svgtiny_OK; - } +#ifdef GRADIENT_DEBUG +/** + * show theoretical gradient strips for debugging + */ +static svgtiny_code +add_debug_gradient_strips(struct svgtiny_parse_state *state, + struct grad_vector *vector) +{ + float gradient_dx = vector->x1 - vector->x0; + float gradient_dy = vector->y1 - vector->y0; + unsigned int strips = 10; + for (unsigned int z = 0; z != strips; z++) { + float f0, fd, strip_x0, strip_y0, strip_dx, strip_dy; + f0 = (float) z / (float) strips; + fd = (float) 1 / (float) strips; + strip_x0 = vector->x0 + f0 * gradient_dx; + strip_y0 = vector->y0 + f0 * gradient_dy; + strip_dx = fd * gradient_dx; + strip_dy = fd * gradient_dy; + fprintf(stderr, "strip %i vector: (%g %g) + (%g %g)\n", + z, strip_x0, strip_y0, strip_dx, strip_dy); - /* render triangles */ - stop_count = grad->linear_gradient_stop_count; - assert(2 <= stop_count); - current_stop = 0; - last_stop_r = 0; - current_stop_r = grad->gradient_stop[0].offset; - red0 = red1 = svgtiny_RED(grad->gradient_stop[0].color); - green0 = green1 = svgtiny_GREEN(grad->gradient_stop[0].color); - blue0 = blue1 = svgtiny_BLUE(grad->gradient_stop[0].color); - t = min_pt; - a = (min_pt + 1) % svgtiny_list_size(pts); - b = min_pt == 0 ? svgtiny_list_size(pts) - 1 : min_pt - 1; - while (a != b) { - struct grad_point *point_t = svgtiny_list_get(pts, t); - struct grad_point *point_a = svgtiny_list_get(pts, a); - struct grad_point *point_b = svgtiny_list_get(pts, b); - float mean_r = (point_t->r + point_a->r + point_b->r) / 3; - float *p; - struct svgtiny_shape *shape; - /*fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f " - "mean_r %.3f\n", - t, pts[t].r, a, pts[a].r, b, pts[b].r, - mean_r);*/ - while (current_stop != stop_count && current_stop_r < mean_r) { - current_stop++; - if (current_stop == stop_count) - break; - red0 = red1; - green0 = green1; - blue0 = blue1; - red1 = svgtiny_RED(grad-> - gradient_stop[current_stop].color); - green1 = svgtiny_GREEN(grad-> - gradient_stop[current_stop].color); - blue1 = svgtiny_BLUE(grad-> - gradient_stop[current_stop].color); - last_stop_r = current_stop_r; - current_stop_r = grad-> - gradient_stop[current_stop].offset; - } - p = malloc(10 * sizeof p[0]); + float *p = malloc(13 * sizeof p[0]); if (!p) return svgtiny_OUT_OF_MEMORY; p[0] = svgtiny_PATH_MOVE; - p[1] = point_t->x; - p[2] = point_t->y; + p[1] = strip_x0 + (strip_dy * 3); + p[2] = strip_y0 - (strip_dx * 3); p[3] = svgtiny_PATH_LINE; - p[4] = point_a->x; - p[5] = point_a->y; + p[4] = p[1] + strip_dx; + p[5] = p[2] + strip_dy; p[6] = svgtiny_PATH_LINE; - p[7] = point_b->x; - p[8] = point_b->y; - p[9] = svgtiny_PATH_CLOSE; - svgtiny_transform_path(p, 10, state); - shape = svgtiny_add_shape(state); - if (!shape) { - free(p); - return svgtiny_OUT_OF_MEMORY; - } - shape->path = p; - shape->path_length = 10; - /*shape->fill = svgtiny_TRANSPARENT;*/ - if (current_stop == 0) - shape->fill = grad->gradient_stop[0].color; - else if (current_stop == stop_count) - shape->fill = grad->gradient_stop[stop_count - 1].color; - else { - float stop_r = (mean_r - last_stop_r) / - (current_stop_r - last_stop_r); - shape->fill = svgtiny_RGB( - (int) ((1 - stop_r) * red0 + stop_r * red1), - (int) ((1 - stop_r) * green0 + stop_r * green1), - (int) ((1 - stop_r) * blue0 + stop_r * blue1)); - } - shape->stroke = svgtiny_TRANSPARENT; - #ifdef GRADIENT_DEBUG - shape->stroke = svgtiny_RGB(0, 0, 0xff); - #endif - state->diagram->shape_count++; - if (point_a->r < point_b->r) { - t = a; - a = (a + 1) % svgtiny_list_size(pts); - } else { - t = b; - b = b == 0 ? svgtiny_list_size(pts) - 1 : b - 1; - } - } - - /* render gradient vector for debugging */ - #ifdef GRADIENT_DEBUG - { - float *p = malloc(7 * sizeof p[0]); - if (!p) - return svgtiny_OUT_OF_MEMORY; - p[0] = svgtiny_PATH_MOVE; - p[1] = gradient_x0; - p[2] = gradient_y0; - p[3] = svgtiny_PATH_LINE; - p[4] = gradient_x1; - p[5] = gradient_y1; - p[6] = svgtiny_PATH_CLOSE; - svgtiny_transform_path(p, 7, state); + p[7] = p[4] - (strip_dy * 6); + p[8] = p[5] + (strip_dx * 6); + p[9] = svgtiny_PATH_LINE; + p[10] = p[7] - strip_dx; + p[11] = p[8] - strip_dy; + p[12] = svgtiny_PATH_CLOSE; + svgtiny_transform_path(p, 13, state); struct svgtiny_shape *shape = svgtiny_add_shape(state); if (!shape) { - free(p); return svgtiny_OUT_OF_MEMORY; } shape->path = p; - shape->path_length = 7; + shape->path_length = 13; shape->fill = svgtiny_TRANSPARENT; - shape->stroke = svgtiny_RGB(0xff, 0, 0); + shape->stroke = svgtiny_RGB(0, 0xff, 0); state->diagram->shape_count++; } - #endif + return svgtiny_OK; +} - /* render triangle vertices with r values for debugging */ - #ifdef GRADIENT_DEBUG - for (unsigned int i = 0; i != svgtiny_list_size(pts); i++) { + +/** + * render triangle vertices with r values for debugging + */ +static svgtiny_code +add_debug_gradient_vertices(struct svgtiny_parse_state *state, + struct svgtiny_list *pts) +{ + unsigned int i; + + for (i = 0; i != svgtiny_list_size(pts); i++) { struct grad_point *point = svgtiny_list_get(pts, i); struct svgtiny_shape *shape = svgtiny_add_shape(state); if (!shape) @@ -701,95 +566,506 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n, sprintf(text, "%i=%.3f", i, point->r); shape->text = text; shape->text_x = state->ctm.a * point->x + - state->ctm.c * point->y + state->ctm.e; + state->ctm.c * point->y + state->ctm.e; shape->text_y = state->ctm.b * point->x + - state->ctm.d * point->y + state->ctm.f; + state->ctm.d * point->y + state->ctm.f; shape->fill = svgtiny_RGB(0, 0, 0); shape->stroke = svgtiny_TRANSPARENT; state->diagram->shape_count++; } - #endif + return svgtiny_OK; +} - /* plot actual path outline */ - if (state->stroke != svgtiny_TRANSPARENT) { - struct svgtiny_shape *shape; - svgtiny_transform_path(p, n, state); +#endif + + +#ifdef GRADIENT_DEBUG_VECTOR +/** + * render gradient vector for debugging + */ +static svgtiny_code +add_debug_gradient_vector(struct svgtiny_parse_state *state, + struct grad_vector *vector) +{ + float *p = malloc(7 * sizeof p[0]); + if (!p) + return svgtiny_OUT_OF_MEMORY; + p[0] = svgtiny_PATH_MOVE; + p[1] = vector->x0; + p[2] = vector->y0; + p[3] = svgtiny_PATH_LINE; + p[4] = vector->x1; + p[5] = vector->y1; + p[6] = svgtiny_PATH_CLOSE; + svgtiny_transform_path(p, 7, state); + struct svgtiny_shape *shape = svgtiny_add_shape(state); + if (!shape) { + free(p); + return svgtiny_OUT_OF_MEMORY; + } + shape->path = p; + shape->path_length = 7; + shape->fill = svgtiny_TRANSPARENT; + shape->stroke = svgtiny_RGB(0xff, 0, 0); + state->diagram->shape_count++; + return svgtiny_OK; +} +#endif + + +struct grad_color { + struct svgtiny_gradient_stop *stops; + unsigned int stop_count; + unsigned int current_stop; + float last_stop_r; + float current_stop_r; + int red0; + int green0; + int blue0; + int red1; + int green1; + int blue1; +}; + +/** + * initalise gradiant colour state to first stop + */ +static inline void +init_grad_color(struct svgtiny_parse_state_gradient *grad, struct grad_color* gc) +{ + assert(2 <= grad->linear_gradient_stop_count); + + gc->stops = grad->gradient_stop; + gc->stop_count = grad->linear_gradient_stop_count; + gc->current_stop = 0; + gc->last_stop_r = 0; + gc->current_stop_r = grad->gradient_stop[0].offset; + gc->red0 = gc->red1 = svgtiny_RED(grad->gradient_stop[0].color); + gc->green0 = gc->green1 = svgtiny_GREEN(grad->gradient_stop[0].color); + gc->blue0 = gc->blue1 = svgtiny_BLUE(grad->gradient_stop[0].color); +} + +/** + * advance through stops to get to a target r value + * + * \param gc The gradient colour + * \param tgt_t distance along gradient vector to advance to + */ +static inline svgtiny_colour +advance_grad_color(struct grad_color* gc, float tgt_r) +{ + svgtiny_colour current_color = 0; + + while ((gc->current_stop != gc->stop_count) && + (gc->current_stop_r < tgt_r)) { + gc->current_stop++; + if (gc->current_stop == gc->stop_count) { + /* no more stops to try */ + break; + } + gc->red0 = gc->red1; + gc->green0 = gc->green1; + gc->blue0 = gc->blue1; + + gc->red1 = svgtiny_RED(gc->stops[gc->current_stop].color); + gc->green1 = svgtiny_GREEN(gc->stops[gc->current_stop].color); + gc->blue1 = svgtiny_BLUE(gc->stops[gc->current_stop].color); + + gc->last_stop_r = gc->current_stop_r; + gc->current_stop_r = gc->stops[gc->current_stop].offset; + } + + /* compute the colour for the target r */ + if (gc->current_stop == 0) { + current_color = gc->stops[0].color; + } else if (gc->current_stop == gc->stop_count) { + current_color = gc->stops[gc->stop_count - 1].color; + } else { + float stop_r = (tgt_r - gc->last_stop_r) / + (gc->current_stop_r - gc->last_stop_r); + current_color = svgtiny_RGB( + (int) ((1 - stop_r) * gc->red0 + stop_r * gc->red1), + (int) ((1 - stop_r) * gc->green0 + stop_r * gc->green1), + (int) ((1 - stop_r) * gc->blue0 + stop_r * gc->blue1)); + } + return current_color; +} + + +/** + * add triangles to fill a gradient + */ +static svgtiny_code +add_gradient_triangles(struct svgtiny_parse_state *state, + struct svgtiny_parse_state_gradient *grad, + struct svgtiny_list *pts, + unsigned int min_pt) +{ + unsigned int t, a, b; + struct grad_color gc; + + init_grad_color(grad, &gc); + t = min_pt; + a = (min_pt + 1) % svgtiny_list_size(pts); + b = min_pt == 0 ? svgtiny_list_size(pts) - 1 : min_pt - 1; + while (a != b) { + struct grad_point *point_t = svgtiny_list_get(pts, t); + struct grad_point *point_a = svgtiny_list_get(pts, a); + struct grad_point *point_b = svgtiny_list_get(pts, b); + float mean_r = (point_t->r + point_a->r + point_b->r) / 3; + float *p; + struct svgtiny_shape *shape; +#ifdef GRADIENT_DEBUG + fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f " + "mean_r %.3f\n", + t, pts[t].r, a, pts[a].r, b, pts[b].r, + mean_r); +#endif + p = malloc(10 * sizeof p[0]); + if (!p) + return svgtiny_OUT_OF_MEMORY; + p[0] = svgtiny_PATH_MOVE; + p[1] = point_t->x; + p[2] = point_t->y; + p[3] = svgtiny_PATH_LINE; + p[4] = point_a->x; + p[5] = point_a->y; + p[6] = svgtiny_PATH_LINE; + p[7] = point_b->x; + p[8] = point_b->y; + p[9] = svgtiny_PATH_CLOSE; + svgtiny_transform_path(p, 10, state); shape = svgtiny_add_shape(state); if (!shape) { free(p); return svgtiny_OUT_OF_MEMORY; } shape->path = p; - shape->path_length = n; - shape->fill = svgtiny_TRANSPARENT; + shape->path_length = 10; + shape->fill = advance_grad_color(&gc, mean_r); + shape->stroke = svgtiny_TRANSPARENT; +#ifdef GRADIENT_DEBUG + shape->stroke = svgtiny_RGB(0, 0, 0xff); +#endif state->diagram->shape_count++; - } else { - free(p); + + if (point_a->r < point_b->r) { + t = a; + a = (a + 1) % svgtiny_list_size(pts); + } else { + t = b; + b = b == 0 ? svgtiny_list_size(pts) - 1 : b - 1; + } } + return svgtiny_OK; +} - svgtiny_list_free(pts); + +/** + * add lines to stroke a gradient + */ +static svgtiny_code +add_gradient_lines(struct svgtiny_parse_state *state, + struct svgtiny_parse_state_gradient *grad, + struct svgtiny_list *pts, + unsigned int min_pt) +{ + struct grad_color gc; + unsigned int b; + struct grad_point *point_a; + struct grad_point *point_b; + int dir; + + for (dir=0;dir <2;dir++) { + init_grad_color(grad, &gc); + + point_a = svgtiny_list_get(pts, min_pt); + if (dir==0) { + b = (min_pt + 1) % svgtiny_list_size(pts); + } else { + b = min_pt == 0 ? svgtiny_list_size(pts) - 1 : min_pt - 1; + } + point_b = svgtiny_list_get(pts, b); + while (point_a->r <= point_b->r) { + float mean_r = (point_a->r + point_b->r) / 2; + float *p; + struct svgtiny_shape *shape; + +#ifdef GRADIENT_DEBUG + fprintf(stderr, + "line: a (x:%.3f y:%.3f r:%.3f) " + "b (i:%i x:%.3f y:%.3f r:%.3f) " + "mean_r %.3f\n", + point_a->x,point_a->y,point_a->r, + b, point_b->x,point_b->y,point_b->r, mean_r); +#endif + + p = malloc(7 * sizeof p[0]); + if (!p) { + return svgtiny_OUT_OF_MEMORY; + } + p[0] = svgtiny_PATH_MOVE; + p[1] = point_a->x; + p[2] = point_a->y; + p[3] = svgtiny_PATH_LINE; + p[4] = point_b->x; + p[5] = point_b->y; + p[6] = svgtiny_PATH_CLOSE; + svgtiny_transform_path(p, 7, state); + shape = svgtiny_add_shape(state); + if (!shape) { + free(p); + return svgtiny_OUT_OF_MEMORY; + } + shape->path = p; + shape->path_length = 7; + shape->fill = svgtiny_TRANSPARENT; + shape->stroke = advance_grad_color(&gc, mean_r); + + state->diagram->shape_count++; + + point_a = point_b; + if (dir==0) { + b = (b + 1) % svgtiny_list_size(pts); + } else { + b = b == 0 ? svgtiny_list_size(pts) - 1 : b - 1; + } + point_b = svgtiny_list_get(pts, b); + + } + } return svgtiny_OK; } /** - * Get the bounding box of path. + * Find a gradient by id and parse it. */ +svgtiny_code +svgtiny_find_gradient(const char *id, + size_t idlen, + struct svgtiny_parse_state_gradient *grad, + struct svgtiny_parse_state *state) +{ + dom_element *gradient; + dom_string *id_str, *name; + dom_exception exc; + svgtiny_code res = svgtiny_OK; + +#ifdef GRADIENT_DEBUG + fprintf(stderr, "svgtiny_find_gradient: id \"%.*s\"\n", (int)idlen, id); +#endif + + grad->linear_gradient_stop_count = 0; + if (grad->gradient_x1 != NULL) + dom_string_unref(grad->gradient_x1); + if (grad->gradient_y1 != NULL) + dom_string_unref(grad->gradient_y1); + if (grad->gradient_x2 != NULL) + dom_string_unref(grad->gradient_x2); + if (grad->gradient_y2 != NULL) + dom_string_unref(grad->gradient_y2); + grad->gradient_x1 = dom_string_ref(state->interned_zero_percent); + grad->gradient_y1 = dom_string_ref(state->interned_zero_percent); + grad->gradient_x2 = dom_string_ref(state->interned_hundred_percent); + grad->gradient_y2 = dom_string_ref(state->interned_zero_percent); + grad->gradient_user_space_on_use = false; + grad->gradient_transform.a = 1; + grad->gradient_transform.b = 0; + grad->gradient_transform.c = 0; + grad->gradient_transform.d = 1; + grad->gradient_transform.e = 0; + grad->gradient_transform.f = 0; + + exc = dom_string_create_interned((const uint8_t *) id, idlen, &id_str); + if (exc != DOM_NO_ERR) + return svgtiny_SVG_ERROR; -void svgtiny_path_bbox(float *p, unsigned int n, - float *x0, float *y0, float *x1, float *y1) + exc = dom_document_get_element_by_id(state->document, id_str, &gradient); + dom_string_unref(id_str); + if (exc != DOM_NO_ERR || gradient == NULL) { +#ifdef GRADIENT_DEBUG + fprintf(stderr, "gradient \"%.*s\" not found\n", (int)idlen, id); +#endif + return svgtiny_SVG_ERROR; + } + + exc = dom_node_get_node_name(gradient, &name); + if (exc != DOM_NO_ERR) { + dom_node_unref(gradient); + return svgtiny_SVG_ERROR; + } + + if (dom_string_isequal(name, state->interned_linearGradient)) { + res = svgtiny_parse_linear_gradient(gradient, grad, state); + } + + dom_node_unref(gradient); + dom_string_unref(name); + +#ifdef GRADIENT_DEBUG + fprintf(stderr, "linear_gradient_stop_count %i\n", + grad->linear_gradient_stop_count); +#endif + + return res; +} + + +/** + * Add a path with a linear gradient stroke to the diagram. + * + */ +svgtiny_code +svgtiny_gradient_add_stroke_path(float *p, + unsigned int n, + struct svgtiny_parse_state *state) { - unsigned int j; + struct grad_vector vector; /* gradient vector */ + struct svgtiny_transformation_matrix trans; + struct svgtiny_list *pts; + unsigned int min_pt = 0; + struct svgtiny_parse_state_gradient *grad; + svgtiny_code res; - *x0 = *x1 = p[1]; - *y0 = *y1 = p[2]; + assert(state->stroke == svgtiny_LINEAR_GRADIENT); + /* original path should not be stroked as a shape is added here */ + state->stroke = svgtiny_TRANSPARENT; - for (j = 0; j != n; ) { - unsigned int points = 0; - unsigned int k; - switch ((int) p[j]) { - case svgtiny_PATH_MOVE: - case svgtiny_PATH_LINE: - points = 1; - break; - case svgtiny_PATH_CLOSE: - points = 0; - break; - case svgtiny_PATH_BEZIER: - points = 3; - break; - default: - assert(0); - } - j++; - for (k = 0; k != points; k++) { - float x = p[j], y = p[j + 1]; - if (x < *x0) - *x0 = x; - else if (*x1 < x) - *x1 = x; - if (y < *y0) - *y0 = y; - else if (*y1 < y) - *y1 = y; - j += 2; + grad = &state->stroke_grad; + + /* at least two stops are required to form a valid gradient */ + if (grad->linear_gradient_stop_count < 2) { + if (grad->linear_gradient_stop_count == 1) { + /* stroke the shape with first stop colour */ + state->stroke = grad->gradient_stop[0].color; } + return svgtiny_OK; + } + + compute_gradient_vector(p, n, state, grad, &vector); + + svgtiny_invert_matrix(&grad->gradient_transform, &trans); + + /* compute points on the path for vertices */ + pts = svgtiny_list_create(sizeof (struct grad_point)); + if (!pts) { + return svgtiny_OUT_OF_MEMORY; + } + res = compute_grad_points(p, n, &trans, &vector, pts, &min_pt); + if (res != svgtiny_OK) { + svgtiny_list_free(pts); + return res; } + + /* There must be at least a single point for the gradient */ + if (svgtiny_list_size(pts) == 0) { + svgtiny_list_free(pts); + return svgtiny_OK; + } + + /* render lines */ + res = add_gradient_lines(state, grad, pts, min_pt); + + svgtiny_list_free(pts); + + return res; } /** - * Invert a transformation matrix. + * Add a path with a linear gradient fill to the diagram. */ -void svgtiny_invert_matrix(float *m, float *inv) +svgtiny_code +svgtiny_gradient_add_fill_path(float *p, + unsigned int n, + struct svgtiny_parse_state *state) { - float determinant = m[0]*m[3] - m[1]*m[2]; - inv[0] = m[3] / determinant; - inv[1] = -m[1] / determinant; - inv[2] = -m[2] / determinant; - inv[3] = m[0] / determinant; - inv[4] = (m[2]*m[5] - m[3]*m[4]) / determinant; - inv[5] = (m[1]*m[4] - m[0]*m[5]) / determinant; + struct grad_vector vector; /* gradient vector */ + struct svgtiny_transformation_matrix trans; + struct svgtiny_list *pts; + unsigned int min_pt = 0; + struct svgtiny_parse_state_gradient *grad; + svgtiny_code res; + + assert(state->fill == svgtiny_LINEAR_GRADIENT); + /* original path should not be filled as a shape is added here */ + state->fill = svgtiny_TRANSPARENT; + + grad = &state->fill_grad; + + /* at least two stops are required to form a valid gradient */ + if (grad->linear_gradient_stop_count < 2) { + if (grad->linear_gradient_stop_count == 1) { + /* fill the shape with stop colour */ + state->fill = grad->gradient_stop[0].color; + } + return svgtiny_OK; + } + + compute_gradient_vector(p, n, state, grad, &vector); + +#ifdef GRADIENT_DEBUG + /* debug strips */ + res = add_debug_gradient_strips(state, &vector); + if (res != svgtiny_OK) { + return res; + } +#endif + + /* invert gradient transform for applying to vertices */ + svgtiny_invert_matrix(&grad->gradient_transform, &trans); +#ifdef GRADIENT_DEBUG + fprintf(stderr, "inverse transform %g %g %g %g %g %g\n", + trans[0], trans[1], trans[2], trans[3], + trans[4], trans[5]); +#endif + + /* compute points on the path for triangle vertices */ + pts = svgtiny_list_create(sizeof (struct grad_point)); + if (!pts) { + return svgtiny_OUT_OF_MEMORY; + } + res = compute_grad_points(p, n, &trans, &vector, pts, &min_pt); + if (res != svgtiny_OK) { + svgtiny_list_free(pts); + return res; + } + + /* There must be at least a single point for the gradient */ + if (svgtiny_list_size(pts) == 0) { + svgtiny_list_free(pts); + return svgtiny_OK; + } + + /* render triangles */ + res = add_gradient_triangles(state, grad, pts, min_pt); + if (res != svgtiny_OK) { + svgtiny_list_free(pts); + return res; + } + +#ifdef GRADIENT_DEBUG_VECTOR + /* render gradient vector for debugging */ + res = add_debug_gradient_vector(state, &vector); + if (res != svgtiny_OK) { + svgtiny_list_free(pts); + return res; + } +#endif + +#ifdef GRADIENT_DEBUG + /* render triangle vertices with r values for debugging */ + res = add_debug_gradient_vertices(state, pts); + if (res != svgtiny_OK) { + svgtiny_list_free(pts); + return res; + } +#endif + + svgtiny_list_free(pts); + + return svgtiny_OK; } diff --git a/src/svgtiny_internal.h b/src/svgtiny_internal.h index d2ba4d3..41879f2 100644 --- a/src/svgtiny_internal.h +++ b/src/svgtiny_internal.h @@ -94,13 +94,6 @@ struct svgtiny_parse_internal_operation { struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state); void svgtiny_transform_path(float *p, unsigned int n, struct svgtiny_parse_state *state); -#if (defined(_GNU_SOURCE) && !defined(__APPLE__) || defined(__amigaos4__) || defined(__HAIKU__) || (defined(_POSIX_C_SOURCE) && ((_POSIX_C_SOURCE - 0) >= 200809L))) -#define HAVE_STRNDUP -#else -#undef HAVE_STRNDUP -char *svgtiny_strndup(const char *s, size_t n); -#define strndup svgtiny_strndup -#endif /* svgtiny_parse.c */ svgtiny_code svgtiny_parse_poly_points(const char *data, size_t datalen, @@ -126,7 +119,9 @@ svgtiny_code svgtiny_find_gradient(const char *id, size_t idlen, struct svgtiny_parse_state_gradient *grad, struct svgtiny_parse_state *state); -svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n, +svgtiny_code svgtiny_gradient_add_fill_path(float *p, unsigned int n, + struct svgtiny_parse_state *state); +svgtiny_code svgtiny_gradient_add_stroke_path(float *p, unsigned int n, struct svgtiny_parse_state *state); /* svgtiny_list.c */ diff --git a/test/data/gradient.mvg b/test/data/gradient.mvg new file mode 100644 index 0000000..182d37b --- /dev/null +++ b/test/data/gradient.mvg @@ -0,0 +1,84 @@ +viewbox 0 0 1000 1000 +fill #aa88ff stroke none stroke-width 21 path 'M 83.3333 125 L 916.667 125 L 83.3333 162.5 Z ' +fill #aa88ff stroke none stroke-width 21 path 'M 916.667 125 L 916.667 162.5 L 83.3333 162.5 Z ' +fill #ab89fe stroke none stroke-width 21 path 'M 83.3333 162.5 L 916.667 162.5 L 83.3333 200 Z ' +fill #ad8bfd stroke none stroke-width 21 path 'M 916.667 162.5 L 916.667 200 L 83.3333 200 Z ' +fill #b08efb stroke none stroke-width 21 path 'M 916.667 200 L 916.667 237.5 L 83.3333 200 Z ' +fill #b18ffa stroke none stroke-width 21 path 'M 83.3333 200 L 916.667 237.5 L 83.3333 237.5 Z ' +fill #b593f8 stroke none stroke-width 21 path 'M 83.3333 237.5 L 916.667 237.5 L 83.3333 275 Z ' +fill #b694f7 stroke none stroke-width 21 path 'M 916.667 237.5 L 916.667 275 L 83.3333 275 Z ' +fill #b997f5 stroke none stroke-width 21 path 'M 83.3333 275 L 916.667 275 L 83.3333 312.5 Z ' +fill #bb99f4 stroke none stroke-width 21 path 'M 916.667 275 L 916.667 312.5 L 83.3333 312.5 Z ' +fill #be9cf2 stroke none stroke-width 21 path 'M 83.3333 312.5 L 916.667 312.5 L 83.3333 350 Z ' +fill #c09ef1 stroke none stroke-width 21 path 'M 916.667 312.5 L 916.667 350 L 83.3333 350 Z ' +fill #c3a1ef stroke none stroke-width 21 path 'M 83.3333 350 L 916.667 350 L 83.3333 387.5 Z ' +fill #c4a2ee stroke none stroke-width 21 path 'M 916.667 350 L 916.667 387.5 L 83.3333 387.5 Z ' +fill #c7a5ed stroke none stroke-width 21 path 'M 83.3333 387.5 L 916.667 387.5 L 83.3333 425 Z ' +fill #c9a7ec stroke none stroke-width 21 path 'M 916.667 387.5 L 916.667 425 L 83.3333 425 Z ' +fill #ccaaea stroke none stroke-width 21 path 'M 83.3333 425 L 916.667 425 L 83.3333 462.5 Z ' +fill #ceace9 stroke none stroke-width 21 path 'M 916.667 425 L 916.667 462.5 L 83.3333 462.5 Z ' +fill #d1afe7 stroke none stroke-width 21 path 'M 83.3333 462.5 L 916.667 462.5 L 83.3333 500 Z ' +fill #d2b0e6 stroke none stroke-width 21 path 'M 916.667 462.5 L 916.667 500 L 83.3333 500 Z ' +fill #d6b4e4 stroke none stroke-width 21 path 'M 83.3333 500 L 916.667 500 L 83.3333 537.5 Z ' +fill #d7b5e3 stroke none stroke-width 21 path 'M 916.667 500 L 916.667 537.5 L 83.3333 537.5 Z ' +fill #dab8e1 stroke none stroke-width 21 path 'M 83.3333 537.5 L 916.667 537.5 L 83.3333 575 Z ' +fill #dcbae0 stroke none stroke-width 21 path 'M 916.667 537.5 L 916.667 575 L 83.3333 575 Z ' +fill #dfbdde stroke none stroke-width 21 path 'M 83.3333 575 L 916.667 575 L 83.3333 612.5 Z ' +fill #e1bfdd stroke none stroke-width 21 path 'M 916.667 575 L 916.667 612.5 L 83.3333 612.5 Z ' +fill #e4c2dc stroke none stroke-width 21 path 'M 83.3333 612.5 L 916.667 612.5 L 83.3333 650 Z ' +fill #e5c3db stroke none stroke-width 21 path 'M 916.667 612.5 L 916.667 650 L 83.3333 650 Z ' +fill #e8c6d9 stroke none stroke-width 21 path 'M 83.3333 650 L 916.667 650 L 83.3333 687.5 Z ' +fill #eac8d8 stroke none stroke-width 21 path 'M 916.667 650 L 916.667 687.5 L 83.3333 687.5 Z ' +fill #edcbd6 stroke none stroke-width 21 path 'M 83.3333 687.5 L 916.667 687.5 L 83.3333 725 Z ' +fill #efcdd5 stroke none stroke-width 21 path 'M 916.667 687.5 L 916.667 725 L 83.3333 725 Z ' +fill #f2d0d3 stroke none stroke-width 21 path 'M 83.3333 725 L 916.667 725 L 83.3333 762.5 Z ' +fill #f3d1d2 stroke none stroke-width 21 path 'M 916.667 725 L 916.667 762.5 L 83.3333 762.5 Z ' +fill #f7d5d0 stroke none stroke-width 21 path 'M 83.3333 762.5 L 916.667 762.5 L 83.3333 800 Z ' +fill #f8d6cf stroke none stroke-width 21 path 'M 916.667 762.5 L 916.667 800 L 83.3333 800 Z ' +fill #fbd9cd stroke none stroke-width 21 path 'M 83.3333 800 L 916.667 800 L 83.3333 837.5 Z ' +fill #fddbcc stroke none stroke-width 21 path 'M 916.667 800 L 916.667 837.5 L 83.3333 837.5 Z ' +fill #ffddcc stroke none stroke-width 21 path 'M 83.3333 837.5 L 916.667 837.5 L 83.3333 875 Z ' +fill #ffddcc stroke none stroke-width 21 path 'M 916.667 837.5 L 916.667 875 L 83.3333 875 Z ' +fill none stroke #ff6600 stroke-width 21 path 'M 83.3333 125 L 125 125 Z ' +fill none stroke #fe6a02 stroke-width 21 path 'M 125 125 L 166.667 125 Z ' +fill none stroke #ff7208 stroke-width 21 path 'M 166.667 125 L 208.333 125 Z ' +fill none stroke #ff7b0e stroke-width 21 path 'M 208.333 125 L 250 125 Z ' +fill none stroke #ff8313 stroke-width 21 path 'M 250 125 L 291.667 125 Z ' +fill none stroke #ff8c19 stroke-width 21 path 'M 291.667 125 L 333.333 125 Z ' +fill none stroke #ff941f stroke-width 21 path 'M 333.333 125 L 375 125 Z ' +fill none stroke #ff9d24 stroke-width 21 path 'M 375 125 L 416.667 125 Z ' +fill none stroke #ffa52a stroke-width 21 path 'M 416.667 125 L 458.333 125 Z ' +fill none stroke #ffae30 stroke-width 21 path 'M 458.333 125 L 500 125 Z ' +fill none stroke #ffb635 stroke-width 21 path 'M 500 125 L 541.667 125 Z ' +fill none stroke #ffbf3b stroke-width 21 path 'M 541.667 125 L 583.333 125 Z ' +fill none stroke #ffc741 stroke-width 21 path 'M 583.333 125 L 625 125 Z ' +fill none stroke #ffd046 stroke-width 21 path 'M 625 125 L 666.667 125 Z ' +fill none stroke #ffd84c stroke-width 21 path 'M 666.667 125 L 708.333 125 Z ' +fill none stroke #ffe152 stroke-width 21 path 'M 708.333 125 L 750 125 Z ' +fill none stroke #ffe957 stroke-width 21 path 'M 750 125 L 791.667 125 Z ' +fill none stroke #fff25d stroke-width 21 path 'M 791.667 125 L 833.333 125 Z ' +fill none stroke #fffa63 stroke-width 21 path 'M 833.333 125 L 875 125 Z ' +fill none stroke #ffff66 stroke-width 21 path 'M 875 125 L 916.667 125 Z ' +fill none stroke #ffff66 stroke-width 21 path 'M 916.667 125 L 916.667 875 Z ' +fill none stroke #ff6600 stroke-width 21 path 'M 83.3333 125 L 83.3333 875 Z ' +fill none stroke #ff6600 stroke-width 21 path 'M 83.3333 875 L 125 875 Z ' +fill none stroke #ff6a02 stroke-width 21 path 'M 125 875 L 166.667 875 Z ' +fill none stroke #ff7208 stroke-width 21 path 'M 166.667 875 L 208.333 875 Z ' +fill none stroke #ff7b0e stroke-width 21 path 'M 208.333 875 L 250 875 Z ' +fill none stroke #ff8313 stroke-width 21 path 'M 250 875 L 291.667 875 Z ' +fill none stroke #ff8c19 stroke-width 21 path 'M 291.667 875 L 333.333 875 Z ' +fill none stroke #ff941f stroke-width 21 path 'M 333.333 875 L 375 875 Z ' +fill none stroke #ff9d24 stroke-width 21 path 'M 375 875 L 416.667 875 Z ' +fill none stroke #ffa52a stroke-width 21 path 'M 416.667 875 L 458.333 875 Z ' +fill none stroke #ffae30 stroke-width 21 path 'M 458.333 875 L 500 875 Z ' +fill none stroke #ffb635 stroke-width 21 path 'M 500 875 L 541.667 875 Z ' +fill none stroke #ffbf3b stroke-width 21 path 'M 541.667 875 L 583.333 875 Z ' +fill none stroke #ffc741 stroke-width 21 path 'M 583.333 875 L 625 875 Z ' +fill none stroke #ffd046 stroke-width 21 path 'M 625 875 L 666.667 875 Z ' +fill none stroke #ffd84c stroke-width 21 path 'M 666.667 875 L 708.333 875 Z ' +fill none stroke #ffe152 stroke-width 21 path 'M 708.333 875 L 750 875 Z ' +fill none stroke #ffe957 stroke-width 21 path 'M 750 875 L 791.667 875 Z ' +fill none stroke #fff25d stroke-width 21 path 'M 791.667 875 L 833.333 875 Z ' +fill none stroke #fffa63 stroke-width 21 path 'M 833.333 875 L 875 875 Z ' +fill none stroke #ffff66 stroke-width 21 path 'M 875 875 L 916.667 875 Z ' +fill none stroke #ffff66 stroke-width 21 path 'M 916.667 875 L 916.667 125 Z ' diff --git a/test/data/gradient.svg b/test/data/gradient.svg new file mode 100644 index 0000000..f093370 --- /dev/null +++ b/test/data/gradient.svg @@ -0,0 +1,25 @@ + + + + Example lingrag01 + Fill a rectangle using a linear-gradient paint server. + + + + + + + + + + + + + + + -- 2.49.0