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