KiCad PCB EDA Suite
bitmap2component.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) 1992-2019 jean-pierre.charras
5  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include <algorithm> // std::max
26 #include <cerrno>
27 #include <cmath>
28 #include <cstdio>
29 #include <cstdlib>
30 #include <cstring>
31 #include <string>
32 #include <vector>
33 
34 #include <convert_to_biu.h>
35 #include <layer_ids.h>
36 
37 #include <locale_io.h>
38 #include <potracelib.h>
39 
40 #include "bitmap2component.h"
41 
42 
43 /* free a potrace bitmap */
44 static void bm_free( potrace_bitmap_t* bm )
45 {
46  if( bm != nullptr )
47  {
48  free( bm->map );
49  }
50 
51  free( bm );
52 }
53 
54 
55 static void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
56  potrace_dpoint_t p1,
57  potrace_dpoint_t p2,
58  potrace_dpoint_t p3,
59  potrace_dpoint_t p4 );
60 
61 
62 BITMAPCONV_INFO::BITMAPCONV_INFO( std::string& aData ):
63  m_Data( aData )
64 {
66  m_PixmapWidth = 0;
67  m_PixmapHeight = 0;
68  m_ScaleX = 1.0;
69  m_ScaleY = 1.0;
70  m_Paths = nullptr;
71  m_CmpName = "LOGO";
72 }
73 
74 
75 int BITMAPCONV_INFO::ConvertBitmap( potrace_bitmap_t* aPotrace_bitmap, OUTPUT_FMT_ID aFormat,
76  int aDpi_X, int aDpi_Y, BMP2CMP_MOD_LAYER aModLayer )
77 {
78  potrace_param_t* param;
79  potrace_state_t* st;
80 
81  // set tracing parameters, starting from defaults
82  param = potrace_param_default();
83 
84  if( !param )
85  {
86  char msg[256];
87  sprintf( msg, "Error allocating parameters: %s\n", strerror( errno ) );
88  m_errors += msg;
89  return 1;
90  }
91 
92  // For parameters: see http://potrace.sourceforge.net/potracelib.pdf
93  param->turdsize = 0; // area (in pixels) of largest path to be ignored.
94  // Potrace default is 2
95  param->opttolerance = 0.2; // curve optimization tolerance. Potrace default is 0.2
96 
97  /* convert the bitmap to curves */
98  st = potrace_trace( param, aPotrace_bitmap );
99 
100  if( !st || st->status != POTRACE_STATUS_OK )
101  {
102  if( st )
103  {
104  potrace_state_free( st );
105  }
106 
107  potrace_param_free( param );
108 
109  char msg[256];
110  sprintf( msg, "Error tracing bitmap: %s\n", strerror( errno ) );
111  m_errors += msg;
112  return 1;
113  }
114 
115  m_PixmapWidth = aPotrace_bitmap->w;
116  m_PixmapHeight = aPotrace_bitmap->h; // the bitmap size in pixels
117  m_Paths = st->plist;
118 
119  switch( aFormat )
120  {
121  case KICAD_WKS_LOGO:
123  m_ScaleX = PL_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to micron
124  m_ScaleY = PL_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom
126  break;
127 
128  case POSTSCRIPT_FMT:
130  m_ScaleX = 1.0; // the conversion scale
131  m_ScaleY = m_ScaleX;
132 
133  // output vector data, e.g. as a rudimentary EPS file (mainly for tests)
135  break;
136 
137  case EESCHEMA_FMT:
139  m_ScaleX = SCH_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to eeschema iu
140  m_ScaleY = -SCH_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is bottom to Top for components in libs
142  break;
143 
144  case PCBNEW_KICAD_MOD:
146  m_ScaleX = PCB_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to IU
147  m_ScaleY = PCB_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom in Footprint Editor
148  createOutputData( aModLayer );
149  break;
150 
151  default:
152  break;
153  }
154 
155 
156  bm_free( aPotrace_bitmap );
157  potrace_state_free( st );
158  potrace_param_free( param );
159 
160  return 0;
161 }
162 
163 
165 {
166  const char* layerName = "F.SilkS";
167 
168  switch( aChoice )
169  {
170  case MOD_LYR_FSOLDERMASK:
171  layerName = "F.Mask";
172  break;
173 
174  case MOD_LYR_ECO1:
175  layerName = "Eco1.User";
176  break;
177 
178  case MOD_LYR_ECO2:
179  layerName = "Eco2.User";
180  break;
181 
182  case MOD_LYR_FSILKS:
183  default: // case MOD_LYR_FSILKS only unless there is a bug
184  break;
185  }
186 
187  return layerName;
188 }
189 
190 
191 void BITMAPCONV_INFO::outputDataHeader( const char * aBrdLayerName )
192 {
193  double Ypos = ( m_PixmapHeight / 2 * m_ScaleY ); // fields Y position in mm
194  double fieldSize; // fields text size in mm
195  char strbuf[1024];
196 
197  switch( m_Format )
198  {
199  case POSTSCRIPT_FMT:
200  /* output vector data, e.g. as a rudimentary EPS file */
201  m_Data += "%%!PS-Adobe-3.0 EPSF-3.0\n";
202  sprintf( strbuf, "%%%%BoundingBox: 0 0 %d %d\n", m_PixmapWidth, m_PixmapHeight );
203  m_Data += strbuf;
204  m_Data += "gsave\n";
205  break;
206 
207  case PCBNEW_KICAD_MOD:
208  // fields text size = 1.5 mm
209  // fields text thickness = 1.5 / 5 = 0.3mm
210  sprintf( strbuf, "(footprint \"%s\" (version 20210606) (generator bitmap2component) "
211  "(layer \"F.Cu\")\n (at 0 0)\n", m_CmpName.c_str() );
212  m_Data += strbuf;
213  sprintf( strbuf, "(attr board_only exclude_from_pos_files exclude_from_bom)\n");
214  m_Data += strbuf;
215  sprintf( strbuf, " (fp_text reference \"G***\" (at 0 0) (layer %s)\n"
216  " (effects (font (thickness 0.3)))\n )\n", aBrdLayerName );
217  m_Data += strbuf;
218  sprintf( strbuf, " (fp_text value \"%s\" (at 0.75 0) (layer %s) hide\n"
219  " (effects (font (thickness 0.3)))\n )\n", m_CmpName.c_str(), aBrdLayerName );
220  m_Data += strbuf;
221  break;
222 
223  case KICAD_WKS_LOGO:
224  m_Data += "(kicad_wks (version 20210606) (generator bitmap2component)\n";
225  m_Data += " (polygon (pos 0 0 rbcorner) (rotate 0) (linewidth 0.01)\n";
226  break;
227 
228  case EESCHEMA_FMT:
229  fieldSize = 1.27; // fields text size in mm (= 50 mils)
230  Ypos /= SCH_IU_PER_MM;
231  Ypos += fieldSize / 2;
232  // sprintf( strbuf, "# pixmap size w = %d, h = %d\n#\n", m_PixmapWidth, m_PixmapHeight );
233  sprintf( strbuf, "(kicad_symbol_lib (version 20210619) (generator bitmap2component)\n"
234  " (symbol \"%s\" (pin_names (offset 1.016)) (in_bom yes) (on_board yes)\n",
235  m_CmpName.c_str() );
236  m_Data += strbuf;
237 
238  sprintf( strbuf, " (property \"Reference\" \"#G\" (id 0) (at 0 %g 0)\n"
239  " (effects (font (size %g %g)) hide)\n )\n",
240  -Ypos, fieldSize, fieldSize );
241  m_Data += strbuf;
242 
243  sprintf( strbuf, " (property \"Value\" \"%s\" (id 1) (at 0 %g 0)\n"
244  " (effects (font (size %g %g)) hide)\n )\n",
245  m_CmpName.c_str(), Ypos, fieldSize, fieldSize );
246  m_Data += strbuf;
247 
248  sprintf( strbuf, " (property \"Footprint\" \"\" (id 2) (at 0 0 0)\n"
249  " (effects (font (size %g %g)) hide)\n )\n",
250  fieldSize, fieldSize );
251  m_Data += strbuf;
252 
253  sprintf( strbuf, " (property \"Datasheet\" \"\" (id 3) (at 0 0 0)\n"
254  " (effects (font (size %g %g)) hide)\n )\n",
255  fieldSize, fieldSize );
256  m_Data += strbuf;
257 
258  sprintf( strbuf, " (symbol \"%s_0_0\"\n", m_CmpName.c_str() );
259  m_Data += strbuf;
260  break;
261  }
262 }
263 
264 
266 {
267  switch( m_Format )
268  {
269  case POSTSCRIPT_FMT:
270  m_Data += "grestore\n";
271  m_Data += "%%EOF\n";
272  break;
273 
274  case PCBNEW_KICAD_MOD:
275  m_Data += ")\n";
276  break;
277 
278  case KICAD_WKS_LOGO:
279  m_Data += " )\n)\n";
280  break;
281 
282  case EESCHEMA_FMT:
283  m_Data += " )\n"; // end symbol_0_0
284  m_Data += " )\n"; // end symbol
285  m_Data += ")\n"; // end lib
286  break;
287  }
288 }
289 
290 
291 void BITMAPCONV_INFO::outputOnePolygon( SHAPE_LINE_CHAIN& aPolygon, const char* aBrdLayerName )
292 {
293  // write one polygon to output file.
294  // coordinates are expected in target unit.
295  int ii, jj;
296  VECTOR2I currpoint;
297  char strbuf[1024];
298 
299  int offsetX = (int)( m_PixmapWidth / 2 * m_ScaleX );
300  int offsetY = (int)( m_PixmapHeight / 2 * m_ScaleY );
301 
302  const VECTOR2I startpoint = aPolygon.CPoint( 0 );
303 
304  switch( m_Format )
305  {
306  case POSTSCRIPT_FMT:
307  offsetY = (int)( m_PixmapHeight * m_ScaleY );
308  sprintf( strbuf, "newpath\n%d %d moveto\n", startpoint.x, offsetY - startpoint.y );
309  m_Data += strbuf;
310  jj = 0;
311 
312  for( ii = 1; ii < aPolygon.PointCount(); ii++ )
313  {
314  currpoint = aPolygon.CPoint( ii );
315  sprintf( strbuf, " %d %d lineto", currpoint.x, offsetY - currpoint.y );
316  m_Data += strbuf;
317 
318  if( jj++ > 6 )
319  {
320  jj = 0;
321  m_Data += "\n";
322  }
323  }
324 
325  m_Data += "\nclosepath fill\n";
326  break;
327 
328  case PCBNEW_KICAD_MOD:
329  {
330  double width = 0.0; // outline thickness in mm: no thickness
331  m_Data += " (fp_poly (pts";
332 
333  jj = 0;
334 
335  for( ii = 0; ii < aPolygon.PointCount(); ii++ )
336  {
337  currpoint = aPolygon.CPoint( ii );
338  sprintf( strbuf, " (xy %f %f)",
339  ( currpoint.x - offsetX ) / PCB_IU_PER_MM,
340  ( currpoint.y - offsetY ) / PCB_IU_PER_MM );
341  m_Data += strbuf;
342 
343  if( jj++ > 6 )
344  {
345  jj = 0;
346  m_Data += "\n ";
347  }
348  }
349  // No need to close polygon
350 
351  m_Data += " )";
352  sprintf( strbuf, "(layer %s) (width %f)\n )\n", aBrdLayerName, width );
353  m_Data += strbuf;
354  }
355  break;
356 
357  case KICAD_WKS_LOGO:
358  m_Data += " (pts";
359 
360  // Internal units = micron, file unit = mm
361  jj = 0;
362 
363  for( ii = 0; ii < aPolygon.PointCount(); ii++ )
364  {
365  currpoint = aPolygon.CPoint( ii );
366  sprintf( strbuf, " (xy %.3f %.3f)",
367  ( currpoint.x - offsetX ) / PL_IU_PER_MM,
368  ( currpoint.y - offsetY ) / PL_IU_PER_MM );
369  m_Data += strbuf;
370 
371  if( jj++ > 4 )
372  {
373  jj = 0;
374  m_Data += "\n ";
375  }
376  }
377 
378  // Close polygon
379  sprintf( strbuf, " (xy %.3f %.3f) )\n",
380  ( startpoint.x - offsetX ) / PL_IU_PER_MM,
381  ( startpoint.y - offsetY ) / PL_IU_PER_MM );
382  m_Data += strbuf;
383  break;
384 
385  case EESCHEMA_FMT:
386  // The polygon outline thickness is fixed here to 0.01 ( 0.0 is the default thickness)
387  #define SCH_LINE_THICKNESS_MM 0.01
388  //sprintf( strbuf, "P %d 0 0 %d", (int) aPolygon.PointCount() + 1, EE_LINE_THICKNESS );
389  m_Data += " (polyline\n (pts\n";
390 
391  for( ii = 0; ii < aPolygon.PointCount(); ii++ )
392  {
393  currpoint = aPolygon.CPoint( ii );
394  sprintf( strbuf, " (xy %f %f)\n",
395  ( currpoint.x - offsetX ) / SCH_IU_PER_MM,
396  ( currpoint.y - offsetY ) / SCH_IU_PER_MM );
397  m_Data += strbuf;
398  }
399 
400  // Close polygon
401  sprintf( strbuf, " (xy %f %f)\n",
402  ( startpoint.x - offsetX ) / SCH_IU_PER_MM,
403  ( startpoint.y - offsetY ) / SCH_IU_PER_MM );
404  m_Data += strbuf;
405  m_Data += " )\n"; // end pts
406 
407  sprintf( strbuf, " (stroke (width %g)) (fill (type outline))\n",
409  m_Data += strbuf;
410  m_Data += " )\n"; // end polyline
411  break;
412  }
413 }
414 
415 
417 {
418  std::vector <potrace_dpoint_t> cornersBuffer;
419 
420  // polyset_areas is a set of polygon to draw
421  SHAPE_POLY_SET polyset_areas;
422 
423  // polyset_holes is the set of holes inside polyset_areas outlines
424  SHAPE_POLY_SET polyset_holes;
425 
426  potrace_dpoint_t( *c )[3];
427 
428  LOCALE_IO toggle; // Temporary switch the locale to standard C to r/w floats
429 
430  // The layer name has meaning only for .kicad_mod files.
431  // For these files the header creates 2 invisible texts: value and ref
432  // (needed but not useful) on silk screen layer
434 
435  bool main_outline = true;
436 
437  /* draw each as a polygon with no hole.
438  * Bezier curves are approximated by a polyline
439  */
440  potrace_path_t* paths = m_Paths; // the list of paths
441 
442  if(!m_Paths)
443  {
444  m_errors += "No path in black and white image: no outline created\n";
445  }
446 
447  while( paths != nullptr )
448  {
449  int cnt = paths->curve.n;
450  int* tag = paths->curve.tag;
451  c = paths->curve.c;
452  potrace_dpoint_t startpoint = c[cnt - 1][2];
453 
454  for( int i = 0; i < cnt; i++ )
455  {
456  switch( tag[i] )
457  {
458  case POTRACE_CORNER:
459  cornersBuffer.push_back( c[i][1] );
460  cornersBuffer.push_back( c[i][2] );
461  startpoint = c[i][2];
462  break;
463 
464  case POTRACE_CURVETO:
465  BezierToPolyline( cornersBuffer, startpoint, c[i][0], c[i][1], c[i][2] );
466  startpoint = c[i][2];
467  break;
468  }
469  }
470 
471  // Store current path
472  if( main_outline )
473  {
474  main_outline = false;
475 
476  // build the current main polygon
477  polyset_areas.NewOutline();
478  for( unsigned int i = 0; i < cornersBuffer.size(); i++ )
479  {
480  polyset_areas.Append( int( cornersBuffer[i].x * m_ScaleX ),
481  int( cornersBuffer[i].y * m_ScaleY ) );
482  }
483  }
484  else
485  {
486  // Add current hole in polyset_holes
487  polyset_holes.NewOutline();
488 
489  for( unsigned int i = 0; i < cornersBuffer.size(); i++ )
490  {
491  polyset_holes.Append( int( cornersBuffer[i].x * m_ScaleX ),
492  int( cornersBuffer[i].y * m_ScaleY ) );
493  }
494  }
495 
496  cornersBuffer.clear();
497 
498  // at the end of a group of a positive path and its negative children, fill.
499  if( paths->next == nullptr || paths->next->sign == '+' )
500  {
503  polyset_areas.BooleanSubtract( polyset_holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
504 
505  // Ensure there are no self intersecting polygons
506  polyset_areas.NormalizeAreaOutlines();
507 
508  // Convert polygon with holes to a unique polygon
510 
511  // Output current resulting polygon(s)
512  for( int ii = 0; ii < polyset_areas.OutlineCount(); ii++ )
513  {
514  SHAPE_LINE_CHAIN& poly = polyset_areas.Outline( ii );
515  outputOnePolygon( poly, getBoardLayerName( aModLayer ));
516  }
517 
518  polyset_areas.RemoveAllContours();
519  polyset_holes.RemoveAllContours();
520  main_outline = true;
521  }
522 
523  paths = paths->next;
524  }
525 
526  outputDataEnd();
527 }
528 
529 // a helper function to calculate a square value
530 inline double square( double x )
531 {
532  return x * x;
533 }
534 
535 
536 // a helper function to calculate a cube value
537 inline double cube( double x )
538 {
539  return x * x * x;
540 }
541 
542 
543 /* render a Bezier curve. */
544 void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
545  potrace_dpoint_t p1,
546  potrace_dpoint_t p2,
547  potrace_dpoint_t p3,
548  potrace_dpoint_t p4 )
549 {
550  double dd0, dd1, dd, delta, e2, epsilon, t;
551 
552  // p1 = starting point
553 
554  /* we approximate the curve by small line segments. The interval
555  * size, epsilon, is determined on the fly so that the distance
556  * between the true curve and its approximation does not exceed the
557  * desired accuracy delta. */
558 
559  delta = 0.25; /* desired accuracy, in pixels */
560 
561  /* let dd = maximal value of 2nd derivative over curve - this must
562  * occur at an endpoint. */
563  dd0 = square( p1.x - 2 * p2.x + p3.x ) + square( p1.y - 2 * p2.y + p3.y );
564  dd1 = square( p2.x - 2 * p3.x + p4.x ) + square( p2.y - 2 * p3.y + p4.y );
565  dd = 6 * sqrt( std::max( dd0, dd1 ) );
566  e2 = 8 * delta <= dd ? 8 * delta / dd : 1;
567  epsilon = sqrt( e2 ); /* necessary interval size */
568 
569  for( t = epsilon; t<1; t += epsilon )
570  {
571  potrace_dpoint_t intermediate_point;
572  intermediate_point.x = p1.x * cube( 1 - t ) +
573  3* p2.x* square( 1 - t ) * t +
574  3 * p3.x * (1 - t) * square( t ) +
575  p4.x* cube( t );
576 
577  intermediate_point.y = p1.y * cube( 1 - t ) +
578  3* p2.y* square( 1 - t ) * t +
579  3 * p3.y * (1 - t) * square( t ) + p4.y* cube( t );
580 
581  aCornersBuffer.push_back( intermediate_point );
582  }
583 
584  aCornersBuffer.push_back( p4 );
585 }
int OutlineCount() const
Return the number of vertices in a given outline/hole.
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:40
std::string m_CmpName
potrace_path_t * m_Paths
int NormalizeAreaOutlines()
Convert a self-intersecting polygon to one (or more) non self-intersecting polygon(s).
int PointCount() const
Return the number of points (vertices) in this line chain.
BITMAPCONV_INFO(std::string &aData)
static void bm_free(potrace_bitmap_t *bm)
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
void createOutputData(BMP2CMP_MOD_LAYER aModLayer=(BMP2CMP_MOD_LAYER) 0)
Creates the data specified by m_Format.
enum OUTPUT_FMT_ID m_Format
void outputDataHeader(const char *aBrdLayerName)
Function outputDataHeader write to file the header depending on file format.
BMP2CMP_MOD_LAYER
Represent a set of closed polygons.
constexpr double PCB_IU_PER_MM
SHAPE_LINE_CHAIN & Outline(int aIndex)
const char * getBoardLayerName(BMP2CMP_MOD_LAYER aChoice)
void Simplify(POLYGON_MODE aFastMode)
#define SCH_LINE_THICKNESS_MM
int NewOutline()
Creates a new hole in a given outline.
void Fracture(POLYGON_MODE aFastMode)
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
std::string m_errors
constexpr double SCH_IU_PER_MM
double cube(double x)
std::string & m_Data
void outputDataEnd()
Function outputDataEnd write to file the last strings depending on file format.
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)
constexpr double PL_IU_PER_MM
void outputOnePolygon(SHAPE_LINE_CHAIN &aPolygon, const char *aBrdLayerName)
Function outputOnePolygon write one polygon to output file.
Represent a polyline (an zero-thickness chain of connected line segments).
constexpr int delta
OUTPUT_FMT_ID
double square(double x)
void BooleanSubtract(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset intersection For aFastMode meaning, see function booleanOp.
int ConvertBitmap(potrace_bitmap_t *aPotrace_bitmap, OUTPUT_FMT_ID aFormat, int aDpi_X, int aDpi_Y, BMP2CMP_MOD_LAYER aModLayer)
Run the conversion of the bitmap.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Add a new vertex to the contour indexed by aOutline and aHole (defaults to the outline of the last po...