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 (C) 1992-2023 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 <locale_io.h>
32
33#include <eda_item.h>
34#include "graphics_importer.h"
35
36static const int SVG_DPI = 96;
37
38static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
39 std::function< const float&( const float&, const float& ) > comparator );
40static float calculateBezierSegmentationThreshold( const float* aCurvePoints );
41static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
42 float aStep, const float* aCurvePoints, float aSegmentationThreshold,
43 std::vector< VECTOR2D >& aGeneratedPoints );
44static void createNewBezierCurveSegments( const VECTOR2D& aStart, const VECTOR2D& aMiddle,
45 const VECTOR2D& aEnd, float aOffset, float aStep, const float* aCurvePoints,
46 float aSegmentationThreshold, std::vector< VECTOR2D >& aGeneratedPoints );
47static VECTOR2D getBezierPoint( const float* aCurvePoints, float aStep );
48static VECTOR2D getPoint( const float* aPointCoordinates );
49static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
50 float aDistance );
51static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
52 const VECTOR2D& aLineEnd );
53
54
55bool SVG_IMPORT_PLUGIN::Load( const wxString& aFileName )
56{
57 wxCHECK( m_importer, false );
58
59 LOCALE_IO toggle; // switch on/off the locale "C" notation
60
61 // 1- wxFopen takes care of unicode filenames across platforms
62 // 2 - nanosvg (exactly nsvgParseFromFile) expects a binary file (exactly the CRLF eof must
63 // not be replaced by LF and changes the byte count) in one validity test,
64 // so open it in binary mode.
65 FILE* fp = wxFopen( aFileName, wxT( "rb" ) );
66
67 if( fp == nullptr )
68 return false;
69
70 // nsvgParseFromFile will close the file after reading
71 m_parsedImage = nsvgParseFromFile( fp, "mm", SVG_DPI );
72
73 wxCHECK( m_parsedImage, false );
74
75 return true;
76}
77
78
79bool SVG_IMPORT_PLUGIN::LoadFromMemory( const wxMemoryBuffer& aMemBuffer )
80{
81 wxCHECK( m_importer, false );
82
83 LOCALE_IO toggle; // switch on/off the locale "C" notation
84
85 std::string str( reinterpret_cast<char*>( aMemBuffer.GetData() ), aMemBuffer.GetDataLen() );
86 wxCHECK( str.data()[aMemBuffer.GetDataLen()] == '\0', false );
87
88 // nsvgParse will modify the string data
89 m_parsedImage = nsvgParse( str.data(), "mm", SVG_DPI );
90
91 wxCHECK( m_parsedImage, false );
92
93 return true;
94}
95
96
98{
99 auto alpha =
100 []( unsigned int color )
101 {
102 return color >> 24;
103 };
104
105 for( NSVGshape* shape = m_parsedImage->shapes; shape != nullptr; shape = shape->next )
106 {
107 if( !( shape->flags & NSVG_FLAGS_VISIBLE ) )
108 continue;
109
110 if( shape->stroke.type == NSVG_PAINT_NONE && shape->fill.type == NSVG_PAINT_NONE )
111 continue;
112
113 double lineWidth = shape->stroke.type != NSVG_PAINT_NONE ? shape->strokeWidth : -1;
114 bool filled = shape->fill.type != NSVG_PAINT_NONE && alpha( shape->fill.color ) > 0;
115
116 COLOR4D fillColor = COLOR4D::UNSPECIFIED;
117
118 if( shape->fill.type == NSVG_PAINT_COLOR )
119 {
120 unsigned int icolor = shape->fill.color;
121
122 fillColor.r = std::clamp( ( icolor >> 0 ) & 0xFF, 0u, 255u ) / 255.0;
123 fillColor.g = std::clamp( ( icolor >> 8 ) & 0xFF, 0u, 255u ) / 255.0;
124 fillColor.b = std::clamp( ( icolor >> 16 ) & 0xFF, 0u, 255u ) / 255.0;
125 fillColor.a = std::clamp( ( icolor >> 24 ) & 0xFF, 0u, 255u ) / 255.0;
126
127 // nanosvg probably didn't read it properly, use default
128 if( fillColor == COLOR4D::BLACK )
129 fillColor = COLOR4D::UNSPECIFIED;
130 }
131
132 COLOR4D strokeColor = COLOR4D::UNSPECIFIED;
133
134 if( shape->stroke.type == NSVG_PAINT_COLOR )
135 {
136 unsigned int icolor = shape->stroke.color;
137
138 strokeColor.r = std::clamp( ( icolor >> 0 ) & 0xFF, 0u, 255u ) / 255.0;
139 strokeColor.g = std::clamp( ( icolor >> 8 ) & 0xFF, 0u, 255u ) / 255.0;
140 strokeColor.b = std::clamp( ( icolor >> 16 ) & 0xFF, 0u, 255u ) / 255.0;
141 strokeColor.a = std::clamp( ( icolor >> 24 ) & 0xFF, 0u, 255u ) / 255.0;
142
143 // nanosvg probably didn't read it properly, use default
144 if( strokeColor == COLOR4D::BLACK )
145 strokeColor = COLOR4D::UNSPECIFIED;
146 }
147
148 LINE_STYLE dashType = LINE_STYLE::SOLID;
149
150 if( shape->strokeDashCount > 0 )
151 {
152 float* dashArray = shape->strokeDashArray;
153
154 int dotCount = 0;
155 int dashCount = 0;
156
157 const float dashThreshold = shape->strokeWidth * 1.9f;
158
159 for( int i = 0; i < shape->strokeDashCount; i += 2 )
160 {
161 if( dashArray[i] < dashThreshold )
162 dotCount++;
163 else
164 dashCount++;
165 }
166
167 if( dotCount > 0 && dashCount == 0 )
168 dashType = LINE_STYLE::DOT;
169 else if( dotCount == 0 && dashCount > 0 )
170 dashType = LINE_STYLE::DASH;
171 else if( dotCount == 1 && dashCount == 1 )
172 dashType = LINE_STYLE::DASHDOT;
173 else if( dotCount == 2 && dashCount == 1 )
174 dashType = LINE_STYLE::DASHDOTDOT;
175 }
176
177 IMPORTED_STROKE stroke( lineWidth, dashType, strokeColor );
178
180
181 switch( shape->fillRule )
182 {
183 case NSVG_FILLRULE_NONZERO: rule = GRAPHICS_IMPORTER::PF_NONZERO; break;
184 case NSVG_FILLRULE_EVENODD: rule = GRAPHICS_IMPORTER::PF_EVEN_ODD; break;
185 default: break;
186 }
187
189
190 for( NSVGpath* path = shape->paths; path != nullptr; path = path->next )
191 {
192 bool closed = path->closed || filled || rule == GRAPHICS_IMPORTER::PF_EVEN_ODD;
193
194 DrawPath( path->pts, path->npts, closed, stroke, filled, fillColor );
195 }
196 }
197
199 wxCHECK( m_importer, false );
201
202 return true;
203}
204
205
207{
208 if( !m_parsedImage )
209 {
210 wxASSERT_MSG( false, wxT( "Image must have been loaded before checking height" ) );
211 return 0.0;
212 }
213
214 return m_parsedImage->height / SVG_DPI * inches2mm;
215}
216
217
219{
220 if( !m_parsedImage )
221 {
222 wxASSERT_MSG( false, wxT( "Image must have been loaded before checking width" ) );
223 return 0.0;
224 }
225
226 return m_parsedImage->width / SVG_DPI * inches2mm;
227}
228
229
231{
232 BOX2D bbox;
233
234 if( !m_parsedImage || !m_parsedImage->shapes )
235 {
236 wxASSERT_MSG( false, wxT( "Image must have been loaded before getting bbox" ) );
237 return bbox;
238 }
239
240 for( NSVGshape* shape = m_parsedImage->shapes; shape != nullptr; shape = shape->next )
241 {
242 BOX2D shapeBbox;
243 float( &bounds )[4] = shape->bounds;
244
245 shapeBbox.SetOrigin( bounds[0], bounds[1] );
246 shapeBbox.SetEnd( bounds[2], bounds[3] );
247
248 bbox.Merge( shapeBbox );
249 }
250
251 return bbox;
252}
253
254
255void SVG_IMPORT_PLUGIN::DrawPath( const float* aPoints, int aNumPoints, bool aClosedPath,
256 const IMPORTED_STROKE& aStroke, bool aFilled,
257 const COLOR4D& aFillColor )
258{
259 std::vector<VECTOR2D> collectedPathPoints;
260
261 if( aNumPoints > 0 )
262 DrawCubicBezierPath( aPoints, aNumPoints, collectedPathPoints );
263
264 if( aClosedPath && collectedPathPoints.size() > 2 )
265 DrawPolygon( collectedPathPoints, aStroke, aFilled, aFillColor );
266 else
267 DrawLineSegments( collectedPathPoints, aStroke );
268}
269
270
271void SVG_IMPORT_PLUGIN::DrawCubicBezierPath( const float* aPoints, int aNumPoints,
272 std::vector<VECTOR2D>& aGeneratedPoints )
273{
274 const int pointsPerSegment = 4;
275 const int curveSpecificPointsPerSegment = 3;
276 const int curveSpecificCoordinatesPerSegment = 2 * curveSpecificPointsPerSegment;
277 const float* currentPoints = aPoints;
278 int remainingPoints = aNumPoints;
279
280 while( remainingPoints >= pointsPerSegment )
281 {
282 DrawCubicBezierCurve( currentPoints, aGeneratedPoints );
283 currentPoints += curveSpecificCoordinatesPerSegment;
284 remainingPoints -= curveSpecificPointsPerSegment;
285 }
286}
287
288
290 std::vector<VECTOR2D>& aGeneratedPoints )
291{
292 auto start = getBezierPoint( aPoints, 0.0f );
293 auto end = getBezierPoint( aPoints, 1.0f );
294 auto segmentationThreshold = calculateBezierSegmentationThreshold( aPoints );
295
296 aGeneratedPoints.push_back( start );
297 segmentBezierCurve( start, end, 0.0f, 0.5f, aPoints, segmentationThreshold, aGeneratedPoints );
298 aGeneratedPoints.push_back( end );
299}
300
301
302void SVG_IMPORT_PLUGIN::DrawPolygon( const std::vector<VECTOR2D>& aPoints,
303 const IMPORTED_STROKE& aStroke, bool aFilled,
304 const COLOR4D& aFillColor )
305{
306 m_internalImporter.AddPolygon( aPoints, aStroke, aFilled, aFillColor );
307}
308
309
310void SVG_IMPORT_PLUGIN::DrawLineSegments( const std::vector<VECTOR2D>& aPoints,
311 const IMPORTED_STROKE& aStroke )
312{
313 unsigned int numLineStartPoints = aPoints.size() - 1;
314
315 for( unsigned int pointIndex = 0; pointIndex < numLineStartPoints; ++pointIndex )
316 m_internalImporter.AddLine( aPoints[pointIndex], aPoints[pointIndex + 1], aStroke );
317}
318
319
320static VECTOR2D getPoint( const float* aPointCoordinates )
321{
322 return VECTOR2D( aPointCoordinates[0], aPointCoordinates[1] );
323}
324
325
326static VECTOR2D getBezierPoint( const float* aPoints, float aStep )
327{
328 const int coordinatesPerPoint = 2;
329
330 auto firstCubicPoint = getPoint( aPoints );
331 auto secondCubicPoint = getPoint( aPoints + 1 * coordinatesPerPoint );
332 auto thirdCubicPoint = getPoint( aPoints + 2 * coordinatesPerPoint );
333 auto fourthCubicPoint = getPoint( aPoints + 3 * coordinatesPerPoint );
334
335 auto firstQuadraticPoint = getPointInLine( firstCubicPoint, secondCubicPoint, aStep );
336 auto secondQuadraticPoint = getPointInLine( secondCubicPoint, thirdCubicPoint, aStep );
337 auto thirdQuadraticPoint = getPointInLine( thirdCubicPoint, fourthCubicPoint, aStep );
338
339 auto firstLinearPoint = getPointInLine( firstQuadraticPoint, secondQuadraticPoint, aStep );
340 auto secondLinearPoint = getPointInLine( secondQuadraticPoint, thirdQuadraticPoint, aStep );
341
342 return getPointInLine( firstLinearPoint, secondLinearPoint, aStep );
343}
344
345
346static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
347 float aDistance )
348{
349 return aLineStart + ( aLineEnd - aLineStart ) * aDistance;
350}
351
352
353static float calculateBezierSegmentationThreshold( const float* aCurvePoints )
354{
355 using comparatorFunction = const float&(*)( const float&, const float& );
356
357 auto minimumComparator = static_cast< comparatorFunction >( &std::min );
358 auto maximumComparator = static_cast< comparatorFunction >( &std::max );
359
360 VECTOR2D minimum = calculateBezierBoundingBoxExtremity( aCurvePoints, minimumComparator );
361 VECTOR2D maximum = calculateBezierBoundingBoxExtremity( aCurvePoints, maximumComparator );
362 VECTOR2D boundingBoxDimensions = maximum - minimum;
363
364 return 0.001 * std::max( boundingBoxDimensions.x, boundingBoxDimensions.y );
365}
366
367
368static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
369 std::function< const float&( const float&, const float& ) > comparator )
370{
371 float x = aCurvePoints[0];
372 float y = aCurvePoints[1];
373
374 for( int pointIndex = 1; pointIndex < 3; ++pointIndex )
375 {
376 x = comparator( x, aCurvePoints[ 2 * pointIndex ] );
377 y = comparator( y, aCurvePoints[ 2 * pointIndex + 1 ] );
378 }
379
380 return VECTOR2D( x, y );
381}
382
383
384static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
385 float aStep, const float* aCurvePoints,
386 float aSegmentationThreshold,
387 std::vector< VECTOR2D >& aGeneratedPoints )
388{
389 VECTOR2D middle = getBezierPoint( aCurvePoints, aOffset + aStep );
390 float distanceToPreviousSegment = distanceFromPointToLine( middle, aStart, aEnd );
391
392 if( distanceToPreviousSegment > aSegmentationThreshold )
393 {
394 createNewBezierCurveSegments( aStart, middle, aEnd, aOffset, aStep, aCurvePoints,
395 aSegmentationThreshold, aGeneratedPoints );
396 }
397}
398
399
400static void createNewBezierCurveSegments( const VECTOR2D& aStart, const VECTOR2D& aMiddle,
401 const VECTOR2D& aEnd, float aOffset, float aStep,
402 const float* aCurvePoints, float aSegmentationThreshold,
403 std::vector< VECTOR2D >& aGeneratedPoints )
404{
405 float newStep = aStep / 2.f;
406 float offsetAfterMiddle = aOffset + aStep;
407
408 segmentBezierCurve( aStart, aMiddle, aOffset, newStep, aCurvePoints, aSegmentationThreshold,
409 aGeneratedPoints );
410
411 aGeneratedPoints.push_back( aMiddle );
412
413 segmentBezierCurve( aMiddle, aEnd, offsetAfterMiddle, newStep, aCurvePoints,
414 aSegmentationThreshold, aGeneratedPoints );
415}
416
417
418static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
419 const VECTOR2D& aLineEnd )
420{
421 auto lineDirection = aLineEnd - aLineStart;
422 auto lineNormal = lineDirection.Perpendicular().Resize( 1.f );
423 auto lineStartToPoint = aPoint - aLineStart;
424
425 auto distance = lineNormal.Dot( lineStartToPoint );
426
427 return fabs( distance );
428}
int color
Definition: DXF_plotter.cpp:58
void SetOrigin(const Vec &pos)
Definition: box2.h:203
void SetEnd(coord_type x, coord_type y)
Definition: box2.h:256
BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition: box2.h:589
void AddLine(const VECTOR2D &aStart, const VECTOR2D &aEnd, const IMPORTED_STROKE &aStroke) override
Create an object representing a line segment.
void ImportTo(GRAPHICS_IMPORTER &aImporter)
void AddPolygon(const std::vector< VECTOR2D > &aVertices, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor=COLOR4D::UNSPECIFIED) override
Create an object representing a polygon.
virtual void NewShape(POLY_FILL_RULE aFillRule=PF_NONZERO)
GRAPHICS_IMPORTER * m_importer
< Importer used to create objects representing the imported shapes.
A clone of IMPORTED_STROKE, but with floating-point width.
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
double r
Red component.
Definition: color4d.h:392
double g
Green component.
Definition: color4d.h:393
double a
Alpha component.
Definition: color4d.h:395
double b
Blue component.
Definition: color4d.h:394
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:49
void DrawCubicBezierPath(const float *aPoints, int aNumPoints, std::vector< VECTOR2D > &aGeneratedPoints)
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.
void DrawCubicBezierCurve(const float *aPoints, std::vector< VECTOR2D > &aGeneratedPoints)
virtual double GetImageHeight() const override
Return image height from original imported file.
void DrawPath(const float *aPoints, int aNumPoints, bool aClosedPath, const IMPORTED_STROKE &aStroke, bool aFilled, const COLOR4D &aFillColor)
VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition: vector2d.h:279
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:350
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
LINE_STYLE
Dashed line types.
Definition: stroke_params.h:48
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 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)
VECTOR2< double > VECTOR2D
Definition: vector2d.h:587