]> gitweb.michael.orlitzky.com - libsvgtiny.git/blob - src/svgtiny_gradient.c
More work towards libdom conversion
[libsvgtiny.git] / src / 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 #include <assert.h>
9 #include <math.h>
10 #include <string.h>
11 #include <stdio.h>
12
13 #include "svgtiny.h"
14 #include "svgtiny_internal.h"
15
16 #undef GRADIENT_DEBUG
17
18 static svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear,
19 struct svgtiny_parse_state *state);
20 static float svgtiny_parse_gradient_offset(const char *s);
21 static void svgtiny_path_bbox(float *p, unsigned int n,
22 float *x0, float *y0, float *x1, float *y1);
23 static void svgtiny_invert_matrix(float *m, float *inv);
24
25
26 /**
27 * Find a gradient by id and parse it.
28 */
29
30 void svgtiny_find_gradient(const char *id, struct svgtiny_parse_state *state)
31 {
32 dom_element *gradient;
33 dom_string *id_str;
34 dom_exception exc;
35
36 fprintf(stderr, "svgtiny_find_gradient: id \"%s\"\n", id);
37
38 state->linear_gradient_stop_count = 0;
39 state->gradient_x1 = "0%";
40 state->gradient_y1 = "0%";
41 state->gradient_x2 = "100%";
42 state->gradient_y2 = "0%";
43 state->gradient_user_space_on_use = false;
44 state->gradient_transform.a = 1;
45 state->gradient_transform.b = 0;
46 state->gradient_transform.c = 0;
47 state->gradient_transform.d = 1;
48 state->gradient_transform.e = 0;
49 state->gradient_transform.f = 0;
50
51 exc = dom_string_create_interned((const uint8_t *) id, strlen(id),
52 &id_str);
53 if (exc != DOM_NO_ERR)
54 return;
55
56 exc = dom_document_get_element_by_id(state->document, id_str,
57 &gradient);
58 dom_string_unref(id_str);
59 if (exc != DOM_NO_ERR)
60 return;
61
62 if (gradient == NULL) {
63 fprintf(stderr, "gradient \"%s\" not found\n", id);
64 return;
65 }
66
67 exc = dom_node_get_node_name(gradient, &id_str);
68 if (exc != DOM_NO_ERR) {
69 dom_node_unref(gradient);
70 return;
71 }
72
73 if (dom_string_isequal(id_str, state->interned_linearGradient))
74 svgtiny_parse_linear_gradient(gradient, state);
75
76 dom_string_unref(id_str);
77 dom_node_unref(gradient);
78 }
79
80
81 /**
82 * Parse a <linearGradient> element node.
83 *
84 * http://www.w3.org/TR/SVG11/pservers#LinearGradients
85 */
86
87 svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear,
88 struct svgtiny_parse_state *state)
89 {
90 unsigned int i = 0;
91 dom_element *stop;
92 dom_string *attr;
93 dom_exception exc;
94
95 exc = dom_element_get_attribute(linear, state->interned_href, &attr);
96 if (exc == DOM_NO_ERR && attr != NULL) {
97 if (dom_string_data(attr)[0] == (uint8_t) '#') {
98 char *s = strndup(dom_string_data(attr) + 1,
99 dom_string_length(attr) - 1);
100 svgtiny_find_gradient(s, state);
101 free(s);
102 }
103 dom_string_unref(attr);
104 }
105
106 for (attr = linear->properties; attr; attr = attr->next) {
107 const char *name = (const char *) attr->name;
108 const char *content = (const char *) attr->children->content;
109 if (strcmp(name, "x1") == 0)
110 state->gradient_x1 = content;
111 else if (strcmp(name, "y1") == 0)
112 state->gradient_y1 = content;
113 else if (strcmp(name, "x2") == 0)
114 state->gradient_x2 = content;
115 else if (strcmp(name, "y2") == 0)
116 state->gradient_y2 = content;
117 else if (strcmp(name, "gradientUnits") == 0)
118 state->gradient_user_space_on_use =
119 strcmp(content, "userSpaceOnUse") == 0;
120 else if (strcmp(name, "gradientTransform") == 0) {
121 float a = 1, b = 0, c = 0, d = 1, e = 0, f = 0;
122 char *s = strdup(content);
123 if (!s)
124 return svgtiny_OUT_OF_MEMORY;
125 svgtiny_parse_transform(s, &a, &b, &c, &d, &e, &f);
126 free(s);
127 fprintf(stderr, "transform %g %g %g %g %g %g\n",
128 a, b, c, d, e, f);
129 state->gradient_transform.a = a;
130 state->gradient_transform.b = b;
131 state->gradient_transform.c = c;
132 state->gradient_transform.d = d;
133 state->gradient_transform.e = e;
134 state->gradient_transform.f = f;
135 }
136 }
137
138 for (stop = linear->children; stop; stop = stop->next) {
139 float offset = -1;
140 svgtiny_colour color = svgtiny_TRANSPARENT;
141
142 if (stop->type != XML_ELEMENT_NODE)
143 continue;
144 if (strcmp((const char *) stop->name, "stop") != 0)
145 continue;
146
147 for (attr = stop->properties; attr;
148 attr = attr->next) {
149 const char *name = (const char *) attr->name;
150 const char *content =
151 (const char *) attr->children->content;
152 if (strcmp(name, "offset") == 0)
153 offset = svgtiny_parse_gradient_offset(content);
154 else if (strcmp(name, "stop-color") == 0)
155 svgtiny_parse_color(content, &color, state);
156 else if (strcmp(name, "style") == 0) {
157 const char *s;
158 char *value;
159 if ((s = strstr(content, "stop-color:"))) {
160 s += 11;
161 while (*s == ' ')
162 s++;
163 value = strndup(s, strcspn(s, "; "));
164 svgtiny_parse_color(value, &color,
165 state);
166 free(value);
167 }
168 }
169 }
170
171 if (offset != -1 && color != svgtiny_TRANSPARENT) {
172 fprintf(stderr, "stop %g %x\n", offset, color);
173 state->gradient_stop[i].offset = offset;
174 state->gradient_stop[i].color = color;
175 i++;
176 }
177
178 if (i == svgtiny_MAX_STOPS)
179 break;
180 }
181
182 if (i)
183 state->linear_gradient_stop_count = i;
184
185 return svgtiny_OK;
186 }
187
188
189 float svgtiny_parse_gradient_offset(const char *s)
190 {
191 int num_length = strspn(s, "0123456789+-.");
192 const char *unit = s + num_length;
193 float n = atof((const char *) s);
194
195 if (unit[0] == 0)
196 ;
197 else if (unit[0] == '%')
198 n /= 100.0;
199 else
200 return -1;
201
202 if (n < 0)
203 n = 0;
204 if (1 < n)
205 n = 1;
206 return n;
207 }
208
209
210 /**
211 * Add a path with a linear gradient fill to the svgtiny_diagram.
212 */
213
214 svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
215 struct svgtiny_parse_state *state)
216 {
217 struct grad_point {
218 float x, y, r;
219 };
220 float object_x0, object_y0, object_x1, object_y1;
221 float gradient_x0, gradient_y0, gradient_x1, gradient_y1,
222 gradient_dx, gradient_dy;
223 float trans[6];
224 unsigned int steps = 10;
225 float x0 = 0, y0 = 0, x0_trans, y0_trans, r0; /* segment start point */
226 float x1, y1, x1_trans, y1_trans, r1; /* segment end point */
227 /* segment control points (beziers only) */
228 float c0x = 0, c0y = 0, c1x = 0, c1y = 0;
229 float gradient_norm_squared;
230 struct svgtiny_list *pts;
231 float min_r = 1000;
232 unsigned int min_pt = 0;
233 unsigned int j;
234 unsigned int stop_count;
235 unsigned int current_stop;
236 float last_stop_r;
237 float current_stop_r;
238 int red0, green0, blue0, red1, green1, blue1;
239 unsigned int t, a, b;
240
241 /* determine object bounding box */
242 svgtiny_path_bbox(p, n, &object_x0, &object_y0, &object_x1, &object_y1);
243 #ifdef GRADIENT_DEBUG
244 fprintf(stderr, "object bbox: (%g %g) (%g %g)\n",
245 object_x0, object_y0, object_x1, object_y1);
246 #endif
247
248 /* compute gradient vector */
249 fprintf(stderr, "x1 %s, y1 %s, x2 %s, y2 %s\n",
250 state->gradient_x1, state->gradient_y1,
251 state->gradient_x2, state->gradient_y2);
252 if (!state->gradient_user_space_on_use) {
253 gradient_x0 = object_x0 +
254 svgtiny_parse_length(state->gradient_x1,
255 object_x1 - object_x0, *state);
256 gradient_y0 = object_y0 +
257 svgtiny_parse_length(state->gradient_y1,
258 object_y1 - object_y0, *state);
259 gradient_x1 = object_x0 +
260 svgtiny_parse_length(state->gradient_x2,
261 object_x1 - object_x0, *state);
262 gradient_y1 = object_y0 +
263 svgtiny_parse_length(state->gradient_y2,
264 object_y1 - object_y0, *state);
265 } else {
266 gradient_x0 = svgtiny_parse_length(state->gradient_x1,
267 state->viewport_width, *state);
268 gradient_y0 = svgtiny_parse_length(state->gradient_y1,
269 state->viewport_height, *state);
270 gradient_x1 = svgtiny_parse_length(state->gradient_x2,
271 state->viewport_width, *state);
272 gradient_y1 = svgtiny_parse_length(state->gradient_y2,
273 state->viewport_height, *state);
274 }
275 gradient_dx = gradient_x1 - gradient_x0;
276 gradient_dy = gradient_y1 - gradient_y0;
277 #ifdef GRADIENT_DEBUG
278 fprintf(stderr, "gradient vector: (%g %g) => (%g %g)\n",
279 gradient_x0, gradient_y0, gradient_x1, gradient_y1);
280 #endif
281
282 /* show theoretical gradient strips for debugging */
283 /*unsigned int strips = 10;
284 for (unsigned int z = 0; z != strips; z++) {
285 float f0, fd, strip_x0, strip_y0, strip_dx, strip_dy;
286 f0 = (float) z / (float) strips;
287 fd = (float) 1 / (float) strips;
288 strip_x0 = gradient_x0 + f0 * gradient_dx;
289 strip_y0 = gradient_y0 + f0 * gradient_dy;
290 strip_dx = fd * gradient_dx;
291 strip_dy = fd * gradient_dy;
292 fprintf(stderr, "strip %i vector: (%g %g) + (%g %g)\n",
293 z, strip_x0, strip_y0, strip_dx, strip_dy);
294
295 float *p = malloc(13 * sizeof p[0]);
296 if (!p)
297 return svgtiny_OUT_OF_MEMORY;
298 p[0] = svgtiny_PATH_MOVE;
299 p[1] = strip_x0 + (strip_dy * 3);
300 p[2] = strip_y0 - (strip_dx * 3);
301 p[3] = svgtiny_PATH_LINE;
302 p[4] = p[1] + strip_dx;
303 p[5] = p[2] + strip_dy;
304 p[6] = svgtiny_PATH_LINE;
305 p[7] = p[4] - (strip_dy * 6);
306 p[8] = p[5] + (strip_dx * 6);
307 p[9] = svgtiny_PATH_LINE;
308 p[10] = p[7] - strip_dx;
309 p[11] = p[8] - strip_dy;
310 p[12] = svgtiny_PATH_CLOSE;
311 svgtiny_transform_path(p, 13, state);
312 struct svgtiny_shape *shape = svgtiny_add_shape(state);
313 if (!shape) {
314 free(p);
315 return svgtiny_OUT_OF_MEMORY;
316 }
317 shape->path = p;
318 shape->path_length = 13;
319 shape->fill = svgtiny_TRANSPARENT;
320 shape->stroke = svgtiny_RGB(0, 0xff, 0);
321 state->diagram->shape_count++;
322 }*/
323
324 /* invert gradient transform for applying to vertices */
325 svgtiny_invert_matrix(&state->gradient_transform.a, trans);
326 fprintf(stderr, "inverse transform %g %g %g %g %g %g\n",
327 trans[0], trans[1], trans[2], trans[3],
328 trans[4], trans[5]);
329
330 /* compute points on the path for triangle vertices */
331 /* r, r0, r1 are distance along gradient vector */
332 gradient_norm_squared = gradient_dx * gradient_dx +
333 gradient_dy * gradient_dy;
334 pts = svgtiny_list_create(
335 sizeof (struct grad_point));
336 if (!pts)
337 return svgtiny_OUT_OF_MEMORY;
338 for (j = 0; j != n; ) {
339 int segment_type = (int) p[j];
340 struct grad_point *point;
341 unsigned int z;
342
343 if (segment_type == svgtiny_PATH_MOVE) {
344 x0 = p[j + 1];
345 y0 = p[j + 2];
346 j += 3;
347 continue;
348 }
349
350 assert(segment_type == svgtiny_PATH_CLOSE ||
351 segment_type == svgtiny_PATH_LINE ||
352 segment_type == svgtiny_PATH_BEZIER);
353
354 /* start point (x0, y0) */
355 x0_trans = trans[0]*x0 + trans[2]*y0 + trans[4];
356 y0_trans = trans[1]*x0 + trans[3]*y0 + trans[5];
357 r0 = ((x0_trans - gradient_x0) * gradient_dx +
358 (y0_trans - gradient_y0) * gradient_dy) /
359 gradient_norm_squared;
360 point = svgtiny_list_push(pts);
361 if (!point) {
362 svgtiny_list_free(pts);
363 return svgtiny_OUT_OF_MEMORY;
364 }
365 point->x = x0;
366 point->y = y0;
367 point->r = r0;
368 if (r0 < min_r) {
369 min_r = r0;
370 min_pt = svgtiny_list_size(pts) - 1;
371 }
372
373 /* end point (x1, y1) */
374 if (segment_type == svgtiny_PATH_LINE) {
375 x1 = p[j + 1];
376 y1 = p[j + 2];
377 j += 3;
378 } else if (segment_type == svgtiny_PATH_CLOSE) {
379 x1 = p[1];
380 y1 = p[2];
381 j++;
382 } else /* svgtiny_PATH_BEZIER */ {
383 c0x = p[j + 1];
384 c0y = p[j + 2];
385 c1x = p[j + 3];
386 c1y = p[j + 4];
387 x1 = p[j + 5];
388 y1 = p[j + 6];
389 j += 7;
390 }
391 x1_trans = trans[0]*x1 + trans[2]*y1 + trans[4];
392 y1_trans = trans[1]*x1 + trans[3]*y1 + trans[5];
393 r1 = ((x1_trans - gradient_x0) * gradient_dx +
394 (y1_trans - gradient_y0) * gradient_dy) /
395 gradient_norm_squared;
396
397 /* determine steps from change in r */
398 steps = ceilf(fabsf(r1 - r0) / 0.05);
399 if (steps == 0)
400 steps = 1;
401 fprintf(stderr, "r0 %g, r1 %g, steps %i\n",
402 r0, r1, steps);
403
404 /* loop through intermediate points */
405 for (z = 1; z != steps; z++) {
406 float t, x, y, x_trans, y_trans, r;
407 struct grad_point *point;
408 t = (float) z / (float) steps;
409 if (segment_type == svgtiny_PATH_BEZIER) {
410 x = (1-t) * (1-t) * (1-t) * x0 +
411 3 * t * (1-t) * (1-t) * c0x +
412 3 * t * t * (1-t) * c1x +
413 t * t * t * x1;
414 y = (1-t) * (1-t) * (1-t) * y0 +
415 3 * t * (1-t) * (1-t) * c0y +
416 3 * t * t * (1-t) * c1y +
417 t * t * t * y1;
418 } else {
419 x = (1-t) * x0 + t * x1;
420 y = (1-t) * y0 + t * y1;
421 }
422 x_trans = trans[0]*x + trans[2]*y + trans[4];
423 y_trans = trans[1]*x + trans[3]*y + trans[5];
424 r = ((x_trans - gradient_x0) * gradient_dx +
425 (y_trans - gradient_y0) * gradient_dy) /
426 gradient_norm_squared;
427 fprintf(stderr, "(%g %g [%g]) ", x, y, r);
428 point = svgtiny_list_push(pts);
429 if (!point) {
430 svgtiny_list_free(pts);
431 return svgtiny_OUT_OF_MEMORY;
432 }
433 point->x = x;
434 point->y = y;
435 point->r = r;
436 if (r < min_r) {
437 min_r = r;
438 min_pt = svgtiny_list_size(pts) - 1;
439 }
440 }
441 fprintf(stderr, "\n");
442
443 /* next segment start point is this segment end point */
444 x0 = x1;
445 y0 = y1;
446 }
447 fprintf(stderr, "pts size %i, min_pt %i, min_r %.3f\n",
448 svgtiny_list_size(pts), min_pt, min_r);
449
450 /* render triangles */
451 stop_count = state->linear_gradient_stop_count;
452 assert(2 <= stop_count);
453 current_stop = 0;
454 last_stop_r = 0;
455 current_stop_r = state->gradient_stop[0].offset;
456 red0 = red1 = svgtiny_RED(state->gradient_stop[0].color);
457 green0 = green1 = svgtiny_GREEN(state->gradient_stop[0].color);
458 blue0 = blue1 = svgtiny_BLUE(state->gradient_stop[0].color);
459 t = min_pt;
460 a = (min_pt + 1) % svgtiny_list_size(pts);
461 b = min_pt == 0 ? svgtiny_list_size(pts) - 1 : min_pt - 1;
462 while (a != b) {
463 struct grad_point *point_t = svgtiny_list_get(pts, t);
464 struct grad_point *point_a = svgtiny_list_get(pts, a);
465 struct grad_point *point_b = svgtiny_list_get(pts, b);
466 float mean_r = (point_t->r + point_a->r + point_b->r) / 3;
467 float *p;
468 struct svgtiny_shape *shape;
469 /*fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f "
470 "mean_r %.3f\n",
471 t, pts[t].r, a, pts[a].r, b, pts[b].r,
472 mean_r);*/
473 while (current_stop != stop_count && current_stop_r < mean_r) {
474 current_stop++;
475 if (current_stop == stop_count)
476 break;
477 red0 = red1;
478 green0 = green1;
479 blue0 = blue1;
480 red1 = svgtiny_RED(state->
481 gradient_stop[current_stop].color);
482 green1 = svgtiny_GREEN(state->
483 gradient_stop[current_stop].color);
484 blue1 = svgtiny_BLUE(state->
485 gradient_stop[current_stop].color);
486 last_stop_r = current_stop_r;
487 current_stop_r = state->
488 gradient_stop[current_stop].offset;
489 }
490 p = malloc(10 * sizeof p[0]);
491 if (!p)
492 return svgtiny_OUT_OF_MEMORY;
493 p[0] = svgtiny_PATH_MOVE;
494 p[1] = point_t->x;
495 p[2] = point_t->y;
496 p[3] = svgtiny_PATH_LINE;
497 p[4] = point_a->x;
498 p[5] = point_a->y;
499 p[6] = svgtiny_PATH_LINE;
500 p[7] = point_b->x;
501 p[8] = point_b->y;
502 p[9] = svgtiny_PATH_CLOSE;
503 svgtiny_transform_path(p, 10, state);
504 shape = svgtiny_add_shape(state);
505 if (!shape) {
506 free(p);
507 return svgtiny_OUT_OF_MEMORY;
508 }
509 shape->path = p;
510 shape->path_length = 10;
511 /*shape->fill = svgtiny_TRANSPARENT;*/
512 if (current_stop == 0)
513 shape->fill = state->gradient_stop[0].color;
514 else if (current_stop == stop_count)
515 shape->fill = state->
516 gradient_stop[stop_count - 1].color;
517 else {
518 float stop_r = (mean_r - last_stop_r) /
519 (current_stop_r - last_stop_r);
520 shape->fill = svgtiny_RGB(
521 (int) ((1 - stop_r) * red0 + stop_r * red1),
522 (int) ((1 - stop_r) * green0 + stop_r * green1),
523 (int) ((1 - stop_r) * blue0 + stop_r * blue1));
524 }
525 shape->stroke = svgtiny_TRANSPARENT;
526 #ifdef GRADIENT_DEBUG
527 shape->stroke = svgtiny_RGB(0, 0, 0xff);
528 #endif
529 state->diagram->shape_count++;
530 if (point_a->r < point_b->r) {
531 t = a;
532 a = (a + 1) % svgtiny_list_size(pts);
533 } else {
534 t = b;
535 b = b == 0 ? svgtiny_list_size(pts) - 1 : b - 1;
536 }
537 }
538
539 /* render gradient vector for debugging */
540 #ifdef GRADIENT_DEBUG
541 {
542 float *p = malloc(7 * sizeof p[0]);
543 if (!p)
544 return svgtiny_OUT_OF_MEMORY;
545 p[0] = svgtiny_PATH_MOVE;
546 p[1] = gradient_x0;
547 p[2] = gradient_y0;
548 p[3] = svgtiny_PATH_LINE;
549 p[4] = gradient_x1;
550 p[5] = gradient_y1;
551 p[6] = svgtiny_PATH_CLOSE;
552 svgtiny_transform_path(p, 7, state);
553 struct svgtiny_shape *shape = svgtiny_add_shape(state);
554 if (!shape) {
555 free(p);
556 return svgtiny_OUT_OF_MEMORY;
557 }
558 shape->path = p;
559 shape->path_length = 7;
560 shape->fill = svgtiny_TRANSPARENT;
561 shape->stroke = svgtiny_RGB(0xff, 0, 0);
562 state->diagram->shape_count++;
563 }
564 #endif
565
566 /* render triangle vertices with r values for debugging */
567 #ifdef GRADIENT_DEBUG
568 for (unsigned int i = 0; i != pts->size; i++) {
569 struct grad_point *point = svgtiny_list_get(pts, i);
570 struct svgtiny_shape *shape = svgtiny_add_shape(state);
571 if (!shape)
572 return svgtiny_OUT_OF_MEMORY;
573 char *text = malloc(20);
574 if (!text)
575 return svgtiny_OUT_OF_MEMORY;
576 sprintf(text, "%i=%.3f", i, point->r);
577 shape->text = text;
578 shape->text_x = state->ctm.a * point->x +
579 state->ctm.c * point->y + state->ctm.e;
580 shape->text_y = state->ctm.b * point->x +
581 state->ctm.d * point->y + state->ctm.f;
582 shape->fill = svgtiny_RGB(0, 0, 0);
583 shape->stroke = svgtiny_TRANSPARENT;
584 state->diagram->shape_count++;
585 }
586 #endif
587
588 /* plot actual path outline */
589 if (state->stroke != svgtiny_TRANSPARENT) {
590 struct svgtiny_shape *shape;
591 svgtiny_transform_path(p, n, state);
592
593 shape = svgtiny_add_shape(state);
594 if (!shape) {
595 free(p);
596 return svgtiny_OUT_OF_MEMORY;
597 }
598 shape->path = p;
599 shape->path_length = n;
600 shape->fill = svgtiny_TRANSPARENT;
601 state->diagram->shape_count++;
602 } else {
603 free(p);
604 }
605
606 svgtiny_list_free(pts);
607
608 return svgtiny_OK;
609 }
610
611
612 /**
613 * Get the bounding box of path.
614 */
615
616 void svgtiny_path_bbox(float *p, unsigned int n,
617 float *x0, float *y0, float *x1, float *y1)
618 {
619 unsigned int j;
620
621 *x0 = *x1 = p[1];
622 *y0 = *y1 = p[2];
623
624 for (j = 0; j != n; ) {
625 unsigned int points = 0;
626 unsigned int k;
627 switch ((int) p[j]) {
628 case svgtiny_PATH_MOVE:
629 case svgtiny_PATH_LINE:
630 points = 1;
631 break;
632 case svgtiny_PATH_CLOSE:
633 points = 0;
634 break;
635 case svgtiny_PATH_BEZIER:
636 points = 3;
637 break;
638 default:
639 assert(0);
640 }
641 j++;
642 for (k = 0; k != points; k++) {
643 float x = p[j], y = p[j + 1];
644 if (x < *x0)
645 *x0 = x;
646 else if (*x1 < x)
647 *x1 = x;
648 if (y < *y0)
649 *y0 = y;
650 else if (*y1 < y)
651 *y1 = y;
652 j += 2;
653 }
654 }
655 }
656
657
658 /**
659 * Invert a transformation matrix.
660 */
661 void svgtiny_invert_matrix(float *m, float *inv)
662 {
663 float determinant = m[0]*m[3] - m[1]*m[2];
664 inv[0] = m[3] / determinant;
665 inv[1] = -m[1] / determinant;
666 inv[2] = -m[2] / determinant;
667 inv[3] = m[0] / determinant;
668 inv[4] = (m[2]*m[5] - m[3]*m[4]) / determinant;
669 inv[5] = (m[1]*m[4] - m[0]*m[5]) / determinant;
670 }
671