429 lines
15 KiB
C++
429 lines
15 KiB
C++
/*
|
|
* This program source code file is part of KICAD, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2016 CERN
|
|
* Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
* @author Janito V. Ferreira Filho
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include "svg_import_plugin.h"
|
|
|
|
#include <nanosvg.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <locale_io.h>
|
|
|
|
#include <eda_item.h>
|
|
#include "graphics_importer.h"
|
|
|
|
static const int SVG_DPI = 96;
|
|
|
|
static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
|
|
std::function< const float&( const float&, const float& ) > comparator );
|
|
static float calculateBezierSegmentationThreshold( const float* aCurvePoints );
|
|
static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
|
|
float aStep, const float* aCurvePoints, float aSegmentationThreshold,
|
|
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 getBezierPoint( const float* aCurvePoints, float aStep );
|
|
static VECTOR2D getPoint( const float* aPointCoordinates );
|
|
static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
|
|
float aDistance );
|
|
static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
|
|
const VECTOR2D& aLineEnd );
|
|
|
|
|
|
bool SVG_IMPORT_PLUGIN::Load( const wxString& aFileName )
|
|
{
|
|
wxCHECK( m_importer, false );
|
|
|
|
LOCALE_IO toggle; // switch on/off the locale "C" notation
|
|
|
|
// 1- wxFopen takes care of unicode filenames across platforms
|
|
// 2 - nanosvg (exactly nsvgParseFromFile) expects a binary file (exactly the CRLF eof must
|
|
// not be replaced by LF and changes the byte count) in one validity test,
|
|
// so open it in binary mode.
|
|
FILE* fp = wxFopen( aFileName, wxT( "rb" ) );
|
|
|
|
if( fp == nullptr )
|
|
return false;
|
|
|
|
// nsvgParseFromFile will close the file after reading
|
|
m_parsedImage = nsvgParseFromFile( fp, "mm", SVG_DPI );
|
|
|
|
wxCHECK( m_parsedImage, false );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SVG_IMPORT_PLUGIN::LoadFromMemory( const wxMemoryBuffer& aMemBuffer )
|
|
{
|
|
wxCHECK( m_importer, false );
|
|
|
|
LOCALE_IO toggle; // switch on/off the locale "C" notation
|
|
|
|
std::string str( reinterpret_cast<char*>( aMemBuffer.GetData() ), aMemBuffer.GetDataLen() );
|
|
wxCHECK( str.data()[aMemBuffer.GetDataLen()] == '\0', false );
|
|
|
|
// nsvgParse will modify the string data
|
|
m_parsedImage = nsvgParse( str.data(), "mm", SVG_DPI );
|
|
|
|
wxCHECK( m_parsedImage, false );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SVG_IMPORT_PLUGIN::Import()
|
|
{
|
|
auto alpha =
|
|
[]( unsigned int color )
|
|
{
|
|
return color >> 24;
|
|
};
|
|
|
|
for( NSVGshape* shape = m_parsedImage->shapes; shape != nullptr; shape = shape->next )
|
|
{
|
|
if( !( shape->flags & NSVG_FLAGS_VISIBLE ) )
|
|
continue;
|
|
|
|
if( shape->stroke.type == NSVG_PAINT_NONE && shape->fill.type == NSVG_PAINT_NONE )
|
|
continue;
|
|
|
|
double lineWidth = shape->stroke.type != NSVG_PAINT_NONE ? shape->strokeWidth : -1;
|
|
bool filled = shape->fill.type != NSVG_PAINT_NONE && alpha( shape->fill.color ) > 0;
|
|
|
|
COLOR4D fillColor = COLOR4D::UNSPECIFIED;
|
|
|
|
if( shape->fill.type == NSVG_PAINT_COLOR )
|
|
{
|
|
unsigned int icolor = shape->fill.color;
|
|
|
|
fillColor.r = std::clamp( ( icolor >> 0 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
fillColor.g = std::clamp( ( icolor >> 8 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
fillColor.b = std::clamp( ( icolor >> 16 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
fillColor.a = std::clamp( ( icolor >> 24 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
|
|
// nanosvg probably didn't read it properly, use default
|
|
if( fillColor == COLOR4D::BLACK )
|
|
fillColor = COLOR4D::UNSPECIFIED;
|
|
}
|
|
|
|
COLOR4D strokeColor = COLOR4D::UNSPECIFIED;
|
|
|
|
if( shape->stroke.type == NSVG_PAINT_COLOR )
|
|
{
|
|
unsigned int icolor = shape->stroke.color;
|
|
|
|
strokeColor.r = std::clamp( ( icolor >> 0 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
strokeColor.g = std::clamp( ( icolor >> 8 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
strokeColor.b = std::clamp( ( icolor >> 16 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
strokeColor.a = std::clamp( ( icolor >> 24 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
|
|
// nanosvg probably didn't read it properly, use default
|
|
if( strokeColor == COLOR4D::BLACK )
|
|
strokeColor = COLOR4D::UNSPECIFIED;
|
|
}
|
|
|
|
LINE_STYLE dashType = LINE_STYLE::SOLID;
|
|
|
|
if( shape->strokeDashCount > 0 )
|
|
{
|
|
float* dashArray = shape->strokeDashArray;
|
|
|
|
int dotCount = 0;
|
|
int dashCount = 0;
|
|
|
|
const float dashThreshold = shape->strokeWidth * 1.9f;
|
|
|
|
for( int i = 0; i < shape->strokeDashCount; i += 2 )
|
|
{
|
|
if( dashArray[i] < dashThreshold )
|
|
dotCount++;
|
|
else
|
|
dashCount++;
|
|
}
|
|
|
|
if( dotCount > 0 && dashCount == 0 )
|
|
dashType = LINE_STYLE::DOT;
|
|
else if( dotCount == 0 && dashCount > 0 )
|
|
dashType = LINE_STYLE::DASH;
|
|
else if( dotCount == 1 && dashCount == 1 )
|
|
dashType = LINE_STYLE::DASHDOT;
|
|
else if( dotCount == 2 && dashCount == 1 )
|
|
dashType = LINE_STYLE::DASHDOTDOT;
|
|
}
|
|
|
|
IMPORTED_STROKE stroke( lineWidth, dashType, strokeColor );
|
|
|
|
GRAPHICS_IMPORTER::POLY_FILL_RULE rule = GRAPHICS_IMPORTER::PF_NONZERO;
|
|
|
|
switch( shape->fillRule )
|
|
{
|
|
case NSVG_FILLRULE_NONZERO: rule = GRAPHICS_IMPORTER::PF_NONZERO; break;
|
|
case NSVG_FILLRULE_EVENODD: rule = GRAPHICS_IMPORTER::PF_EVEN_ODD; break;
|
|
default: break;
|
|
}
|
|
|
|
m_internalImporter.NewShape( rule );
|
|
|
|
for( NSVGpath* path = shape->paths; path != nullptr; path = path->next )
|
|
{
|
|
bool closed = path->closed || filled || rule == GRAPHICS_IMPORTER::PF_EVEN_ODD;
|
|
|
|
DrawPath( path->pts, path->npts, closed, stroke, filled, fillColor );
|
|
}
|
|
}
|
|
|
|
m_internalImporter.PostprocessNestedPolygons();
|
|
wxCHECK( m_importer, false );
|
|
m_internalImporter.ImportTo( *m_importer );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
double SVG_IMPORT_PLUGIN::GetImageHeight() const
|
|
{
|
|
if( !m_parsedImage )
|
|
{
|
|
wxASSERT_MSG( false, wxT( "Image must have been loaded before checking height" ) );
|
|
return 0.0;
|
|
}
|
|
|
|
return m_parsedImage->height / SVG_DPI * inches2mm;
|
|
}
|
|
|
|
|
|
double SVG_IMPORT_PLUGIN::GetImageWidth() const
|
|
{
|
|
if( !m_parsedImage )
|
|
{
|
|
wxASSERT_MSG( false, wxT( "Image must have been loaded before checking width" ) );
|
|
return 0.0;
|
|
}
|
|
|
|
return m_parsedImage->width / SVG_DPI * inches2mm;
|
|
}
|
|
|
|
|
|
BOX2D SVG_IMPORT_PLUGIN::GetImageBBox() const
|
|
{
|
|
BOX2D bbox;
|
|
|
|
if( !m_parsedImage || !m_parsedImage->shapes )
|
|
{
|
|
wxASSERT_MSG( false, wxT( "Image must have been loaded before getting bbox" ) );
|
|
return bbox;
|
|
}
|
|
|
|
for( NSVGshape* shape = m_parsedImage->shapes; shape != nullptr; shape = shape->next )
|
|
{
|
|
BOX2D shapeBbox;
|
|
float( &bounds )[4] = shape->bounds;
|
|
|
|
shapeBbox.SetOrigin( bounds[0], bounds[1] );
|
|
shapeBbox.SetEnd( bounds[2], bounds[3] );
|
|
|
|
bbox.Merge( shapeBbox );
|
|
}
|
|
|
|
return bbox;
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawPath( const float* aPoints, int aNumPoints, bool aClosedPath,
|
|
const IMPORTED_STROKE& aStroke, bool aFilled,
|
|
const COLOR4D& aFillColor )
|
|
{
|
|
std::vector<VECTOR2D> collectedPathPoints;
|
|
|
|
if( aNumPoints > 0 )
|
|
DrawCubicBezierPath( aPoints, aNumPoints, collectedPathPoints );
|
|
|
|
if( aClosedPath && collectedPathPoints.size() > 2 )
|
|
DrawPolygon( collectedPathPoints, aStroke, aFilled, aFillColor );
|
|
else
|
|
DrawLineSegments( collectedPathPoints, aStroke );
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawCubicBezierPath( const float* aPoints, int aNumPoints,
|
|
std::vector<VECTOR2D>& aGeneratedPoints )
|
|
{
|
|
const int pointsPerSegment = 4;
|
|
const int curveSpecificPointsPerSegment = 3;
|
|
const int curveSpecificCoordinatesPerSegment = 2 * curveSpecificPointsPerSegment;
|
|
const float* currentPoints = aPoints;
|
|
int remainingPoints = aNumPoints;
|
|
|
|
while( remainingPoints >= pointsPerSegment )
|
|
{
|
|
DrawCubicBezierCurve( currentPoints, aGeneratedPoints );
|
|
currentPoints += curveSpecificCoordinatesPerSegment;
|
|
remainingPoints -= curveSpecificPointsPerSegment;
|
|
}
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawCubicBezierCurve( const float* aPoints,
|
|
std::vector<VECTOR2D>& aGeneratedPoints )
|
|
{
|
|
auto start = getBezierPoint( aPoints, 0.0f );
|
|
auto end = getBezierPoint( aPoints, 1.0f );
|
|
auto segmentationThreshold = calculateBezierSegmentationThreshold( aPoints );
|
|
|
|
aGeneratedPoints.push_back( start );
|
|
segmentBezierCurve( start, end, 0.0f, 0.5f, aPoints, segmentationThreshold, aGeneratedPoints );
|
|
aGeneratedPoints.push_back( end );
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawPolygon( const std::vector<VECTOR2D>& aPoints,
|
|
const IMPORTED_STROKE& aStroke, bool aFilled,
|
|
const COLOR4D& aFillColor )
|
|
{
|
|
m_internalImporter.AddPolygon( aPoints, aStroke, aFilled, aFillColor );
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawLineSegments( const std::vector<VECTOR2D>& aPoints,
|
|
const IMPORTED_STROKE& aStroke )
|
|
{
|
|
unsigned int numLineStartPoints = aPoints.size() - 1;
|
|
|
|
for( unsigned int pointIndex = 0; pointIndex < numLineStartPoints; ++pointIndex )
|
|
m_internalImporter.AddLine( aPoints[pointIndex], aPoints[pointIndex + 1], aStroke );
|
|
}
|
|
|
|
|
|
static VECTOR2D getPoint( const float* aPointCoordinates )
|
|
{
|
|
return VECTOR2D( aPointCoordinates[0], aPointCoordinates[1] );
|
|
}
|
|
|
|
|
|
static VECTOR2D getBezierPoint( const float* aPoints, float aStep )
|
|
{
|
|
const int coordinatesPerPoint = 2;
|
|
|
|
auto firstCubicPoint = getPoint( aPoints );
|
|
auto secondCubicPoint = getPoint( aPoints + 1 * coordinatesPerPoint );
|
|
auto thirdCubicPoint = getPoint( aPoints + 2 * coordinatesPerPoint );
|
|
auto fourthCubicPoint = getPoint( aPoints + 3 * coordinatesPerPoint );
|
|
|
|
auto firstQuadraticPoint = getPointInLine( firstCubicPoint, secondCubicPoint, aStep );
|
|
auto secondQuadraticPoint = getPointInLine( secondCubicPoint, thirdCubicPoint, aStep );
|
|
auto thirdQuadraticPoint = getPointInLine( thirdCubicPoint, fourthCubicPoint, aStep );
|
|
|
|
auto firstLinearPoint = getPointInLine( firstQuadraticPoint, secondQuadraticPoint, aStep );
|
|
auto secondLinearPoint = getPointInLine( secondQuadraticPoint, thirdQuadraticPoint, aStep );
|
|
|
|
return getPointInLine( firstLinearPoint, secondLinearPoint, aStep );
|
|
}
|
|
|
|
|
|
static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
|
|
float aDistance )
|
|
{
|
|
return aLineStart + ( aLineEnd - aLineStart ) * aDistance;
|
|
}
|
|
|
|
|
|
static float calculateBezierSegmentationThreshold( const float* aCurvePoints )
|
|
{
|
|
using comparatorFunction = const float&(*)( const float&, const float& );
|
|
|
|
auto minimumComparator = static_cast< comparatorFunction >( &std::min );
|
|
auto maximumComparator = static_cast< comparatorFunction >( &std::max );
|
|
|
|
VECTOR2D minimum = calculateBezierBoundingBoxExtremity( aCurvePoints, minimumComparator );
|
|
VECTOR2D maximum = calculateBezierBoundingBoxExtremity( aCurvePoints, maximumComparator );
|
|
VECTOR2D boundingBoxDimensions = maximum - minimum;
|
|
|
|
return 0.001 * std::max( boundingBoxDimensions.x, boundingBoxDimensions.y );
|
|
}
|
|
|
|
|
|
static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
|
|
std::function< const float&( const float&, const float& ) > comparator )
|
|
{
|
|
float x = aCurvePoints[0];
|
|
float y = aCurvePoints[1];
|
|
|
|
for( int pointIndex = 1; pointIndex < 3; ++pointIndex )
|
|
{
|
|
x = comparator( x, aCurvePoints[ 2 * pointIndex ] );
|
|
y = comparator( y, aCurvePoints[ 2 * pointIndex + 1 ] );
|
|
}
|
|
|
|
return VECTOR2D( x, y );
|
|
}
|
|
|
|
|
|
static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
|
|
float aStep, const float* aCurvePoints,
|
|
float aSegmentationThreshold,
|
|
std::vector< VECTOR2D >& aGeneratedPoints )
|
|
{
|
|
VECTOR2D middle = getBezierPoint( aCurvePoints, aOffset + aStep );
|
|
float distanceToPreviousSegment = distanceFromPointToLine( middle, aStart, aEnd );
|
|
|
|
if( distanceToPreviousSegment > aSegmentationThreshold )
|
|
{
|
|
createNewBezierCurveSegments( aStart, middle, aEnd, aOffset, aStep, aCurvePoints,
|
|
aSegmentationThreshold, 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 )
|
|
{
|
|
float newStep = aStep / 2.f;
|
|
float offsetAfterMiddle = aOffset + aStep;
|
|
|
|
segmentBezierCurve( aStart, aMiddle, aOffset, newStep, aCurvePoints, aSegmentationThreshold,
|
|
aGeneratedPoints );
|
|
|
|
aGeneratedPoints.push_back( aMiddle );
|
|
|
|
segmentBezierCurve( aMiddle, aEnd, offsetAfterMiddle, newStep, aCurvePoints,
|
|
aSegmentationThreshold, aGeneratedPoints );
|
|
}
|
|
|
|
|
|
static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
|
|
const VECTOR2D& aLineEnd )
|
|
{
|
|
auto lineDirection = aLineEnd - aLineStart;
|
|
auto lineNormal = lineDirection.Perpendicular().Resize( 1.f );
|
|
auto lineStartToPoint = aPoint - aLineStart;
|
|
|
|
auto distance = lineNormal.Dot( lineStartToPoint );
|
|
|
|
return fabs( distance );
|
|
}
|