KiCad PCB EDA Suite
Loading...
Searching...
No Matches
svg_import_plugin.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KICAD, a free EDA CAD application.
3 *
4 * Copyright (C) 2016 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Janito V. Ferreira Filho
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include "svg_import_plugin.h"
23
24#include <nanosvg.h>
25#include <algorithm>
26#include <cmath>
27#include <wx/math.h>
28#include <locale_io.h>
29
30#include <eda_item.h>
31#include "graphics_importer.h"
32
33static const int SVG_DPI = 96;
34
35static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
36 std::function< const float&( const float&, const float& ) > comparator );
37static float calculateBezierSegmentationThreshold( const float* aCurvePoints );
38static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
39 float aStep, const float* aCurvePoints, float aSegmentationThreshold,
40 std::vector< VECTOR2D >& aGeneratedPoints );
41static void createNewBezierCurveSegments( const VECTOR2D& aStart, const VECTOR2D& aMiddle,
42 const VECTOR2D& aEnd, float aOffset, float aStep, const float* aCurvePoints,
43 float aSegmentationThreshold, std::vector< VECTOR2D >& aGeneratedPoints );
44static VECTOR2D getBezierPoint( const float* aCurvePoints, float aStep );
45static VECTOR2D getPoint( const float* aPointCoordinates );
46static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
47 float aDistance );
48static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
49 const VECTOR2D& aLineEnd );
50
51
52bool SVG_IMPORT_PLUGIN::Load( const wxString& aFileName )
53{
54 wxCHECK( m_importer, false );
55
56 LOCALE_IO toggle; // switch on/off the locale "C" notation
57
58 // 1- wxFopen takes care of unicode filenames across platforms
59 // 2 - nanosvg (exactly nsvgParseFromFile) expects a binary file (exactly the CRLF eof must
60 // not be replaced by LF and changes the byte count) in one validity test,
61 // so open it in binary mode.
62 FILE* fp = wxFopen( aFileName, wxT( "rb" ) );
63
64 if( fp == nullptr )
65 return false;
66
67 // nsvgParseFromFile will close the file after reading
68 m_parsedImage = nsvgParseFromFile( fp, "mm", SVG_DPI );
69
70 wxCHECK( m_parsedImage, false );
71
72 return true;
73}
74
75
76bool SVG_IMPORT_PLUGIN::LoadFromMemory( const wxMemoryBuffer& aMemBuffer )
77{
78 wxCHECK( m_importer, false );
79
80 LOCALE_IO toggle; // switch on/off the locale "C" notation
81
82 std::string str( reinterpret_cast<char*>( aMemBuffer.GetData() ), aMemBuffer.GetDataLen() );
83 wxCHECK( str.data()[aMemBuffer.GetDataLen()] == '\0', false );
84
85 // nsvgParse will modify the string data
86 m_parsedImage = nsvgParse( str.data(), "mm", SVG_DPI );
87
88 wxCHECK( m_parsedImage, false );
89
90 return true;
91}
92
93
95{
96 auto alpha =
97 []( unsigned int color )
98 {
99 return color >> 24;
100 };
101
102 for( NSVGshape* shape = m_parsedImage->shapes; shape != nullptr; shape = shape->next )
103 {
104 if( !( shape->flags & NSVG_FLAGS_VISIBLE ) )
105 continue;
106
107 if( shape->stroke.type == NSVG_PAINT_NONE && shape->fill.type == NSVG_PAINT_NONE )
108 continue;
109
110 double lineWidth = shape->stroke.type != NSVG_PAINT_NONE ? shape->strokeWidth : -1;
111 bool filled = shape->fill.type != NSVG_PAINT_NONE && alpha( shape->fill.color ) > 0;
112
113 COLOR4D fillColor = COLOR4D::UNSPECIFIED;
114
115 if( shape->fill.type == NSVG_PAINT_COLOR )
116 {
117 unsigned int icolor = shape->fill.color;
118
119 fillColor.r = std::clamp( ( icolor >> 0 ) & 0xFF, 0u, 255u ) / 255.0;
120 fillColor.g = std::clamp( ( icolor >> 8 ) & 0xFF, 0u, 255u ) / 255.0;
121 fillColor.b = std::clamp( ( icolor >> 16 ) & 0xFF, 0u, 255u ) / 255.0;
122 fillColor.a = std::clamp( ( icolor >> 24 ) & 0xFF, 0u, 255u ) / 255.0;
123
124 // nanosvg probably didn't read it properly, use default
125 if( fillColor == COLOR4D::BLACK )
126 fillColor = COLOR4D::UNSPECIFIED;
127 }
128
129 COLOR4D strokeColor = COLOR4D::UNSPECIFIED;
130
131 if( shape->stroke.type == NSVG_PAINT_COLOR )
132 {
133 unsigned int icolor = shape->stroke.color;
134
135 strokeColor.r = std::clamp( ( icolor >> 0 ) & 0xFF, 0u, 255u ) / 255.0;
136 strokeColor.g = std::clamp( ( icolor >> 8 ) & 0xFF, 0u, 255u ) / 255.0;
137 strokeColor.b = std::clamp( ( icolor >> 16 ) & 0xFF, 0u, 255u ) / 255.0;
138 strokeColor.a = std::clamp( ( icolor >> 24 ) & 0xFF, 0u, 255u ) / 255.0;
139
140 // nanosvg probably didn't read it properly, use default
141 if( strokeColor == COLOR4D::BLACK )
142 strokeColor = COLOR4D::UNSPECIFIED;
143 }
144
145 LINE_STYLE dashType = LINE_STYLE::SOLID;
146
147 if( shape->strokeDashCount > 0 )
148 {
149 float* dashArray = shape->strokeDashArray;
150
151 int dotCount = 0;
152 int dashCount = 0;
153
154 const float dashThreshold = shape->strokeWidth * 1.9f;
155
156 for( int i = 0; i < shape->strokeDashCount; i += 2 )
157 {
158 if( dashArray[i] < dashThreshold )
159 dotCount++;
160 else
161 dashCount++;
162 }
163
164 if( dotCount > 0 && dashCount == 0 )
165 dashType = LINE_STYLE::DOT;
166 else if( dotCount == 0 && dashCount > 0 )
167 dashType = LINE_STYLE::DASH;
168 else if( dotCount == 1 && dashCount == 1 )
169 dashType = LINE_STYLE::DASHDOT;
170 else if( dotCount == 2 && dashCount == 1 )
171 dashType = LINE_STYLE::DASHDOTDOT;
172 }
173
174 IMPORTED_STROKE stroke( lineWidth, dashType, strokeColor );
175
177
178 switch( shape->fillRule )
179 {
180 case NSVG_FILLRULE_NONZERO: rule = GRAPHICS_IMPORTER::PF_NONZERO; break;
181 case NSVG_FILLRULE_EVENODD: rule = GRAPHICS_IMPORTER::PF_EVEN_ODD; break;
182 default: break;
183 }
184
185 m_internalImporter.NewShape( rule );
186
187 for( NSVGpath* path = shape->paths; path != nullptr; path = path->next )
188 {
189 if( filled && !path->closed )
190 {
191 // KiCad doesn't support a single object representing a filled shape that is
192 // *not* closed so create a filled, closed shape for the fill, and an unfilled,
193 // open shape for the outline
195 const bool closed = true;
196
197 DrawPath( path->pts, path->npts, closed, noStroke, true, fillColor );
198
199 if( stroke.GetWidth() > 0 )
200 DrawPath( path->pts, path->npts, !closed, stroke, false, COLOR4D::UNSPECIFIED );
201 }
202 else
203 {
204 // Either the shape has fill and no stroke, so we implicitly close it (for no
205 // difference), or it's really closed.
206 // We could choose to import a not-filled, closed outline as splines to keep the
207 // original editability and control points, but currently we don't.
208 const bool closed = path->closed || filled;
209
210 DrawPath( path->pts, path->npts, closed, stroke, filled, fillColor );
211 }
212 }
213 }
214
215 m_internalImporter.PostprocessNestedPolygons();
216 wxCHECK( m_importer, false );
217 m_internalImporter.ImportTo( *m_importer );
218
219 return true;
220}
221
222
224{
225 if( !m_parsedImage )
226 {
227 wxASSERT_MSG( false, wxT( "Image must have been loaded before checking height" ) );
228 return 0.0;
229 }
230
231 return m_parsedImage->height / SVG_DPI * inches2mm;
232}
233
234
236{
237 if( !m_parsedImage )
238 {
239 wxASSERT_MSG( false, wxT( "Image must have been loaded before checking width" ) );
240 return 0.0;
241 }
242
243 return m_parsedImage->width / SVG_DPI * inches2mm;
244}
245
246
248{
249 BOX2D bbox;
250
251 if( !m_parsedImage || !m_parsedImage->shapes )
252 {
253 wxASSERT_MSG( false, wxT( "Image must have been loaded before getting bbox" ) );
254 return bbox;
255 }
256
257 for( NSVGshape* shape = m_parsedImage->shapes; shape != nullptr; shape = shape->next )
258 {
259 BOX2D shapeBbox;
260 float( &bounds )[4] = shape->bounds;
261
262 shapeBbox.SetOrigin( bounds[0], bounds[1] );
263 shapeBbox.SetEnd( bounds[2], bounds[3] );
264
265 bbox.Merge( shapeBbox );
266 }
267
268 return bbox;
269}
270
271
272static void GatherInterpolatedCubicBezierCurve( const float* aPoints,
273 std::vector<VECTOR2D>& aGeneratedPoints )
274{
275 auto start = getBezierPoint( aPoints, 0.0f );
276 auto end = getBezierPoint( aPoints, 1.0f );
277 auto segmentationThreshold = calculateBezierSegmentationThreshold( aPoints );
278
279 if( aGeneratedPoints.size() == 0 || aGeneratedPoints.back() != start )
280 aGeneratedPoints.push_back( start );
281
282 segmentBezierCurve( start, end, 0.0f, 0.5f, aPoints, segmentationThreshold, aGeneratedPoints );
283 aGeneratedPoints.push_back( end );
284}
285
286
287static void GatherInterpolatedCubicBezierPath( const float* aPoints, int aNumPoints,
288 std::vector<VECTOR2D>& aGeneratedPoints )
289{
290 const int pointsPerSegment = 4;
291 const int curveSpecificPointsPerSegment = 3;
292 const int curveSpecificCoordinatesPerSegment = 2 * curveSpecificPointsPerSegment;
293 const float* currentPoints = aPoints;
294 int remainingPoints = aNumPoints;
295
296 while( remainingPoints >= pointsPerSegment )
297 {
298 GatherInterpolatedCubicBezierCurve( currentPoints, aGeneratedPoints );
299 currentPoints += curveSpecificCoordinatesPerSegment;
300 remainingPoints -= curveSpecificPointsPerSegment;
301 }
302}
303
304
305void SVG_IMPORT_PLUGIN::DrawPath( const float* aPoints, int aNumPoints, bool aClosedPath,
306 const IMPORTED_STROKE& aStroke, bool aFilled,
307 const COLOR4D& aFillColor )
308{
309 bool drewPolygon = false;
310
311 if( aClosedPath )
312 {
313 // Closed paths are always polygons, which mean they need to be interpolated
314 std::vector<VECTOR2D> collectedPathPoints;
315
316 if( aNumPoints > 0 )
317 GatherInterpolatedCubicBezierPath( aPoints, aNumPoints, collectedPathPoints );
318
319 if( collectedPathPoints.size() > 2 )
320 {
321 DrawPolygon( collectedPathPoints, aStroke, aFilled, aFillColor );
322 drewPolygon = true;
323 }
324 }
325
326 if( !drewPolygon )
327 {
328 DrawSplinePath( aPoints, aNumPoints, aStroke );
329 }
330}
331
332
333void SVG_IMPORT_PLUGIN::DrawSplinePath( const float* aCoords, int aNumPoints,
334 const IMPORTED_STROKE& aStroke )
335{
336 // NanoSVG just gives us the points of the Bezier curves, so we have to
337 // decide whether to draw lines or splines based on the points we have.
338
339 const int pointsPerSegment = 4;
340 const int curveSpecificPointsPerSegment = 3;
341 const int curveSpecificCoordinatesPerSegment = 2 * curveSpecificPointsPerSegment;
342 const float* currentCoords = aCoords;
343 int remainingPoints = aNumPoints;
344
345 while( remainingPoints >= pointsPerSegment )
346 {
347 VECTOR2D start = getPoint( currentCoords );
348 VECTOR2D c1 = getPoint( currentCoords + 2 );
349 VECTOR2D c2 = getPoint( currentCoords + 4 );
350 VECTOR2D end = getPoint( currentCoords + 6 );
351
352 // Add as a spline and the importer will decide whether to draw it as a spline or as lines
353 m_internalImporter.AddSpline( start, c1, c2, end, aStroke );
354
355 currentCoords += curveSpecificCoordinatesPerSegment;
356 remainingPoints -= curveSpecificPointsPerSegment;
357 }
358}
359
360
361void SVG_IMPORT_PLUGIN::DrawPolygon( const std::vector<VECTOR2D>& aPoints,
362 const IMPORTED_STROKE& aStroke, bool aFilled,
363 const COLOR4D& aFillColor )
364{
365 m_internalImporter.AddPolygon( aPoints, aStroke, aFilled, aFillColor );
366}
367
368
369void SVG_IMPORT_PLUGIN::DrawLineSegments( const std::vector<VECTOR2D>& aPoints,
370 const IMPORTED_STROKE& aStroke )
371{
372 unsigned int numLineStartPoints = aPoints.size() - 1;
373
374 for( unsigned int pointIndex = 0; pointIndex < numLineStartPoints; ++pointIndex )
375 m_internalImporter.AddLine( aPoints[pointIndex], aPoints[pointIndex + 1], aStroke );
376}
377
378
379static VECTOR2D getPoint( const float* aPointCoordinates )
380{
381 return VECTOR2D( aPointCoordinates[0], aPointCoordinates[1] );
382}
383
384
385static VECTOR2D getBezierPoint( const float* aPoints, float aStep )
386{
387 const int coordinatesPerPoint = 2;
388
389 auto firstCubicPoint = getPoint( aPoints );
390 auto secondCubicPoint = getPoint( aPoints + 1 * coordinatesPerPoint );
391 auto thirdCubicPoint = getPoint( aPoints + 2 * coordinatesPerPoint );
392 auto fourthCubicPoint = getPoint( aPoints + 3 * coordinatesPerPoint );
393
394 auto firstQuadraticPoint = getPointInLine( firstCubicPoint, secondCubicPoint, aStep );
395 auto secondQuadraticPoint = getPointInLine( secondCubicPoint, thirdCubicPoint, aStep );
396 auto thirdQuadraticPoint = getPointInLine( thirdCubicPoint, fourthCubicPoint, aStep );
397
398 auto firstLinearPoint = getPointInLine( firstQuadraticPoint, secondQuadraticPoint, aStep );
399 auto secondLinearPoint = getPointInLine( secondQuadraticPoint, thirdQuadraticPoint, aStep );
400
401 return getPointInLine( firstLinearPoint, secondLinearPoint, aStep );
402}
403
404
405static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
406 float aDistance )
407{
408 return aLineStart + ( aLineEnd - aLineStart ) * aDistance;
409}
410
411
412static float calculateBezierSegmentationThreshold( const float* aCurvePoints )
413{
414 using comparatorFunction = const float&(*)( const float&, const float& );
415
416 auto minimumComparator = static_cast< comparatorFunction >( &std::min );
417 auto maximumComparator = static_cast< comparatorFunction >( &std::max );
418
419 VECTOR2D minimum = calculateBezierBoundingBoxExtremity( aCurvePoints, minimumComparator );
420 VECTOR2D maximum = calculateBezierBoundingBoxExtremity( aCurvePoints, maximumComparator );
421 VECTOR2D boundingBoxDimensions = maximum - minimum;
422
423 return 0.001 * std::max( boundingBoxDimensions.x, boundingBoxDimensions.y );
424}
425
426
427static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
428 std::function< const float&( const float&, const float& ) > comparator )
429{
430 float x = aCurvePoints[0];
431 float y = aCurvePoints[1];
432
433 for( int pointIndex = 1; pointIndex < 3; ++pointIndex )
434 {
435 x = comparator( x, aCurvePoints[ 2 * pointIndex ] );
436 y = comparator( y, aCurvePoints[ 2 * pointIndex + 1 ] );
437 }
438
439 return VECTOR2D( x, y );
440}
441
442
443static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
444 float aStep, const float* aCurvePoints,
445 float aSegmentationThreshold,
446 std::vector< VECTOR2D >& aGeneratedPoints )
447{
448 VECTOR2D middle = getBezierPoint( aCurvePoints, aOffset + aStep );
449 float distanceToPreviousSegment = distanceFromPointToLine( middle, aStart, aEnd );
450
451 if( distanceToPreviousSegment > aSegmentationThreshold )
452 {
453 createNewBezierCurveSegments( aStart, middle, aEnd, aOffset, aStep, aCurvePoints,
454 aSegmentationThreshold, aGeneratedPoints );
455 }
456}
457
458
459static void createNewBezierCurveSegments( const VECTOR2D& aStart, const VECTOR2D& aMiddle,
460 const VECTOR2D& aEnd, float aOffset, float aStep,
461 const float* aCurvePoints, float aSegmentationThreshold,
462 std::vector< VECTOR2D >& aGeneratedPoints )
463{
464 float newStep = aStep / 2.f;
465 float offsetAfterMiddle = aOffset + aStep;
466
467 segmentBezierCurve( aStart, aMiddle, aOffset, newStep, aCurvePoints, aSegmentationThreshold,
468 aGeneratedPoints );
469
470 aGeneratedPoints.push_back( aMiddle );
471
472 segmentBezierCurve( aMiddle, aEnd, offsetAfterMiddle, newStep, aCurvePoints,
473 aSegmentationThreshold, aGeneratedPoints );
474}
475
476
477static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
478 const VECTOR2D& aLineEnd )
479{
480 auto lineDirection = aLineEnd - aLineStart;
481
482 // When start and end coincide (e.g. a single-node closed bezier), the line degenerates to a
483 // point. Perpendicular().Resize(1) on a zero vector returns (0,0), which makes the dot
484 // product always zero and prevents bezier subdivision. Fall back to point-to-point distance.
485 if( lineDirection.x == 0.0 && lineDirection.y == 0.0 )
486 return ( aPoint - aLineStart ).EuclideanNorm();
487
488 auto lineNormal = lineDirection.Perpendicular().Resize( 1.f );
489 auto lineStartToPoint = aPoint - aLineStart;
490
491 auto distance = lineNormal.Dot( lineStartToPoint );
492
493 return fabs( distance );
494}
495
496
497void SVG_IMPORT_PLUGIN::ReportMsg( const wxString& aMessage )
498{
499 // Add message to keep trace of not handled svg entities
500 m_messages += aMessage;
501 m_messages += '\n';
502}
BOX2< VECTOR2D > BOX2D
Definition box2.h:919
constexpr void SetOrigin(const Vec &pos)
Definition box2.h:233
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:654
constexpr void SetEnd(coord_type x, coord_type y)
Definition box2.h:293
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:398
static const COLOR4D BLACK
Definition color4d.h:402
GRAPHICS_IMPORTER * m_importer
Importer used to create objects representing the imported shapes.
A clone of IMPORTED_STROKE, but with floating-point width.
double GetWidth() const
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
double r
Red component.
Definition color4d.h:389
double g
Green component.
Definition color4d.h:390
double a
Alpha component.
Definition color4d.h:392
double b
Blue component.
Definition color4d.h:391
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
void DrawPolygon(const std::vector< VECTOR2D > &aPoints, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor)
virtual BOX2D GetImageBBox() const override
Return image bounding box from original imported file.
GRAPHICS_IMPORTER_BUFFER m_internalImporter
struct NSVGimage * m_parsedImage
void DrawLineSegments(const std::vector< VECTOR2D > &aPoints, const IMPORTED_STROKE &aStroke)
bool Import() override
Actually imports the file.
virtual double GetImageWidth() const override
Return image width from original imported file.
bool LoadFromMemory(const wxMemoryBuffer &aMemBuffer) override
Set memory buffer with content for import.
bool Load(const wxString &aFileName) override
Load file for import.
virtual double GetImageHeight() const override
Return image height from original imported file.
void DrawSplinePath(const float *aPoints, int aNumPoints, const IMPORTED_STROKE &aStroke)
Draw a path made up of cubic Bezier curves, adding them as real bezier curves.
void DrawPath(const float *aPoints, int aNumPoints, bool aClosedPath, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor)
void ReportMsg(const wxString &aMessage) override
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
LINE_STYLE
Dashed line types.
static void segmentBezierCurve(const VECTOR2D &aStart, const VECTOR2D &aEnd, float aOffset, float aStep, const float *aCurvePoints, float aSegmentationThreshold, std::vector< VECTOR2D > &aGeneratedPoints)
static float distanceFromPointToLine(const VECTOR2D &aPoint, const VECTOR2D &aLineStart, const VECTOR2D &aLineEnd)
static const int SVG_DPI
static VECTOR2D getPointInLine(const VECTOR2D &aLineStart, const VECTOR2D &aLineEnd, float aDistance)
static VECTOR2D getBezierPoint(const float *aCurvePoints, float aStep)
static VECTOR2D calculateBezierBoundingBoxExtremity(const float *aCurvePoints, std::function< const float &(const float &, const float &) > comparator)
static void GatherInterpolatedCubicBezierCurve(const float *aPoints, std::vector< VECTOR2D > &aGeneratedPoints)
static void createNewBezierCurveSegments(const VECTOR2D &aStart, const VECTOR2D &aMiddle, const VECTOR2D &aEnd, float aOffset, float aStep, const float *aCurvePoints, float aSegmentationThreshold, std::vector< VECTOR2D > &aGeneratedPoints)
static VECTOR2D getPoint(const float *aPointCoordinates)
static float calculateBezierSegmentationThreshold(const float *aCurvePoints)
static void GatherInterpolatedCubicBezierPath(const float *aPoints, int aNumPoints, std::vector< VECTOR2D > &aGeneratedPoints)
std::string path
VECTOR2I end
VECTOR2< double > VECTOR2D
Definition vector2d.h:682