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