]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - svgtiny_gradient.c
c88c8044b75b0897f4c5938d56b946cbffaa6fb6
[libsvgtiny.git] / svgtiny_gradient.c
1 /*
2 * This file is part of Libsvgtiny
3 * Licensed under the MIT License,
4 * http://opensource.org/licenses/mit-license.php
5 * Copyright 2008 James Bursa <james@semichrome.net>
6 */
7
8 #define _GNU_SOURCE /* for strndup */
9 #include <assert.h>
10 #include <string.h>
11 #include "svgtiny.h"
12 #include "svgtiny_internal.h"
13
14 #undef GRADIENT_DEBUG
15
16 static svgtiny_code svgtiny_parse_linear_gradient(xmlNode *linear,
17 struct svgtiny_parse_state *state);
18 static float svgtiny_parse_gradient_offset(const char *s);
19 static void svgtiny_path_bbox(float *p, unsigned int n,
20 float *x0, float *y0, float *x1, float *y1);
21
22
23 /**
24 * Find a gradient by id and parse it.
25 */
26
27 void svgtiny_find_gradient(const char *id, struct svgtiny_parse_state *state)
28 {
29 fprintf(stderr, "svgtiny_find_gradient: id \"%s\"\n", id);
30
31 state->linear_gradient_stop_count = 0;
32 state->gradient_x1 = "0%";
33 state->gradient_y1 = "0%";
34 state->gradient_x2 = "100%";
35 state->gradient_y2 = "0%";
36
37 xmlNode *gradient = svgtiny_find_element_by_id(
38 (xmlNode *) state->document, id);
39 fprintf(stderr, "gradient %p\n", gradient);
40 if (!gradient) {
41 fprintf(stderr, "gradient \"%s\" not found\n", id);
42 return;
43 }
44
45 fprintf(stderr, "gradient name \"%s\"\n", gradient->name);
46 if (strcmp((const char *) gradient->name, "linearGradient") == 0) {
47 svgtiny_parse_linear_gradient(gradient, state);
48 }
49 }
50
51
52 /**
53 * Parse a <linearGradient> element node.
54 *
55 * http://www.w3.org/TR/SVG11/pservers#LinearGradients
56 */
57
58 svgtiny_code svgtiny_parse_linear_gradient(xmlNode *linear,
59 struct svgtiny_parse_state *state)
60 {
61 xmlAttr *href = xmlHasProp(linear, (const xmlChar *) "href");
62 if (href && href->children->content[0] == '#')
63 svgtiny_find_gradient((const char *) href->children->content
64 + 1, state);
65
66 for (xmlAttr *attr = linear->properties; attr; attr = attr->next) {
67 const char *name = (const char *) attr->name;
68 const char *content = (const char *) attr->children->content;
69 if (strcmp(name, "x1") == 0)
70 state->gradient_x1 = content;
71 else if (strcmp(name, "y1") == 0)
72 state->gradient_y1 = content;
73 else if (strcmp(name, "x2") == 0)
74 state->gradient_x2 = content;
75 else if (strcmp(name, "y2") == 0)
76 state->gradient_y2 = content;
77 }
78
79 unsigned int i = 0;
80 for (xmlNode *stop = linear->children; stop; stop = stop->next) {
81 float offset = -1;
82 svgtiny_colour color = svgtiny_TRANSPARENT;
83
84 if (stop->type != XML_ELEMENT_NODE)
85 continue;
86 if (strcmp((const char *) stop->name, "stop") != 0)
87 continue;
88
89 for (xmlAttr *attr = stop->properties; attr;
90 attr = attr->next) {
91 const char *name = (const char *) attr->name;
92 const char *content =
93 (const char *) attr->children->content;
94 if (strcmp(name, "offset") == 0)
95 offset = svgtiny_parse_gradient_offset(content);
96 else if (strcmp(name, "stop-color") == 0)
97 svgtiny_parse_color(content, &color, state);
98 else if (strcmp(name, "style") == 0) {
99 const char *s;
100 char *value;
101 if ((s = strstr(content, "stop-color:"))) {
102 s += 11;
103 while (*s == ' ')
104 s++;
105 value = strndup(s, strcspn(s, "; "));
106 svgtiny_parse_color(value, &color,
107 state);
108 free(value);
109 }
110 }
111 }
112
113 if (offset != -1 && color != svgtiny_TRANSPARENT) {
114 fprintf(stderr, "stop %g %x\n", offset, color);
115 state->gradient_stop[i].offset = offset;
116 state->gradient_stop[i].color = color;
117 i++;
118 }
119
120 if (i == svgtiny_MAX_STOPS)
121 break;
122 }
123
124 if (i)
125 state->linear_gradient_stop_count = i;
126
127 return svgtiny_OK;
128 }
129
130
131 float svgtiny_parse_gradient_offset(const char *s)
132 {
133 int num_length = strspn(s, "0123456789+-.");
134 const char *unit = s + num_length;
135 float n = atof((const char *) s);
136
137 if (unit[0] == 0)
138 ;
139 else if (unit[0] == '%')
140 n /= 100.0;
141 else
142 return -1;
143
144 if (n < 0)
145 n = 0;
146 if (1 < n)
147 n = 1;
148 return n;
149 }
150
151
152 /**
153 * Add a path with a linear gradient fill to the svgtiny_diagram.
154 */
155
156 svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
157 struct svgtiny_parse_state *state)
158 {
159 /* determine object bounding box */
160 float object_x0, object_y0, object_x1, object_y1;
161 svgtiny_path_bbox(p, n, &object_x0, &object_y0, &object_x1, &object_y1);
162 #ifdef GRADIENT_DEBUG
163 fprintf(stderr, "object bbox: (%g %g) (%g %g)\n",
164 object_x0, object_y0, object_x1, object_y1);
165 #endif
166
167 /* compute gradient vector */
168 fprintf(stderr, "x1 %s, y1 %s, x2 %s, y2 %s\n",
169 state->gradient_x1, state->gradient_y1,
170 state->gradient_x2, state->gradient_y2);
171 float gradient_x0, gradient_y0, gradient_x1, gradient_y1,
172 gradient_dx, gradient_dy;
173 gradient_x0 = object_x0 + svgtiny_parse_length(state->gradient_x1,
174 object_x1 - object_x0, *state);
175 gradient_y0 = object_y0 + svgtiny_parse_length(state->gradient_y1,
176 object_y1 - object_y0, *state);
177 gradient_x1 = object_x0 + svgtiny_parse_length(state->gradient_x2,
178 object_x1 - object_x0, *state);
179 gradient_y1 = object_y0 + svgtiny_parse_length(state->gradient_y2,
180 object_y1 - object_y0, *state);
181 gradient_dx = gradient_x1 - gradient_x0;
182 gradient_dy = gradient_y1 - gradient_y0;
183 #ifdef GRADIENT_DEBUG
184 fprintf(stderr, "gradient vector: (%g %g) => (%g %g)\n",
185 gradient_x0, gradient_y0, gradient_x1, gradient_y1);
186 #endif
187
188 /* show theoretical gradient strips for debugging */
189 /*unsigned int strips = 10;
190 for (unsigned int z = 0; z != strips; z++) {
191 float f0, fd, strip_x0, strip_y0, strip_dx, strip_dy;
192 f0 = (float) z / (float) strips;
193 fd = (float) 1 / (float) strips;
194 strip_x0 = gradient_x0 + f0 * gradient_dx;
195 strip_y0 = gradient_y0 + f0 * gradient_dy;
196 strip_dx = fd * gradient_dx;
197 strip_dy = fd * gradient_dy;
198 fprintf(stderr, "strip %i vector: (%g %g) + (%g %g)\n",
199 z, strip_x0, strip_y0, strip_dx, strip_dy);
200
201 float *p = malloc(13 * sizeof p[0]);
202 if (!p)
203 return svgtiny_OUT_OF_MEMORY;
204 p[0] = svgtiny_PATH_MOVE;
205 p[1] = strip_x0 + (strip_dy * 3);
206 p[2] = strip_y0 - (strip_dx * 3);
207 p[3] = svgtiny_PATH_LINE;
208 p[4] = p[1] + strip_dx;
209 p[5] = p[2] + strip_dy;
210 p[6] = svgtiny_PATH_LINE;
211 p[7] = p[4] - (strip_dy * 6);
212 p[8] = p[5] + (strip_dx * 6);
213 p[9] = svgtiny_PATH_LINE;
214 p[10] = p[7] - strip_dx;
215 p[11] = p[8] - strip_dy;
216 p[12] = svgtiny_PATH_CLOSE;
217 svgtiny_transform_path(p, 13, state);
218 struct svgtiny_shape *shape = svgtiny_add_shape(state);
219 if (!shape) {
220 free(p);
221 return svgtiny_OUT_OF_MEMORY;
222 }
223 shape->path = p;
224 shape->path_length = 13;
225 shape->fill = svgtiny_TRANSPARENT;
226 shape->stroke = svgtiny_RGB(0, 0xff, 0);
227 state->diagram->shape_count++;
228 }*/
229
230 /* compute points on the path for triangle vertices */
231 unsigned int steps = 10;
232 float x0, y0, x1, y1;
233 float gradient_norm_squared = gradient_dx * gradient_dx +
234 gradient_dy * gradient_dy;
235 struct grad_point {
236 float x, y, r;
237 };
238 struct grad_point *pts = malloc(n * steps * sizeof pts[0]);
239 if (!pts)
240 return svgtiny_OUT_OF_MEMORY;
241 unsigned int pts_count = 0;
242 float min_r = 1000;
243 unsigned int min_pt = 0;
244 for (unsigned int j = 0; j != n; ) {
245 switch ((int) p[j]) {
246 case svgtiny_PATH_MOVE:
247 x0 = p[j + 1];
248 y0 = p[j + 2];
249 j += 3;
250 break;
251 case svgtiny_PATH_LINE:
252 case svgtiny_PATH_CLOSE:
253 if (((int) p[j]) == svgtiny_PATH_LINE) {
254 x1 = p[j + 1];
255 y1 = p[j + 2];
256 j += 3;
257 } else {
258 x1 = p[1];
259 y1 = p[2];
260 j++;
261 }
262 fprintf(stderr, "line: ");
263 for (unsigned int z = 0; z != steps; z++) {
264 float f, x, y, r;
265 f = (float) z / (float) steps;
266 x = x0 + f * (x1 - x0);
267 y = y0 + f * (y1 - y0);
268 r = ((x - gradient_x0) * gradient_dx +
269 (y - gradient_y0) * gradient_dy) /
270 gradient_norm_squared;
271 fprintf(stderr, "(%g %g [%g]) ", x, y, r);
272 pts[pts_count].x = x;
273 pts[pts_count].y = y;
274 pts[pts_count].r = r;
275 if (r < min_r) {
276 min_r = r;
277 min_pt = pts_count;
278 }
279 pts_count++;
280 }
281 fprintf(stderr, "\n");
282 x0 = x1;
283 y0 = y1;
284 break;
285 case svgtiny_PATH_BEZIER:
286 fprintf(stderr, "bezier: ");
287 for (unsigned int z = 0; z != steps; z++) {
288 float t, x, y, r;
289 t = (float) z / (float) steps;
290 x = (1-t) * (1-t) * (1-t) * x0 +
291 3 * t * (1-t) * (1-t) * p[j + 1] +
292 3 * t * t * (1-t) * p[j + 3] +
293 t * t * t * p[j + 5];
294 y = (1-t) * (1-t) * (1-t) * y0 +
295 3 * t * (1-t) * (1-t) * p[j + 2] +
296 3 * t * t * (1-t) * p[j + 4] +
297 t * t * t * p[j + 6];
298 r = ((x - gradient_x0) * gradient_dx +
299 (y - gradient_y0) * gradient_dy) /
300 gradient_norm_squared;
301 fprintf(stderr, "(%g %g [%g]) ", x, y, r);
302 pts[pts_count].x = x;
303 pts[pts_count].y = y;
304 pts[pts_count].r = r;
305 if (r < min_r) {
306 min_r = r;
307 min_pt = pts_count;
308 }
309 pts_count++;
310 }
311 fprintf(stderr, "\n");
312 x0 = p[j + 5];
313 y0 = p[j + 6];
314 j += 7;
315 break;
316 default:
317 assert(0);
318 }
319 }
320 fprintf(stderr, "pts_count %i, min_pt %i, min_r %.3f\n",
321 pts_count, min_pt, min_r);
322
323 unsigned int stop_count = state->linear_gradient_stop_count;
324 assert(2 <= stop_count);
325 unsigned int current_stop = 0;
326 float last_stop_r = 0;
327 float current_stop_r = state->gradient_stop[0].offset;
328 int red0, green0, blue0, red1, green1, blue1;
329 red0 = red1 = svgtiny_RED(state->gradient_stop[0].color);
330 green0 = green1 = svgtiny_GREEN(state->gradient_stop[0].color);
331 blue0 = blue1 = svgtiny_BLUE(state->gradient_stop[0].color);
332 unsigned int t, a, b;
333 t = min_pt;
334 a = (min_pt + 1) % pts_count;
335 b = min_pt == 0 ? pts_count - 1 : min_pt - 1;
336 while (a != b) {
337 float mean_r = (pts[t].r + pts[a].r + pts[b].r) / 3;
338 /*fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f "
339 "mean_r %.3f\n",
340 t, pts[t].r, a, pts[a].r, b, pts[b].r,
341 mean_r);*/
342 while (current_stop != stop_count && current_stop_r < mean_r) {
343 current_stop++;
344 if (current_stop == stop_count)
345 break;
346 red0 = red1;
347 green0 = green1;
348 blue0 = blue1;
349 red1 = svgtiny_RED(state->
350 gradient_stop[current_stop].color);
351 green1 = svgtiny_GREEN(state->
352 gradient_stop[current_stop].color);
353 blue1 = svgtiny_BLUE(state->
354 gradient_stop[current_stop].color);
355 last_stop_r = current_stop_r;
356 current_stop_r = state->
357 gradient_stop[current_stop].offset;
358 }
359 float *p = malloc(10 * sizeof p[0]);
360 if (!p)
361 return svgtiny_OUT_OF_MEMORY;
362 p[0] = svgtiny_PATH_MOVE;
363 p[1] = pts[t].x;
364 p[2] = pts[t].y;
365 p[3] = svgtiny_PATH_LINE;
366 p[4] = pts[a].x;
367 p[5] = pts[a].y;
368 p[6] = svgtiny_PATH_LINE;
369 p[7] = pts[b].x;
370 p[8] = pts[b].y;
371 p[9] = svgtiny_PATH_CLOSE;
372 svgtiny_transform_path(p, 10, state);
373 struct svgtiny_shape *shape = svgtiny_add_shape(state);
374 if (!shape) {
375 free(p);
376 return svgtiny_OUT_OF_MEMORY;
377 }
378 shape->path = p;
379 shape->path_length = 10;
380 /*shape->fill = svgtiny_TRANSPARENT;*/
381 if (current_stop == 0)
382 shape->fill = state->gradient_stop[0].color;
383 else if (current_stop == stop_count)
384 shape->fill = state->
385 gradient_stop[stop_count - 1].color;
386 else {
387 float stop_r = (mean_r - last_stop_r) /
388 (current_stop_r - last_stop_r);
389 shape->fill = svgtiny_RGB(
390 (int) ((1 - stop_r) * red0 + stop_r * red1),
391 (int) ((1 - stop_r) * green0 + stop_r * green1),
392 (int) ((1 - stop_r) * blue0 + stop_r * blue1));
393 }
394 shape->stroke = svgtiny_TRANSPARENT;
395 #ifdef GRADIENT_DEBUG
396 shape->stroke = svgtiny_RGB(0, 0, 0xff);
397 #endif
398 state->diagram->shape_count++;
399 if (pts[a].r < pts[b].r) {
400 t = a;
401 a = (a + 1) % pts_count;
402 } else {
403 t = b;
404 b = b == 0 ? pts_count - 1 : b - 1;
405 }
406 }
407
408 /* render gradient vector for debugging */
409 #ifdef GRADIENT_DEBUG
410 {
411 float *p = malloc(7 * sizeof p[0]);
412 if (!p)
413 return svgtiny_OUT_OF_MEMORY;
414 p[0] = svgtiny_PATH_MOVE;
415 p[1] = gradient_x0;
416 p[2] = gradient_y0;
417 p[3] = svgtiny_PATH_LINE;
418 p[4] = gradient_x1;
419 p[5] = gradient_y1;
420 p[6] = svgtiny_PATH_CLOSE;
421 svgtiny_transform_path(p, 7, state);
422 struct svgtiny_shape *shape = svgtiny_add_shape(state);
423 if (!shape) {
424 free(p);
425 return svgtiny_OUT_OF_MEMORY;
426 }
427 shape->path = p;
428 shape->path_length = 7;
429 shape->fill = svgtiny_TRANSPARENT;
430 shape->stroke = svgtiny_RGB(0xff, 0, 0);
431 state->diagram->shape_count++;
432 }
433 #endif
434
435 /* render triangle vertices with r values for debugging */
436 #ifdef GRADIENT_DEBUG
437 for (unsigned int i = 0; i != pts_count; i++) {
438 struct svgtiny_shape *shape = svgtiny_add_shape(state);
439 if (!shape)
440 return svgtiny_OUT_OF_MEMORY;
441 char *text = malloc(20);
442 if (!text)
443 return svgtiny_OUT_OF_MEMORY;
444 sprintf(text, "%i=%.3f", i, pts[i].r);
445 shape->text = text;
446 shape->text_x = state->ctm.a * pts[i].x +
447 state->ctm.c * pts[i].y + state->ctm.e;
448 shape->text_y = state->ctm.b * pts[i].x +
449 state->ctm.d * pts[i].y + state->ctm.f;
450 shape->fill = svgtiny_RGB(0, 0, 0);
451 state->diagram->shape_count++;
452 }
453 #endif
454
455 /* plot actual path outline */
456 if (state->stroke != svgtiny_TRANSPARENT) {
457 svgtiny_transform_path(p, n, state);
458
459 struct svgtiny_shape *shape = svgtiny_add_shape(state);
460 if (!shape) {
461 free(p);
462 return svgtiny_OUT_OF_MEMORY;
463 }
464 shape->path = p;
465 shape->path_length = n;
466 shape->fill = svgtiny_TRANSPARENT;
467 state->diagram->shape_count++;
468 }
469
470 return svgtiny_OK;
471 }
472
473
474 /**
475 * Get the bounding box of path.
476 */
477
478 void svgtiny_path_bbox(float *p, unsigned int n,
479 float *x0, float *y0, float *x1, float *y1)
480 {
481 *x0 = *x1 = p[1];
482 *y0 = *y1 = p[2];
483
484 for (unsigned int j = 0; j != n; ) {
485 unsigned int points = 0;
486 switch ((int) p[j]) {
487 case svgtiny_PATH_MOVE:
488 case svgtiny_PATH_LINE:
489 points = 1;
490 break;
491 case svgtiny_PATH_CLOSE:
492 points = 0;
493 break;
494 case svgtiny_PATH_BEZIER:
495 points = 3;
496 break;
497 default:
498 assert(0);
499 }
500 j++;
501 for (unsigned int k = 0; k != points; k++) {
502 float x = p[j], y = p[j + 1];
503 if (x < *x0)
504 *x0 = x;
505 else if (*x1 < x)
506 *x1 = x;
507 if (y < *y0)
508 *y0 = y;
509 else if (*y1 < y)
510 *y1 = y;
511 j += 2;
512 }
513 }
514 }
515
516
517 /**
518 * Find an element in the document by id.
519 */
520
521 xmlNode *svgtiny_find_element_by_id(xmlNode *node, const char *id)
522 {
523 xmlNode *child;
524 xmlNode *found;
525
526 for (child = node->children; child; child = child->next) {
527 if (child->type != XML_ELEMENT_NODE)
528 continue;
529 xmlAttr *attr = xmlHasProp(child, (const xmlChar *) "id");
530 if (attr && strcmp(id, (const char *) attr->children->content)
531 == 0)
532 return child;
533 found = svgtiny_find_element_by_id(child, id);
534 if (found)
535 return found;
536 }
537
538 return 0;
539 }
540