KiCad PCB EDA Suite
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-2021 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 <algorithm>
29#include <cmath>
30
31#include <math/vector2d.h>
32
33#include <eda_item.h>
34#include "graphics_importer.h"
35
36static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
37 std::function< const float&( const float&, const float& ) > comparator );
38static float calculateBezierSegmentationThreshold( const float* aCurvePoints );
39static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
40 float aStep, const float* aCurvePoints, float aSegmentationThreshold,
41 std::vector< VECTOR2D >& aGeneratedPoints );
42static void createNewBezierCurveSegments( const VECTOR2D& aStart, const VECTOR2D& aMiddle,
43 const VECTOR2D& aEnd, float aOffset, float aStep, const float* aCurvePoints,
44 float aSegmentationThreshold, std::vector< VECTOR2D >& aGeneratedPoints );
45static VECTOR2D getBezierPoint( const float* aCurvePoints, float aStep );
46static VECTOR2D getPoint( const float* aPointCoordinates );
47static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
48 float aDistance );
49static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
50 const VECTOR2D& aLineEnd );
51
52
53bool SVG_IMPORT_PLUGIN::Load( const wxString& aFileName )
54{
55 wxCHECK( m_importer, false );
56
57 // 1- wxFopen takes care of unicode filenames across platforms
58 // 2 - nanosvg (exactly nsvgParseFromFile) expects a binary file (exactly the CRLF eof must
59 // not be replaced by LF and changes the byte count) in one validity test,
60 // so open it in binary mode.
61 FILE* fp = wxFopen( aFileName, wxT( "rb" ) );
62
63 if( fp == nullptr )
64 return false;
65
66 // nsvgParseFromFile will close the file after reading
67 m_parsedImage = nsvgParseFromFile( fp, "mm", 96 );
68
69 wxCHECK( m_parsedImage, false );
70
71 return true;
72}
73
75{
76 auto alpha =
77 []( unsigned int color )
78 {
79 return color >> 24;
80 };
81
82 for( NSVGshape* shape = m_parsedImage->shapes; shape != nullptr; shape = shape->next )
83 {
84 double lineWidth = shape->strokeWidth;
85 bool filled = shape->fill.type != NSVG_PAINT_NONE && alpha( shape->fill.color ) > 0;
86
88
89 switch( shape->fillRule )
90 {
91 case NSVG_FILLRULE_NONZERO: rule = GRAPHICS_IMPORTER::PF_NONZERO; break;
92 case NSVG_FILLRULE_EVENODD: rule = GRAPHICS_IMPORTER::PF_EVEN_ODD; break;
93 default: break;
94 }
95
97
98 for( NSVGpath* path = shape->paths; path != nullptr; path = path->next )
99 {
100 bool closed = path->closed || rule == GRAPHICS_IMPORTER::PF_EVEN_ODD;
101
102 DrawPath( path->pts, path->npts, closed, filled, lineWidth );
103 }
104 }
105
107 wxCHECK( m_importer, false );
109
110 return true;
111}
112
113
115{
116 if( !m_parsedImage )
117 {
118 wxASSERT_MSG( false, wxT( "Image must have been loaded before checking height" ) );
119 return 0.0;
120 }
121
122 return m_parsedImage->height;
123}
124
125
127{
128 if( !m_parsedImage )
129 {
130 wxASSERT_MSG( false, wxT( "Image must have been loaded before checking width" ) );
131 return 0.0;
132 }
133
134 return m_parsedImage->width;
135}
136
137
138void SVG_IMPORT_PLUGIN::DrawPath( const float* aPoints, int aNumPoints, bool aPoly, bool aFilled,
139 double aLineWidth )
140{
141 std::vector< VECTOR2D > collectedPathPoints;
142
143 if( aNumPoints > 0 )
144 DrawCubicBezierPath( aPoints, aNumPoints, collectedPathPoints );
145
146 if( aPoly && aFilled )
147 DrawPolygon( collectedPathPoints, aLineWidth );
148 else
149 DrawLineSegments( collectedPathPoints, aLineWidth );
150}
151
152
153void SVG_IMPORT_PLUGIN::DrawCubicBezierPath( const float* aPoints, int aNumPoints,
154 std::vector< VECTOR2D >& aGeneratedPoints )
155{
156 const int pointsPerSegment = 4;
157 const int curveSpecificPointsPerSegment = 3;
158 const int curveSpecificCoordinatesPerSegment = 2 * curveSpecificPointsPerSegment;
159 const float* currentPoints = aPoints;
160 int remainingPoints = aNumPoints;
161
162 while( remainingPoints >= pointsPerSegment )
163 {
164 DrawCubicBezierCurve( currentPoints, aGeneratedPoints );
165 currentPoints += curveSpecificCoordinatesPerSegment;
166 remainingPoints -= curveSpecificPointsPerSegment;
167 }
168}
169
170
172 std::vector< VECTOR2D >& aGeneratedPoints )
173{
174 auto start = getBezierPoint( aPoints, 0.0f );
175 auto end = getBezierPoint( aPoints, 1.0f );
176 auto segmentationThreshold = calculateBezierSegmentationThreshold( aPoints );
177
178 aGeneratedPoints.push_back( start );
179 segmentBezierCurve( start, end, 0.0f, 0.5f, aPoints, segmentationThreshold, aGeneratedPoints );
180 aGeneratedPoints.push_back( end );
181}
182
183
184void SVG_IMPORT_PLUGIN::DrawPolygon( const std::vector< VECTOR2D >& aPoints, double aWidth )
185{
186 m_internalImporter.AddPolygon( aPoints, aWidth );
187}
188
189
190void SVG_IMPORT_PLUGIN::DrawLineSegments( const std::vector< VECTOR2D >& aPoints, double aWidth )
191{
192 unsigned int numLineStartPoints = aPoints.size() - 1;
193
194 for( unsigned int pointIndex = 0; pointIndex < numLineStartPoints; ++pointIndex )
195 m_internalImporter.AddLine( aPoints[ pointIndex ], aPoints[ pointIndex + 1 ], aWidth );
196}
197
198
199static VECTOR2D getPoint( const float* aPointCoordinates )
200{
201 return VECTOR2D( aPointCoordinates[0], aPointCoordinates[1] );
202}
203
204
205static VECTOR2D getBezierPoint( const float* aPoints, float aStep )
206{
207 const int coordinatesPerPoint = 2;
208
209 auto firstCubicPoint = getPoint( aPoints );
210 auto secondCubicPoint = getPoint( aPoints + 1 * coordinatesPerPoint );
211 auto thirdCubicPoint = getPoint( aPoints + 2 * coordinatesPerPoint );
212 auto fourthCubicPoint = getPoint( aPoints + 3 * coordinatesPerPoint );
213
214 auto firstQuadraticPoint = getPointInLine( firstCubicPoint, secondCubicPoint, aStep );
215 auto secondQuadraticPoint = getPointInLine( secondCubicPoint, thirdCubicPoint, aStep );
216 auto thirdQuadraticPoint = getPointInLine( thirdCubicPoint, fourthCubicPoint, aStep );
217
218 auto firstLinearPoint = getPointInLine( firstQuadraticPoint, secondQuadraticPoint, aStep );
219 auto secondLinearPoint = getPointInLine( secondQuadraticPoint, thirdQuadraticPoint, aStep );
220
221 return getPointInLine( firstLinearPoint, secondLinearPoint, aStep );
222}
223
224
225static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
226 float aDistance )
227{
228 return aLineStart + ( aLineEnd - aLineStart ) * aDistance;
229}
230
231
232static float calculateBezierSegmentationThreshold( const float* aCurvePoints )
233{
234 using comparatorFunction = const float&(*)( const float&, const float& );
235
236 auto minimumComparator = static_cast< comparatorFunction >( &std::min );
237 auto maximumComparator = static_cast< comparatorFunction >( &std::max );
238
239 VECTOR2D minimum = calculateBezierBoundingBoxExtremity( aCurvePoints, minimumComparator );
240 VECTOR2D maximum = calculateBezierBoundingBoxExtremity( aCurvePoints, maximumComparator );
241 VECTOR2D boundingBoxDimensions = maximum - minimum;
242
243 return 0.001 * std::max( boundingBoxDimensions.x, boundingBoxDimensions.y );
244}
245
246
247static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
248 std::function< const float&( const float&, const float& ) > comparator )
249{
250 float x = aCurvePoints[0];
251 float y = aCurvePoints[1];
252
253 for( int pointIndex = 1; pointIndex < 3; ++pointIndex )
254 {
255 x = comparator( x, aCurvePoints[ 2 * pointIndex ] );
256 y = comparator( y, aCurvePoints[ 2 * pointIndex + 1 ] );
257 }
258
259 return VECTOR2D( x, y );
260}
261
262
263static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
264 float aStep, const float* aCurvePoints,
265 float aSegmentationThreshold,
266 std::vector< VECTOR2D >& aGeneratedPoints )
267{
268 VECTOR2D middle = getBezierPoint( aCurvePoints, aOffset + aStep );
269 float distanceToPreviousSegment = distanceFromPointToLine( middle, aStart, aEnd );
270
271 if( distanceToPreviousSegment > aSegmentationThreshold )
272 {
273 createNewBezierCurveSegments( aStart, middle, aEnd, aOffset, aStep, aCurvePoints,
274 aSegmentationThreshold, aGeneratedPoints );
275 }
276}
277
278
279static void createNewBezierCurveSegments( const VECTOR2D& aStart, const VECTOR2D& aMiddle,
280 const VECTOR2D& aEnd, float aOffset, float aStep,
281 const float* aCurvePoints, float aSegmentationThreshold,
282 std::vector< VECTOR2D >& aGeneratedPoints )
283{
284 float newStep = aStep / 2.f;
285 float offsetAfterMiddle = aOffset + aStep;
286
287 segmentBezierCurve( aStart, aMiddle, aOffset, newStep, aCurvePoints, aSegmentationThreshold,
288 aGeneratedPoints );
289
290 aGeneratedPoints.push_back( aMiddle );
291
292 segmentBezierCurve( aMiddle, aEnd, offsetAfterMiddle, newStep, aCurvePoints,
293 aSegmentationThreshold, aGeneratedPoints );
294}
295
296
297static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
298 const VECTOR2D& aLineEnd )
299{
300 auto lineDirection = aLineEnd - aLineStart;
301 auto lineNormal = lineDirection.Perpendicular().Resize( 1.f );
302 auto lineStartToPoint = aPoint - aLineStart;
303
304 auto distance = lineNormal.Dot( lineStartToPoint );
305
306 return fabs( distance );
307}
int color
Definition: DXF_plotter.cpp:57
void ImportTo(GRAPHICS_IMPORTER &aImporter)
void AddPolygon(const std::vector< VECTOR2D > &aVertices, double aWidth) override
void AddLine(const VECTOR2D &aStart, const VECTOR2D &aEnd, double aWidth) override
Create an object representing a line segment.
virtual void NewShape(POLY_FILL_RULE aFillRule=PF_NONZERO)
GRAPHICS_IMPORTER * m_importer
< Importer used to create objects representing the imported shapes.
void DrawCubicBezierPath(const float *aPoints, int aNumPoints, std::vector< VECTOR2D > &aGeneratedPoints)
void DrawLineSegments(const std::vector< VECTOR2D > &aPoints, double aWidth)
GRAPHICS_IMPORTER_BUFFER m_internalImporter
struct NSVGimage * m_parsedImage
void DrawPath(const float *aPoints, int aNumPoints, bool aClosedPath, bool aFilled, double aLineWidth)
bool Import() override
Actually imports the file.
virtual double GetImageWidth() const override
Return image width from original imported file.
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 DrawPolygon(const std::vector< VECTOR2D > &aPoints, double aWidth)
VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition: vector2d.h:307
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:378
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
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 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:617