|
|
/*
* This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 1992-2019 jean-pierre.charras * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors. * * 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 <algorithm> // std::max
#include <cerrno>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <kiid.h>
#include <build_version.h>
#include <layer_ids.h>
#include <locale_io.h>
#include <potracelib.h>
#include <fmt/format.h>
#include "bitmap2component.h"
/* free a potrace bitmap */static void bm_free( potrace_bitmap_t* bm ){ if( bm != nullptr ) { free( bm->map ); }
free( bm );}
static void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer, potrace_dpoint_t p1, potrace_dpoint_t p2, potrace_dpoint_t p3, potrace_dpoint_t p4 );
BITMAPCONV_INFO::BITMAPCONV_INFO( std::string& aData ): m_Data( aData ){ m_Format = POSTSCRIPT_FMT; m_PixmapWidth = 0; m_PixmapHeight = 0; m_ScaleX = 1.0; m_ScaleY = 1.0; m_Paths = nullptr; m_CmpName = "LOGO";}
int BITMAPCONV_INFO::ConvertBitmap( potrace_bitmap_t* aPotrace_bitmap, OUTPUT_FMT_ID aFormat, int aDpi_X, int aDpi_Y, BMP2CMP_MOD_LAYER aModLayer ){ potrace_param_t* param; potrace_state_t* st;
// set tracing parameters, starting from defaults
param = potrace_param_default();
if( !param ) { m_errors += fmt::format( "Error allocating parameters: {}\n", strerror( errno ) ); return 1; }
// For parameters: see http://potrace.sourceforge.net/potracelib.pdf
param->turdsize = 0; // area (in pixels) of largest path to be ignored.
// Potrace default is 2
param->opttolerance = 0.2; // curve optimization tolerance. Potrace default is 0.2
/* convert the bitmap to curves */ st = potrace_trace( param, aPotrace_bitmap );
if( !st || st->status != POTRACE_STATUS_OK ) { if( st ) { potrace_state_free( st ); }
potrace_param_free( param );
m_errors += fmt::format( "Error tracing bitmap: {}\n", strerror( errno ) ); return 1; }
m_PixmapWidth = aPotrace_bitmap->w; m_PixmapHeight = aPotrace_bitmap->h; // the bitmap size in pixels
m_Paths = st->plist;
switch( aFormat ) { case KICAD_WKS_LOGO: m_Format = KICAD_WKS_LOGO; m_ScaleX = PL_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to micron
m_ScaleY = PL_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom
createOutputData(); break;
case POSTSCRIPT_FMT: m_Format = POSTSCRIPT_FMT; m_ScaleX = 1.0; // the conversion scale
m_ScaleY = m_ScaleX;
// output vector data, e.g. as a rudimentary EPS file (mainly for tests)
createOutputData(); break;
case EESCHEMA_FMT: m_Format = EESCHEMA_FMT; m_ScaleX = SCH_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to eeschema iu
m_ScaleY = -SCH_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is bottom to Top for components in libs
createOutputData(); break;
case PCBNEW_KICAD_MOD: m_Format = PCBNEW_KICAD_MOD; m_ScaleX = PCB_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to IU
m_ScaleY = PCB_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom in Footprint Editor
createOutputData( aModLayer ); break;
default: break; }
bm_free( aPotrace_bitmap ); potrace_state_free( st ); potrace_param_free( param );
return 0;}
const char* BITMAPCONV_INFO::getBoardLayerName( BMP2CMP_MOD_LAYER aChoice ){ const char* layerName = "F.SilkS";
switch( aChoice ) { case MOD_LYR_FSOLDERMASK: layerName = "F.Mask"; break;
case MOD_LYR_FAB: layerName = "F.Fab"; break;
case MOD_LYR_DRAWINGS: layerName = "Dwgs.User"; break;
case MOD_LYR_COMMENTS: layerName = "Cmts.User"; break;
case MOD_LYR_ECO1: layerName = "Eco1.User"; break;
case MOD_LYR_ECO2: layerName = "Eco2.User"; break;
case MOD_LYR_FSILKS: break; }
return layerName;}
void BITMAPCONV_INFO::outputDataHeader( const char * aBrdLayerName ){ double Ypos = ( m_PixmapHeight / 2 * m_ScaleY ); // fields Y position in mm
double fieldSize; // fields text size in mm
std::string strbuf;
switch( m_Format ) { case POSTSCRIPT_FMT: /* output vector data, e.g. as a rudimentary EPS file */ m_Data += "%!PS-Adobe-3.0 EPSF-3.0\n"; strbuf = fmt::format( "%%BoundingBox: 0 0 {} {}\n", m_PixmapWidth, m_PixmapHeight ); m_Data += strbuf; m_Data += "gsave\n"; break;
case PCBNEW_KICAD_MOD: // fields text size = 1.5 mm
// fields text thickness = 1.5 / 5 = 0.3mm
strbuf = fmt::format( "(footprint \"{}\" (version 20221018) (generator \"bitmap2component\") (generator_version \"{}\")\n" " (layer \"F.Cu\")\n", m_CmpName.c_str(), GetMajorMinorVersion().ToStdString() );
m_Data += strbuf; strbuf = fmt::format( " (attr board_only exclude_from_pos_files exclude_from_bom)\n" ); m_Data += strbuf; strbuf = fmt::format( " (fp_text reference \"G***\" (at 0 0) (layer \"{}\")\n" " (effects (font (size 1.5 1.5) (thickness 0.3)))\n" " (uuid {})\n )\n", aBrdLayerName, KIID().AsString().ToStdString().c_str() );
m_Data += strbuf; strbuf = fmt::format( " (fp_text value \"{}\" (at 0.75 0) (layer \"{}\") hide\n" " (effects (font (size 1.5 1.5) (thickness 0.3)))\n" " (uuid {})\n )\n", m_CmpName.c_str(), aBrdLayerName, KIID().AsString().ToStdString().c_str() );
m_Data += strbuf; break;
case KICAD_WKS_LOGO: m_Data += fmt::format("(kicad_wks (version 20220228) (generator \"bitmap2component\") (generator_version \"{}\")\n", GetMajorMinorVersion().ToStdString() ); m_Data += " (setup (textsize 1.5 1.5)(linewidth 0.15)(textlinewidth 0.15)\n"; m_Data += " (left_margin 10)(right_margin 10)(top_margin 10)(bottom_margin 10))\n"; m_Data += " (polygon (name \"\") (pos 0 0) (linewidth 0.01)\n"; break;
case EESCHEMA_FMT: fieldSize = 1.27; // fields text size in mm (= 50 mils)
Ypos /= SCH_IU_PER_MM; Ypos += fieldSize / 2; // snprintf( strbuf, sizeof(strbuf), "# pixmap size w = %d, h = %d\n#\n", m_PixmapWidth, m_PixmapHeight );
strbuf = fmt::format( "(kicad_symbol_lib (version 20220914) (generator \"bitmap2component\") (generator_version \"{}\")\n" " (symbol \"{}\" (pin_names (offset 1.016)) (in_bom yes) (on_board yes)\n", GetMajorMinorVersion().ToStdString(), m_CmpName.c_str() ); m_Data += strbuf;
strbuf = fmt::format( " (property \"Reference\" \"#G\" (at 0 {:g} 0)\n" " (effects (font (size {:g} {:g})) hide)\n )\n", -Ypos, fieldSize, fieldSize ); m_Data += strbuf;
strbuf = fmt::format( " (property \"Value\" \"{}\" (at 0 {:g} 0)\n" " (effects (font (size {:g} {:g})) hide)\n )\n", m_CmpName.c_str(), Ypos, fieldSize, fieldSize ); m_Data += strbuf;
strbuf = fmt::format( " (property \"Footprint\" \"\" (at 0 0 0)\n" " (effects (font (size {:g} {:g})) hide)\n )\n", fieldSize, fieldSize ); m_Data += strbuf;
strbuf = fmt::format( " (property \"Datasheet\" \"\" (at 0 0 0)\n" " (effects (font (size {:g} {:g})) hide)\n )\n", fieldSize, fieldSize ); m_Data += strbuf;
strbuf = fmt::format( " (symbol \"{}_0_0\"\n", m_CmpName.c_str() ); m_Data += strbuf; break; }}
void BITMAPCONV_INFO::outputDataEnd(){ switch( m_Format ) { case POSTSCRIPT_FMT: m_Data += "grestore\n"; m_Data += "%%EOF\n"; break;
case PCBNEW_KICAD_MOD: m_Data += ")\n"; break;
case KICAD_WKS_LOGO: m_Data += " )\n)\n"; break;
case EESCHEMA_FMT: m_Data += " )\n"; // end symbol_0_0
m_Data += " )\n"; // end symbol
m_Data += ")\n"; // end lib
break; }}
void BITMAPCONV_INFO::outputOnePolygon( SHAPE_LINE_CHAIN& aPolygon, const char* aBrdLayerName ){ // write one polygon to output file.
// coordinates are expected in target unit.
int ii, jj; VECTOR2I currpoint; std::string strbuf;
int offsetX = (int)( m_PixmapWidth / 2 * m_ScaleX ); int offsetY = (int)( m_PixmapHeight / 2 * m_ScaleY );
const VECTOR2I startpoint = aPolygon.CPoint( 0 );
switch( m_Format ) { case POSTSCRIPT_FMT: offsetY = (int)( m_PixmapHeight * m_ScaleY ); strbuf = fmt::format( "newpath\n{} {} moveto\n", startpoint.x, offsetY - startpoint.y ); m_Data += strbuf; jj = 0;
for( ii = 1; ii < aPolygon.PointCount(); ii++ ) { currpoint = aPolygon.CPoint( ii ); strbuf = fmt::format( " {} {} lineto", currpoint.x, offsetY - currpoint.y ); m_Data += strbuf;
if( jj++ > 6 ) { jj = 0; m_Data += "\n"; } }
m_Data += "\nclosepath fill\n"; break;
case PCBNEW_KICAD_MOD: { double width = 0.0; // outline thickness in mm: no thickness
m_Data += " (fp_poly\n (pts\n";
jj = 0;
for( ii = 0; ii < aPolygon.PointCount(); ii++ ) { currpoint = aPolygon.CPoint( ii ); strbuf = fmt::format( " (xy {} {})\n", ( currpoint.x - offsetX ) / PCB_IU_PER_MM, ( currpoint.y - offsetY ) / PCB_IU_PER_MM ); m_Data += strbuf; } // No need to close polygon
m_Data += " )\n\n"; strbuf = fmt::format( " (stroke (width {:f}) (type solid)) (fill solid) (layer \"{}\") (uuid {}))\n", width, aBrdLayerName, KIID().AsString().ToStdString().c_str() );
m_Data += strbuf; } break;
case KICAD_WKS_LOGO: m_Data += " (pts";
// Internal units = micron, file unit = mm
jj = 1;
for( ii = 0; ii < aPolygon.PointCount(); ii++ ) { currpoint = aPolygon.CPoint( ii ); strbuf = fmt::format( " (xy {:.3f} {:.3f})", ( currpoint.x - offsetX ) / PL_IU_PER_MM, ( currpoint.y - offsetY ) / PL_IU_PER_MM ); m_Data += strbuf;
if( jj++ > 4 ) { jj = 0; m_Data += "\n "; } }
// Close polygon
strbuf = fmt::format( " (xy {:.3f} {:.3f}) )\n", ( startpoint.x - offsetX ) / PL_IU_PER_MM, ( startpoint.y - offsetY ) / PL_IU_PER_MM ); m_Data += strbuf; break;
case EESCHEMA_FMT: // The polygon outline thickness is fixed here to 0.01 ( 0.0 is the default thickness)
#define SCH_LINE_THICKNESS_MM 0.01
//snprintf( strbuf, sizeof(strbuf), "P %d 0 0 %d", (int) aPolygon.PointCount() + 1, EE_LINE_THICKNESS );
m_Data += " (polyline\n (pts\n";
for( ii = 0; ii < aPolygon.PointCount(); ii++ ) { currpoint = aPolygon.CPoint( ii ); strbuf = fmt::format( " (xy {:f} {:f})\n", ( currpoint.x - offsetX ) / SCH_IU_PER_MM, ( currpoint.y - offsetY ) / SCH_IU_PER_MM ); m_Data += strbuf; }
// Close polygon
strbuf = fmt::format( " (xy {:f} {:f})\n", ( startpoint.x - offsetX ) / SCH_IU_PER_MM, ( startpoint.y - offsetY ) / SCH_IU_PER_MM ); m_Data += strbuf; m_Data += " )\n"; // end pts
strbuf = fmt::format( " (stroke (width {:g}) (type default))\n (fill (type outline))\n", SCH_LINE_THICKNESS_MM );
m_Data += strbuf; m_Data += " )\n"; // end polyline
break; }}
void BITMAPCONV_INFO::createOutputData( BMP2CMP_MOD_LAYER aModLayer ){ std::vector <potrace_dpoint_t> cornersBuffer;
// polyset_areas is a set of polygon to draw
SHAPE_POLY_SET polyset_areas;
// polyset_holes is the set of holes inside polyset_areas outlines
SHAPE_POLY_SET polyset_holes;
potrace_dpoint_t( *c )[3];
LOCALE_IO toggle; // Temporary switch the locale to standard C to r/w floats
// The layer name has meaning only for .kicad_mod files.
// For these files the header creates 2 invisible texts: value and ref
// (needed but not useful) on silk screen layer
outputDataHeader( getBoardLayerName( MOD_LYR_FSILKS ) );
bool main_outline = true;
/* draw each as a polygon with no hole.
* Bezier curves are approximated by a polyline */ potrace_path_t* paths = m_Paths; // the list of paths
if(!m_Paths) { m_errors += "No shape in black and white image to convert: no outline created\n"; }
while( paths != nullptr ) { int cnt = paths->curve.n; int* tag = paths->curve.tag; c = paths->curve.c; potrace_dpoint_t startpoint = c[cnt - 1][2];
for( int i = 0; i < cnt; i++ ) { switch( tag[i] ) { case POTRACE_CORNER: cornersBuffer.push_back( c[i][1] ); cornersBuffer.push_back( c[i][2] ); startpoint = c[i][2]; break;
case POTRACE_CURVETO: BezierToPolyline( cornersBuffer, startpoint, c[i][0], c[i][1], c[i][2] ); startpoint = c[i][2]; break; } }
// Store current path
if( main_outline ) { main_outline = false;
// build the current main polygon
polyset_areas.NewOutline(); for( unsigned int i = 0; i < cornersBuffer.size(); i++ ) { polyset_areas.Append( int( cornersBuffer[i].x * m_ScaleX ), int( cornersBuffer[i].y * m_ScaleY ) ); } } else { // Add current hole in polyset_holes
polyset_holes.NewOutline();
for( unsigned int i = 0; i < cornersBuffer.size(); i++ ) { polyset_holes.Append( int( cornersBuffer[i].x * m_ScaleX ), int( cornersBuffer[i].y * m_ScaleY ) ); } }
cornersBuffer.clear();
// at the end of a group of a positive path and its negative children, fill.
if( paths->next == nullptr || paths->next->sign == '+' ) { polyset_areas.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); polyset_holes.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); polyset_areas.BooleanSubtract( polyset_holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
// Ensure there are no self intersecting polygons
if( polyset_areas.NormalizeAreaOutlines() ) { // Convert polygon with holes to a unique polygon
polyset_areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
// Output current resulting polygon(s)
for( int ii = 0; ii < polyset_areas.OutlineCount(); ii++ ) { SHAPE_LINE_CHAIN& poly = polyset_areas.Outline( ii ); outputOnePolygon( poly, getBoardLayerName( aModLayer )); }
polyset_areas.RemoveAllContours(); polyset_holes.RemoveAllContours(); main_outline = true; } }
paths = paths->next; }
outputDataEnd();}
// a helper function to calculate a square value
inline double square( double x ){ return x * x;}
// a helper function to calculate a cube value
inline double cube( double x ){ return x * x * x;}
/* render a Bezier curve. */void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer, potrace_dpoint_t p1, potrace_dpoint_t p2, potrace_dpoint_t p3, potrace_dpoint_t p4 ){ double dd0, dd1, dd, delta, e2, epsilon, t;
// p1 = starting point
/* we approximate the curve by small line segments. The interval
* size, epsilon, is determined on the fly so that the distance * between the true curve and its approximation does not exceed the * desired accuracy delta. */
delta = 0.25; /* desired accuracy, in pixels */
/* let dd = maximal value of 2nd derivative over curve - this must
* occur at an endpoint. */ dd0 = square( p1.x - 2 * p2.x + p3.x ) + square( p1.y - 2 * p2.y + p3.y ); dd1 = square( p2.x - 2 * p3.x + p4.x ) + square( p2.y - 2 * p3.y + p4.y ); dd = 6 * sqrt( std::max( dd0, dd1 ) ); e2 = 8 * delta <= dd ? 8 * delta / dd : 1; epsilon = sqrt( e2 ); /* necessary interval size */
for( t = epsilon; t<1; t += epsilon ) { potrace_dpoint_t intermediate_point; intermediate_point.x = p1.x * cube( 1 - t ) + 3* p2.x* square( 1 - t ) * t + 3 * p3.x * (1 - t) * square( t ) + p4.x* cube( t );
intermediate_point.y = p1.y * cube( 1 - t ) + 3* p2.y* square( 1 - t ) * t + 3 * p3.y * (1 - t) * square( t ) + p4.y* cube( t );
aCornersBuffer.push_back( intermediate_point ); }
aCornersBuffer.push_back( p4 );}
|