KiCad PCB EDA Suite
bitmap2component/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-2019 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 <common.h>
36 
37 #include <potracelib.h>
38 
39 #include "bitmap2component.h"
40 
41 // Unit conversion. Coord unit from potrace is mm
42 #define MM2MICRON 1e3 // For pl_editor
43 #define MM2NANOMETER 1e6 // For pcbew
44 
45 /* free a potrace bitmap */
46 static void bm_free( potrace_bitmap_t* bm )
47 {
48  if( bm != NULL )
49  {
50  free( bm->map );
51  }
52  free( bm );
53 }
54 
55 
56 static void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
57  potrace_dpoint_t p1,
58  potrace_dpoint_t p2,
59  potrace_dpoint_t p3,
60  potrace_dpoint_t p4 );
61 
62 
63 BITMAPCONV_INFO::BITMAPCONV_INFO( std::string& aData ):
64  m_Data( aData )
65 {
67  m_PixmapWidth = 0;
68  m_PixmapHeight = 0;
69  m_ScaleX = 1.0;
70  m_ScaleY = 1.0;
71  m_Paths = NULL;
72  m_CmpName = "LOGO";
73 }
74 
75 
76 int BITMAPCONV_INFO::ConvertBitmap( potrace_bitmap_t* aPotrace_bitmap,
77  OUTPUT_FMT_ID aFormat, int aDpi_X, int aDpi_Y,
78  BMP2CMP_MOD_LAYER aModLayer )
79 {
80  potrace_param_t* param;
81  potrace_state_t* st;
82 
83  // set tracing parameters, starting from defaults
84  param = potrace_param_default();
85 
86  if( !param )
87  {
88  char msg[256];
89  sprintf( msg, "Error allocating parameters: %s\n", strerror( errno ) );
90  m_errors += msg;
91  return 1;
92  }
93 
94  // For parameters: see http://potrace.sourceforge.net/potracelib.pdf
95  param->turdsize = 0; // area (in pixels) of largest path to be ignored.
96  // Potrace default is 2
97  param->opttolerance = 0.2; // curve optimization tolerance. Potrace default is 0.2
98 
99  /* convert the bitmap to curves */
100  st = potrace_trace( param, aPotrace_bitmap );
101 
102  if( !st || st->status != POTRACE_STATUS_OK )
103  {
104  if( st )
105  {
106  potrace_state_free( st );
107  }
108  potrace_param_free( param );
109 
110  char msg[256];
111  sprintf( msg, "Error tracing bitmap: %s\n", strerror( errno ) );
112  m_errors += msg;
113  return 1;
114  }
115 
116  m_PixmapWidth = aPotrace_bitmap->w;
117  m_PixmapHeight = aPotrace_bitmap->h; // the bitmap size in pixels
118  m_Paths = st->plist;
119 
120  switch( aFormat )
121  {
122  case KICAD_LOGO:
124  m_ScaleX = MM2MICRON * 25.4 / aDpi_X; // the conversion scale from PPI to micron
125  m_ScaleY = MM2MICRON * 25.4 / aDpi_Y; // Y axis is top to bottom
127  break;
128 
129  case POSTSCRIPT_FMT:
131  m_ScaleX = 1.0; // the conversion scale
132  m_ScaleY = m_ScaleX;
133  // output vector data, e.g. as a rudimentary EPS file (mainly for tests)
135  break;
136 
137  case EESCHEMA_FMT:
139  m_ScaleX = 1000.0 / aDpi_X; // the conversion scale from PPI to UI (mil)
140  m_ScaleY = -1000.0 / aDpi_Y; // Y axis is bottom to Top for components in libs
142  break;
143 
144  case PCBNEW_KICAD_MOD:
146  m_ScaleX = MM2NANOMETER * 25.4 / aDpi_X; // the conversion scale from PPI to UI
147  m_ScaleY = MM2NANOMETER * 25.4 / aDpi_Y; // Y axis is top to bottom in modedit
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  int Ypos = (int) ( m_PixmapHeight / 2 * m_ScaleY );
194  int fieldSize; // fields text size = 60 mils
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, "(module %s (layer F.Cu)\n (at 0 0)\n", m_CmpName.c_str() );
211  m_Data += strbuf;
212  sprintf( strbuf, " (fp_text reference \"G***\" (at 0 0) (layer %s)\n"
213  " (effects (font (thickness 0.3)))\n )\n", aBrdLayerName );
214  m_Data += strbuf;
215  sprintf( strbuf, " (fp_text value \"%s\" (at 0.75 0) (layer %s) hide\n"
216  " (effects (font (thickness 0.3)))\n )\n", m_CmpName.c_str(), aBrdLayerName );
217  m_Data += strbuf;
218  break;
219 
220  case KICAD_LOGO:
221  m_Data += "(polygon (pos 0 0 rbcorner) (rotate 0) (linewidth 0.01)\n";
222  break;
223 
224  case EESCHEMA_FMT:
225  sprintf( strbuf, "EESchema-LIBRARY Version 2.3\n" );
226  m_Data += strbuf;
227  sprintf( strbuf, "#\n# %s\n", m_CmpName.c_str() );
228  m_Data += strbuf;
229  sprintf( strbuf, "# pixmap size w = %d, h = %d\n#\n",
231  m_Data += strbuf;
232 
233  // print reference and value
234  fieldSize = 50; // fields text size = 50 mils
235  Ypos += fieldSize / 2;
236  sprintf( strbuf, "DEF %s G 0 40 Y Y 1 F N\n", m_CmpName.c_str() );
237  m_Data += strbuf;
238  sprintf( strbuf, "F0 \"#G\" 0 %d %d H I C CNN\n", Ypos, fieldSize );
239  m_Data += strbuf;
240  sprintf( strbuf, "F1 \"%s\" 0 %d %d H I C CNN\n", m_CmpName.c_str(), -Ypos, fieldSize );
241  m_Data += strbuf;
242  m_Data += "DRAW\n";
243  break;
244  }
245 }
246 
247 
249 {
250  switch( m_Format )
251  {
252  case POSTSCRIPT_FMT:
253  m_Data += "grestore\n";
254  m_Data += "%%EOF\n";
255  break;
256 
257  case PCBNEW_KICAD_MOD:
258  m_Data += ")\n";
259  break;
260 
261  case KICAD_LOGO:
262  m_Data += ")\n";
263  break;
264 
265  case EESCHEMA_FMT:
266  m_Data += "ENDDRAW\n";
267  m_Data += "ENDDEF\n";
268  break;
269  }
270 }
271 
272 
273 void BITMAPCONV_INFO::outputOnePolygon( SHAPE_LINE_CHAIN & aPolygon, const char* aBrdLayerName )
274 {
275  // write one polygon to output file.
276  // coordinates are expected in target unit.
277  int ii, jj;
278  VECTOR2I currpoint;
279  char strbuf[1024];
280 
281  int offsetX = (int)( m_PixmapWidth / 2 * m_ScaleX );
282  int offsetY = (int)( m_PixmapHeight / 2 * m_ScaleY );
283 
284  const VECTOR2I startpoint = aPolygon.CPoint( 0 );
285 
286  switch( m_Format )
287  {
288  case POSTSCRIPT_FMT:
289  offsetY = (int)( m_PixmapHeight * m_ScaleY );
290  sprintf( strbuf, "newpath\n%d %d moveto\n",
291  startpoint.x, offsetY - startpoint.y );
292  m_Data += strbuf;
293  jj = 0;
294  for( ii = 1; ii < aPolygon.PointCount(); ii++ )
295  {
296  currpoint = aPolygon.CPoint( ii );
297  sprintf( strbuf, " %d %d lineto",
298  currpoint.x, offsetY - currpoint.y );
299  m_Data += strbuf;
300 
301  if( jj++ > 6 )
302  {
303  jj = 0;
304  m_Data += "\n";
305  }
306  }
307 
308  m_Data += "\nclosepath fill\n";
309  break;
310 
311  case PCBNEW_KICAD_MOD:
312  {
313  double width = 0.0; // outline thickness in mm: no thickness
314  m_Data += " (fp_poly (pts";
315 
316  jj = 0;
317  for( ii = 0; ii < aPolygon.PointCount(); ii++ )
318  {
319  currpoint = aPolygon.CPoint( ii );
320  sprintf( strbuf, " (xy %f %f)",
321  ( currpoint.x - offsetX ) / MM2NANOMETER,
322  ( currpoint.y - offsetY ) / MM2NANOMETER );
323  m_Data += strbuf;
324 
325  if( jj++ > 6 )
326  {
327  jj = 0;
328  m_Data += "\n ";
329  }
330  }
331  // No need to close polygon
332  m_Data += " )";
333  sprintf( strbuf, "(layer %s) (width %f)\n )\n", aBrdLayerName, width );
334  m_Data += strbuf;
335  }
336  break;
337 
338  case KICAD_LOGO:
339  m_Data += " (pts";
340  // Internal units = micron, file unit = mm
341  jj = 0;
342  for( ii = 0; ii < aPolygon.PointCount(); ii++ )
343  {
344  currpoint = aPolygon.CPoint( ii );
345  sprintf( strbuf, " (xy %.3f %.3f)",
346  ( currpoint.x - offsetX ) / MM2MICRON,
347  ( currpoint.y - offsetY ) / MM2MICRON );
348  m_Data += strbuf;
349 
350  if( jj++ > 4 )
351  {
352  jj = 0;
353  m_Data += "\n ";
354  }
355  }
356  // Close polygon
357  sprintf( strbuf, " (xy %.3f %.3f) )\n",
358  ( startpoint.x - offsetX ) / MM2MICRON,
359  ( startpoint.y - offsetY ) / MM2MICRON );
360  m_Data += strbuf;
361  break;
362 
363  case EESCHEMA_FMT:
364  // The polygon outline thickness is fixed here to 1 mil, the minimal
365  // value in Eeschema (0 means use default thickness for graphics)
366  #define EE_LINE_THICKNESS 1
367  sprintf( strbuf, "P %d 0 0 %d",
368  (int) aPolygon.PointCount() + 1, EE_LINE_THICKNESS );
369  m_Data += strbuf;
370  for( ii = 0; ii < aPolygon.PointCount(); ii++ )
371  {
372  currpoint = aPolygon.CPoint( ii );
373  sprintf( strbuf, " %d %d",
374  currpoint.x - offsetX, currpoint.y - offsetY );
375  m_Data += strbuf;
376  }
377 
378  // Close polygon
379  sprintf( strbuf, " %d %d",
380  startpoint.x - offsetX, startpoint.y - offsetY );
381  m_Data += strbuf;
382 
383  m_Data += " F\n";
384  break;
385  }
386 }
387 
388 
390 {
391  std::vector <potrace_dpoint_t> cornersBuffer;
392 
393  // polyset_areas is a set of polygon to draw
394  SHAPE_POLY_SET polyset_areas;
395 
396  // polyset_holes is the set of holes inside polyset_areas outlines
397  SHAPE_POLY_SET polyset_holes;
398 
399  potrace_dpoint_t( *c )[3];
400 
401  LOCALE_IO toggle; // Temporary switch the locale to standard C to r/w floats
402 
403  // The layer name has meaning only for .kicad_mod files.
404  // For these files the header creates 2 invisible texts: value and ref
405  // (needed but not usefull) on silk screen layer
407 
408  bool main_outline = true;
409 
410  /* draw each as a polygon with no hole.
411  * Bezier curves are approximated by a polyline
412  */
413  potrace_path_t* paths = m_Paths; // the list of paths
414 
415  if(!m_Paths)
416  {
417  m_errors += "No path in black and white image: no outline created\n";
418  }
419 
420  while( paths != NULL )
421  {
422  int cnt = paths->curve.n;
423  int* tag = paths->curve.tag;
424  c = paths->curve.c;
425  potrace_dpoint_t startpoint = c[cnt - 1][2];
426  for( int i = 0; i < cnt; i++ )
427  {
428  switch( tag[i] )
429  {
430  case POTRACE_CORNER:
431  cornersBuffer.push_back( c[i][1] );
432  cornersBuffer.push_back( c[i][2] );
433  startpoint = c[i][2];
434  break;
435 
436  case POTRACE_CURVETO:
437  BezierToPolyline( cornersBuffer, startpoint, c[i][0], c[i][1], c[i][2] );
438  startpoint = c[i][2];
439  break;
440  }
441  }
442 
443  // Store current path
444  if( main_outline )
445  {
446  main_outline = false;
447 
448  // build the current main polygon
449  polyset_areas.NewOutline();
450  for( unsigned int i = 0; i < cornersBuffer.size(); i++ )
451  {
452  polyset_areas.Append( int( cornersBuffer[i].x * m_ScaleX ),
453  int( cornersBuffer[i].y * m_ScaleY ) );
454  }
455  }
456  else
457  {
458  // Add current hole in polyset_holes
459  polyset_holes.NewOutline();
460  for( unsigned int i = 0; i < cornersBuffer.size(); i++ )
461  {
462  polyset_holes.Append( int( cornersBuffer[i].x * m_ScaleX ),
463  int( cornersBuffer[i].y * m_ScaleY ) );
464  }
465  }
466 
467  cornersBuffer.clear();
468 
469  /* at the end of a group of a positive path and its negative children, fill.
470  */
471  if( paths->next == NULL || paths->next->sign == '+' )
472  {
475  polyset_areas.BooleanSubtract( polyset_holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
476 
477  // Ensure there are no self intersecting polygons
478  polyset_areas.NormalizeAreaOutlines();
479 
480  // Convert polygon with holes to a unique polygon
482 
483  // Output current resulting polygon(s)
484  for( int ii = 0; ii < polyset_areas.OutlineCount(); ii++ )
485  {
486  SHAPE_LINE_CHAIN& poly = polyset_areas.Outline( ii );
487  outputOnePolygon( poly, getBoardLayerName( aModLayer ));
488  }
489 
490  polyset_areas.RemoveAllContours();
491  polyset_holes.RemoveAllContours();
492  main_outline = true;
493  }
494  paths = paths->next;
495  }
496 
497  outputDataEnd();
498 }
499 
500 // a helper function to calculate a square value
501 inline double square( double x )
502 {
503  return x*x;
504 }
505 
506 // a helper function to calculate a cube value
507 inline double cube( double x )
508 {
509  return x*x*x;
510 }
511 
512 /* render a Bezier curve. */
513 void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
514  potrace_dpoint_t p1,
515  potrace_dpoint_t p2,
516  potrace_dpoint_t p3,
517  potrace_dpoint_t p4 )
518 {
519  double dd0, dd1, dd, delta, e2, epsilon, t;
520 
521  // p1 = starting point
522 
523  /* we approximate the curve by small line segments. The interval
524  * size, epsilon, is determined on the fly so that the distance
525  * between the true curve and its approximation does not exceed the
526  * desired accuracy delta. */
527 
528  delta = 0.25; /* desired accuracy, in pixels */
529 
530  /* let dd = maximal value of 2nd derivative over curve - this must
531  * occur at an endpoint. */
532  dd0 = square( p1.x - 2 * p2.x + p3.x ) + square( p1.y - 2 * p2.y + p3.y );
533  dd1 = square( p2.x - 2 * p3.x + p4.x ) + square( p2.y - 2 * p3.y + p4.y );
534  dd = 6 * sqrt( std::max( dd0, dd1 ) );
535  e2 = 8 * delta <= dd ? 8 * delta / dd : 1;
536  epsilon = sqrt( e2 ); /* necessary interval size */
537 
538  for( t = epsilon; t<1; t += epsilon )
539  {
540  potrace_dpoint_t intermediate_point;
541  intermediate_point.x = p1.x * cube( 1 - t ) +
542  3* p2.x* square( 1 - t ) * t +
543  3 * p3.x * (1 - t) * square( t ) +
544  p4.x* cube( t );
545 
546  intermediate_point.y = p1.y * cube( 1 - t ) +
547  3* p2.y* square( 1 - t ) * t +
548  3 * p3.y * (1 - t) * square( t ) + p4.y* cube( t );
549 
550  aCornersBuffer.push_back( intermediate_point );
551  }
552 
553  aCornersBuffer.push_back( p4 );
554 }
int OutlineCount() const
Returns the number of outlines in the set
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: common.h:202
std::string m_CmpName
potrace_path_t * m_Paths
int NormalizeAreaOutlines()
Function NormalizeAreaOutlines Convert a self-intersecting polygon to one (or more) non self-intersec...
double square(double x)
static void bm_free(potrace_bitmap_t *bm)
int PointCount() const
Function PointCount()
const VECTOR2I & CPoint(int aIndex) const
Function Point()
void createOutputData(BMP2CMP_MOD_LAYER aModLayer=(BMP2CMP_MOD_LAYER) 0)
Creates the data specified by m_Format.
enum OUTPUT_FMT_ID m_Format
#define NULL
void outputDataHeader(const char *aBrdLayerName)
Function outputDataHeader write to file the header depending on file format.
BMP2CMP_MOD_LAYER
SHAPE_POLY_SET.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Returns the reference to aIndex-th outline in the set
const char * getBoardLayerName(BMP2CMP_MOD_LAYER aChoice)
double cube(double x)
#define EE_LINE_THICKNESS
void Simplify(POLYGON_MODE aFastMode)
Simplifies the polyset (merges overlapping polys, eliminates degeneracy/self-intersections) For aFast...
int NewOutline()
Creates a new empty polygon in the set and returns its index
void Fracture(POLYGON_MODE aFastMode)
Converts a set of polygons with holes to a singe outline with "slits"/"fractures" connecting the oute...
std::string m_errors
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)
std::string & m_Data
void outputDataEnd()
Function outputDataEnd write to file the last strings depending on file format.
Board layer functions and definitions.
void outputOnePolygon(SHAPE_LINE_CHAIN &aPolygon, const char *aBrdLayerName)
Function outputOnePolygon write one polygon to output file.
SHAPE_LINE_CHAIN.
void RemoveAllContours()
Removes all outlines & holes (clears) the polygon set.
The common library.
OUTPUT_FMT_ID
void BooleanSubtract(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Performs boolean polyset difference 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)
Appends a vertex at the end of the given outline/hole (default: the last outline)