/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright (C) 1992-2013 jean-pierre.charras * Copyright (C) 1992-2019 Kicad Developers, see change_log.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 // std::max #include #include #include #include #include #include #include #include #include #include #include "bitmap2component.h" /* free a potrace bitmap */ static void bm_free( potrace_bitmap_t* bm ) { if( bm != NULL ) { free( bm->map ); } free( bm ); } /* Helper class to handle useful info to convert a bitmap image to * a polygonal object description */ class BITMAPCONV_INFO { public: enum OUTPUT_FMT_ID m_Format; // File format int m_PixmapWidth; int m_PixmapHeight; // the bitmap size in pixels double m_ScaleX; double m_ScaleY; // the conversion scale potrace_path_t* m_Paths; // the list of paths, from potrace (list of lines and bezier curves) FILE* m_Outfile; // File to create const char * m_CmpName; // The string used as cmp/footprint name public: BITMAPCONV_INFO(); /** * Function CreateOutputFile * Creates the output file specified by m_Outfile, * depending on file format given by m_Format */ void CreateOutputFile( BMP2CMP_MOD_LAYER aModLayer = (BMP2CMP_MOD_LAYER) 0 ); private: /** * Function OuputFileHeader * write to file the header depending on file format */ void OuputFileHeader( const char * aBrdLayerName ); /** * Function OuputFileEnd * write to file the last strings depending on file format */ void OuputFileEnd(); /** * @return the board layer name depending on the board layer selected * @param aChoice = the choice (MOD_LYR_FSILKS to MOD_LYR_FINAL) */ const char * getBrdLayerName( BMP2CMP_MOD_LAYER aChoice ); /** * Function OuputOnePolygon * write one polygon to output file. * Polygon coordinates are expected scaled by the polugon extraction function */ void OuputOnePolygon( SHAPE_LINE_CHAIN & aPolygon, const char* aBrdLayerName ); }; static void BezierToPolyline( std::vector & aCornersBuffer, potrace_dpoint_t p1, potrace_dpoint_t p2, potrace_dpoint_t p3, potrace_dpoint_t p4 ); BITMAPCONV_INFO::BITMAPCONV_INFO() { m_Format = POSTSCRIPT_FMT; m_PixmapWidth = 0; m_PixmapHeight = 0; m_ScaleX = 1.0; m_ScaleY = 1.0; m_Paths = NULL; m_Outfile = NULL; m_CmpName = "LOGO"; } int bitmap2component( potrace_bitmap_t* aPotrace_bitmap, FILE* aOutfile, 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 ) { fprintf( stderr, "Error allocating parameters: %s\n", strerror( errno ) ); return 1; } param->turdsize = 0; /* 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 ); fprintf( stderr, "Error tracing bitmap: %s\n", strerror( errno ) ); return 1; } BITMAPCONV_INFO info; info.m_PixmapWidth = aPotrace_bitmap->w; info.m_PixmapHeight = aPotrace_bitmap->h; // the bitmap size in pixels info.m_Paths = st->plist; info.m_Outfile = aOutfile; switch( aFormat ) { case KICAD_LOGO: info.m_Format = KICAD_LOGO; info.m_ScaleX = 1e3 * 25.4 / aDpi_X; // the conversion scale from PPI to micro info.m_ScaleY = 1e3 * 25.4 / aDpi_Y; // Y axis is top to bottom info.CreateOutputFile(); break; case POSTSCRIPT_FMT: info.m_Format = POSTSCRIPT_FMT; info.m_ScaleX = 1.0; // the conversion scale info.m_ScaleY = info.m_ScaleX; // output vector data, e.g. as a rudimentary EPS file (mainly for tests) info.CreateOutputFile(); break; case EESCHEMA_FMT: info.m_Format = EESCHEMA_FMT; info.m_ScaleX = 1000.0 / aDpi_X; // the conversion scale from PPI to UI info.m_ScaleY = -1000.0 / aDpi_Y; // Y axis is bottom to Top for components in libs info.CreateOutputFile(); break; case PCBNEW_KICAD_MOD: info.m_Format = PCBNEW_KICAD_MOD; info.m_ScaleX = 1e6 * 25.4 / aDpi_X; // the conversion scale from PPI to UI info.m_ScaleY = 1e6 * 25.4 / aDpi_Y; // Y axis is top to bottom in modedit info.CreateOutputFile( aModLayer ); break; default: break; } bm_free( aPotrace_bitmap ); potrace_state_free( st ); potrace_param_free( param ); return 0; } const char* BITMAPCONV_INFO::getBrdLayerName( BMP2CMP_MOD_LAYER aChoice ) { const char * layerName = "F.SilkS"; switch( aChoice ) { case MOD_LYR_FSOLDERMASK: layerName = "F.Mask"; break; case MOD_LYR_ECO1: layerName = "Eco1.User"; break; case MOD_LYR_ECO2: layerName = "Eco2.User"; break; case MOD_LYR_FSILKS: default: // case MOD_LYR_FSILKS only unless there is a bug break; } return layerName; } void BITMAPCONV_INFO::OuputFileHeader( const char * aBrdLayerName ) { int Ypos = (int) ( m_PixmapHeight / 2 * m_ScaleY ); int fieldSize; // fields text size = 60 mils switch( m_Format ) { case POSTSCRIPT_FMT: /* output vector data, e.g. as a rudimentary EPS file */ fprintf( m_Outfile, "%%!PS-Adobe-3.0 EPSF-3.0\n" ); fprintf( m_Outfile, "%%%%BoundingBox: 0 0 %d %d\n", m_PixmapWidth, m_PixmapHeight ); fprintf( m_Outfile, "gsave\n" ); break; case PCBNEW_KICAD_MOD: // fields text size = 1.5 mm // fields text thickness = 1.5 / 5 = 0.3mm fprintf( m_Outfile, "(module %s (layer F.Cu)\n (at 0 0)\n", m_CmpName ); fprintf( m_Outfile, " (fp_text reference \"G***\" (at 0 0) (layer %s) hide\n" " (effects (font (thickness 0.3)))\n )\n", aBrdLayerName ); fprintf( m_Outfile, " (fp_text value \"%s\" (at 0.75 0) (layer %s) hide\n" " (effects (font (thickness 0.3)))\n )\n", m_CmpName, aBrdLayerName ); break; case KICAD_LOGO: fprintf( m_Outfile, "(polygon (pos 0 0 rbcorner) (rotate 0) (linewidth 0.01)\n" ); break; case EESCHEMA_FMT: fprintf( m_Outfile, "EESchema-LIBRARY Version 2.3\n" ); fprintf( m_Outfile, "#\n# %s\n", m_CmpName ); fprintf( m_Outfile, "# pixmap size w = %d, h = %d\n#\n", m_PixmapWidth, m_PixmapHeight ); // print reference and value fieldSize = 60; // fields text size = 60 mils Ypos += fieldSize / 2; fprintf( m_Outfile, "DEF %s G 0 40 Y Y 1 F N\n", m_CmpName ); fprintf( m_Outfile, "F0 \"#G\" 0 %d %d H I C CNN\n", Ypos, fieldSize ); fprintf( m_Outfile, "F1 \"%s\" 0 %d %d H I C CNN\n", m_CmpName, -Ypos, fieldSize ); fprintf( m_Outfile, "DRAW\n" ); break; } } void BITMAPCONV_INFO::OuputFileEnd() { switch( m_Format ) { case POSTSCRIPT_FMT: fprintf( m_Outfile, "grestore\n" ); fprintf( m_Outfile, "%%EOF\n" ); break; case PCBNEW_KICAD_MOD: fprintf( m_Outfile, ")\n" ); break; case KICAD_LOGO: fprintf( m_Outfile, ")\n" ); break; case EESCHEMA_FMT: fprintf( m_Outfile, "ENDDRAW\n" ); fprintf( m_Outfile, "ENDDEF\n" ); break; } } /** * Function OuputOnePolygon * write one polygon to output file. * Polygon coordinates are expected scaled by the polygon extraction function */ void BITMAPCONV_INFO::OuputOnePolygon( SHAPE_LINE_CHAIN & aPolygon, const char* aBrdLayerName ) { int ii, jj; VECTOR2I currpoint; 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 ); fprintf( m_Outfile, "newpath\n%d %d moveto\n", startpoint.x, offsetY - startpoint.y ); jj = 0; for( ii = 1; ii < aPolygon.PointCount(); ii++ ) { currpoint = aPolygon.CPoint( ii ); fprintf( m_Outfile, " %d %d lineto", currpoint.x, offsetY - currpoint.y ); if( jj++ > 6 ) { jj = 0; fprintf( m_Outfile, ("\n") ); } } fprintf( m_Outfile, "\nclosepath fill\n" ); break; case PCBNEW_KICAD_MOD: { double width = 0.01; // outline thickness in mm fprintf( m_Outfile, " (fp_poly (pts" ); jj = 0; for( ii = 0; ii < aPolygon.PointCount(); ii++ ) { currpoint = aPolygon.CPoint( ii ); fprintf( m_Outfile, " (xy %f %f)", ( currpoint.x - offsetX ) / 1e6, ( currpoint.y - offsetY ) / 1e6 ); if( jj++ > 6 ) { jj = 0; fprintf( m_Outfile, ("\n ") ); } } // Close polygon fprintf( m_Outfile, " (xy %f %f) )", ( startpoint.x - offsetX ) / 1e6, ( startpoint.y - offsetY ) / 1e6 ); fprintf( m_Outfile, "(layer %s) (width %f)\n )\n", aBrdLayerName, width ); } break; case KICAD_LOGO: fprintf( m_Outfile, " (pts" ); // Internal units = micron, file unit = mm jj = 0; for( ii = 0; ii < aPolygon.PointCount(); ii++ ) { currpoint = aPolygon.CPoint( ii ); fprintf( m_Outfile, " (xy %.3f %.3f)", ( currpoint.x - offsetX ) / 1e3, ( currpoint.y - offsetY ) / 1e3 ); if( jj++ > 4 ) { jj = 0; fprintf( m_Outfile, ("\n ") ); } } // Close polygon fprintf( m_Outfile, " (xy %.3f %.3f) )\n", ( startpoint.x - offsetX ) / 1e3, ( startpoint.y - offsetY ) / 1e3 ); break; case EESCHEMA_FMT: fprintf( m_Outfile, "P %d 0 0 1", (int) aPolygon.PointCount() + 1 ); for( ii = 0; ii < aPolygon.PointCount(); ii++ ) { currpoint = aPolygon.CPoint( ii ); fprintf( m_Outfile, " %d %d", currpoint.x - offsetX, currpoint.y - offsetY ); } // Close polygon fprintf( m_Outfile, " %d %d", startpoint.x - offsetX, startpoint.y - offsetY ); fprintf( m_Outfile, " F\n" ); break; } } void BITMAPCONV_INFO::CreateOutputFile( BMP2CMP_MOD_LAYER aModLayer ) { std::vector 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 usefull) on silk screen layer OuputFileHeader( getBrdLayerName( 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) printf("NULL Paths!\n"); while( paths != NULL ) { 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 == NULL || paths->next->sign == '+' ) { // Substract holes to main polygon: 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 ); 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 ); OuputOnePolygon(poly, getBrdLayerName( aModLayer ) ); } polyset_areas.RemoveAllContours(); polyset_holes.RemoveAllContours(); main_outline = true; } paths = paths->next; } OuputFileEnd(); } // 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 & 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 ); }