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