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