1 /*
2 Copyright (C) 2019 Andrew Belt and licensed under the BSD-3-Clause License.
3
4 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
6 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
8 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
10 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
12 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13 */
14
15 /* Adapted from
16 https://github.com/VCVRack/Rack/blob/v1/src/svg.cpp
17 https://github.com/VCVRack/Rack/blob/v1/include/math.hpp
18 */
19
20 #include "nanosvg/nanosvg.h"
21 #include "nanovg/nanovg.h"
22
23 #include <cassert>
24 #include <cstdint>
25 #include <cmath>
26
27 // #define DEBUG_ONLY(x) x
28 #define DEBUG_ONLY(x)
29
30 namespace rack {
31
32 namespace math {
33 struct Vec {
34 float x = 0.f;
35 float y = 0.f;
36
Vecrack::math::Vec37 Vec() {}
Vecrack::math::Vec38 Vec(float x, float y) : x(x), y(y) {}
39
40 /** Negates the vector
41 Equivalent to a reflection across the y=-x line.
42 */
negrack::math::Vec43 Vec neg() const {
44 return Vec(-x, -y);
45 }
plusrack::math::Vec46 Vec plus(Vec b) const {
47 return Vec(x + b.x, y + b.y);
48 }
minusrack::math::Vec49 Vec minus(Vec b) const {
50 return Vec(x - b.x, y - b.y);
51 }
52 };
53 }
54
getNVGColor(uint32_t color)55 static NVGcolor getNVGColor(uint32_t color) {
56 return nvgRGBA(
57 (color >> 0) & 0xff,
58 (color >> 8) & 0xff,
59 (color >> 16) & 0xff,
60 (color >> 24) & 0xff);
61 }
62
getPaint(NVGcontext * vg,NSVGpaint * p)63 static NVGpaint getPaint(NVGcontext *vg, NSVGpaint *p) {
64 assert(p->type == NSVG_PAINT_LINEAR_GRADIENT || p->type == NSVG_PAINT_RADIAL_GRADIENT);
65 NSVGgradient *g = p->gradient;
66 assert(g->nstops >= 1);
67 NVGcolor icol = getNVGColor(g->stops[0].color);
68 NVGcolor ocol = getNVGColor(g->stops[g->nstops - 1].color);
69
70 float inverse[6];
71 nvgTransformInverse(inverse, g->xform);
72 DEBUG_ONLY(printf(" inverse: %f %f %f %f %f %f\n", inverse[0], inverse[1], inverse[2], inverse[3], inverse[4], inverse[5]);)
73 math::Vec s, e;
74 DEBUG_ONLY(printf(" sx: %f sy: %f ex: %f ey: %f\n", s.x, s.y, e.x, e.y);)
75 // Is it always the case that the gradient should be transformed from (0, 0) to (0, 1)?
76 nvgTransformPoint(&s.x, &s.y, inverse, 0, 0);
77 nvgTransformPoint(&e.x, &e.y, inverse, 0, 1);
78 DEBUG_ONLY(printf(" sx: %f sy: %f ex: %f ey: %f\n", s.x, s.y, e.x, e.y);)
79
80 NVGpaint paint;
81 if (p->type == NSVG_PAINT_LINEAR_GRADIENT)
82 paint = nvgLinearGradient(vg, s.x, s.y, e.x, e.y, icol, ocol);
83 else
84 paint = nvgRadialGradient(vg, s.x, s.y, 0.0, 160, icol, ocol);
85 return paint;
86 }
87
88 /** Returns the parameterized value of the line p2--p3 where it intersects with p0--p1 */
getLineCrossing(math::Vec p0,math::Vec p1,math::Vec p2,math::Vec p3)89 static float getLineCrossing(math::Vec p0, math::Vec p1, math::Vec p2, math::Vec p3) {
90 math::Vec b = p2.minus(p0);
91 math::Vec d = p1.minus(p0);
92 math::Vec e = p3.minus(p2);
93 float m = d.x * e.y - d.y * e.x;
94 // Check if lines are parallel, or if either pair of points are equal
95 if (std::abs(m) < 1e-6)
96 return NAN;
97 return -(d.x * b.y - d.y * b.x) / m;
98 }
99
svgDraw(NVGcontext * vg,NSVGimage * svg)100 void svgDraw(NVGcontext *vg, NSVGimage *svg) {
101 DEBUG_ONLY(printf("new image: %g x %g px\n", svg->width, svg->height);)
102 int shapeIndex = 0;
103 // Iterate shape linked list
104 for (NSVGshape *shape = svg->shapes; shape; shape = shape->next, shapeIndex++) {
105 DEBUG_ONLY(printf(" new shape: %d id \"%s\", fillrule %d, from (%f, %f) to (%f, %f)\n", shapeIndex, shape->id, shape->fillRule, shape->bounds[0], shape->bounds[1], shape->bounds[2], shape->bounds[3]);)
106
107 // Visibility
108 if (!(shape->flags & NSVG_FLAGS_VISIBLE))
109 continue;
110
111 nvgSave(vg);
112
113 // Opacity
114 if (shape->opacity < 1.0)
115 nvgGlobalAlpha(vg, shape->opacity);
116
117 // Build path
118 nvgBeginPath(vg);
119
120 // Iterate path linked list
121 for (NSVGpath *path = shape->paths; path; path = path->next) {
122 DEBUG_ONLY(printf(" new path: %d points, %s, from (%f, %f) to (%f, %f)\n", path->npts, path->closed ? "closed" : "open", path->bounds[0], path->bounds[1], path->bounds[2], path->bounds[3]);)
123
124 nvgMoveTo(vg, path->pts[0], path->pts[1]);
125 for (int i = 1; i < path->npts; i += 3) {
126 float *p = &path->pts[2*i];
127 nvgBezierTo(vg, p[0], p[1], p[2], p[3], p[4], p[5]);
128 // nvgLineTo(vg, p[4], p[5]);
129 DEBUG_ONLY(printf(" bezier (%f, %f) to (%f, %f)\n", p[-2], p[-1], p[4], p[5]);)
130 }
131
132 // Close path
133 if (path->closed)
134 nvgClosePath(vg);
135
136 // Compute whether this is a hole or a solid.
137 // Assume that no paths are crossing (usually true for normal SVG graphics).
138 // Also assume that the topology is the same if we use straight lines rather than Beziers (not always the case but usually true).
139 // Using the even-odd fill rule, if we draw a line from a point on the path to a point outside the boundary (e.g. top left) and count the number of times it crosses another path, the parity of this count determines whether the path is a hole (odd) or solid (even).
140 int crossings = 0;
141 math::Vec p0 = math::Vec(path->pts[0], path->pts[1]);
142 math::Vec p1 = math::Vec(path->bounds[0] - 1.0, path->bounds[1] - 1.0);
143 // Iterate all other paths
144 for (NSVGpath *path2 = shape->paths; path2; path2 = path2->next) {
145 if (path2 == path)
146 continue;
147
148 // Iterate all lines on the path
149 if (path2->npts < 4)
150 continue;
151 for (int i = 1; i < path2->npts + 3; i += 3) {
152 float *p = &path2->pts[2*i];
153 // The previous point
154 math::Vec p2 = math::Vec(p[-2], p[-1]);
155 // The current point
156 math::Vec p3 = (i < path2->npts) ? math::Vec(p[4], p[5]) : math::Vec(path2->pts[0], path2->pts[1]);
157 float crossing = getLineCrossing(p0, p1, p2, p3);
158 float crossing2 = getLineCrossing(p2, p3, p0, p1);
159 if (0.0 <= crossing && crossing < 1.0 && 0.0 <= crossing2) {
160 crossings++;
161 }
162 }
163 }
164
165 if (crossings % 2 == 0)
166 nvgPathWinding(vg, NVG_SOLID);
167 else
168 nvgPathWinding(vg, NVG_HOLE);
169
170 /*
171 // Shoelace algorithm for computing the area, and thus the winding direction
172 float area = 0.0;
173 math::Vec p0 = math::Vec(path->pts[0], path->pts[1]);
174 for (int i = 1; i < path->npts; i += 3) {
175 float *p = &path->pts[2*i];
176 math::Vec p1 = (i < path->npts) ? math::Vec(p[4], p[5]) : math::Vec(path->pts[0], path->pts[1]);
177 area += 0.5 * (p1.x - p0.x) * (p1.y + p0.y);
178 printf("%f %f, %f %f\n", p0.x, p0.y, p1.x, p1.y);
179 p0 = p1;
180 }
181 printf("%f\n", area);
182
183 if (area < 0.0)
184 nvgPathWinding(vg, NVG_CCW);
185 else
186 nvgPathWinding(vg, NVG_CW);
187 */
188 }
189
190 // Fill shape
191 if (shape->fill.type) {
192 switch (shape->fill.type) {
193 case NSVG_PAINT_COLOR: {
194 NVGcolor color = getNVGColor(shape->fill.color);
195 nvgFillColor(vg, color);
196 DEBUG_ONLY(printf(" fill color (%g, %g, %g, %g)\n", color.r, color.g, color.b, color.a);)
197 } break;
198 case NSVG_PAINT_LINEAR_GRADIENT:
199 case NSVG_PAINT_RADIAL_GRADIENT: {
200 NSVGgradient *g = shape->fill.gradient;
201 (void)g;
202 DEBUG_ONLY(printf(" gradient: type: %s xform: %f %f %f %f %f %f spread: %d fx: %f fy: %f nstops: %d\n", (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT ? "linear" : "radial"), g->xform[0], g->xform[1], g->xform[2], g->xform[3], g->xform[4], g->xform[5], g->spread, g->fx, g->fy, g->nstops);)
203 for (int i = 0; i < g->nstops; i++) {
204 DEBUG_ONLY(printf(" stop: #%08x\t%f\n", g->stops[i].color, g->stops[i].offset);)
205 }
206 nvgFillPaint(vg, getPaint(vg, &shape->fill));
207 } break;
208 }
209 nvgFill(vg);
210 }
211
212 // Stroke shape
213 if (shape->stroke.type) {
214 nvgStrokeWidth(vg, shape->strokeWidth);
215 // strokeDashOffset, strokeDashArray, strokeDashCount not yet supported
216 nvgLineCap(vg, (NVGlineCap) shape->strokeLineCap);
217 nvgLineJoin(vg, (int) shape->strokeLineJoin);
218
219 switch (shape->stroke.type) {
220 case NSVG_PAINT_COLOR: {
221 NVGcolor color = getNVGColor(shape->stroke.color);
222 nvgStrokeColor(vg, color);
223 DEBUG_ONLY(printf(" stroke color (%g, %g, %g, %g)\n", color.r, color.g, color.b, color.a);)
224 } break;
225 case NSVG_PAINT_LINEAR_GRADIENT: {
226 // NSVGgradient *g = shape->stroke.gradient;
227 // printf(" lin grad: %f\t%f\n", g->fx, g->fy);
228 } break;
229 }
230 nvgStroke(vg);
231 }
232
233 nvgRestore(vg);
234 }
235
236 DEBUG_ONLY(printf("\n");)
237 }
238
239
240 } // namespace rack
241