KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 The 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#include <kiid.h>
34
35#include <build_version.h>
36#include <locale_io.h>
37#include <macros.h>
38#include <potracelib.h>
39#include <reporter.h>
40#include <fmt/format.h>
41#include <wx/translation.h>
42
43#include "bitmap2component.h"
44
45
46/* free a potrace bitmap */
47static void bm_free( potrace_bitmap_t* bm )
48{
49 if( bm )
50 free( bm->map );
51
52 free( bm );
53}
54
55
56static void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
57 potrace_dpoint_t p1, potrace_dpoint_t p2,
58 potrace_dpoint_t p3, potrace_dpoint_t p4 );
59
60
61BITMAPCONV_INFO::BITMAPCONV_INFO( std::string& aData, REPORTER& aReporter ):
62 m_Data( aData ),
63 m_reporter( aReporter )
64{
66 m_PixmapWidth = 0;
68 m_ScaleX = 1.0;
69 m_ScaleY = 1.0;
70 m_Paths = nullptr;
71 m_CmpName = "LOGO";
72}
73
74
75int BITMAPCONV_INFO::ConvertBitmap( potrace_bitmap_t* aPotrace_bitmap, OUTPUT_FMT_ID aFormat,
76 int aDpi_X, int aDpi_Y, const wxString& aLayer )
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 m_reporter.Report( fmt::format( "Error allocating parameters: {}\n", strerror( errno ) ),
88 return 1;
89 }
90
91 // For parameters: see http://potrace.sourceforge.net/potracelib.pdf
92 param->turdsize = 0; // area (in pixels) of largest path to be ignored.
93 // Potrace default is 2
94 param->opttolerance = 0.2; // curve optimization tolerance. Potrace default is 0.2
95
96 /* convert the bitmap to curves */
97 st = potrace_trace( param, aPotrace_bitmap );
98
99 if( !st || st->status != POTRACE_STATUS_OK )
100 {
101 if( st )
102 potrace_state_free( st );
103
104 potrace_param_free( param );
105
106 m_reporter.Report( fmt::format( "Error tracing bitmap: {}\n", strerror( errno ) ),
108 return 1;
109 }
110
111 m_PixmapWidth = aPotrace_bitmap->w;
112 m_PixmapHeight = aPotrace_bitmap->h; // the bitmap size in pixels
113 m_Paths = st->plist;
114 m_Format = aFormat;
115
116 switch( aFormat )
117 {
119 m_ScaleX = PL_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to micron
120 m_ScaleY = PL_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom
122 break;
123
124 case POSTSCRIPT_FMT:
125 m_ScaleX = 1.0; // the conversion scale
128 break;
129
130 case SYMBOL_FMT:
131 case SYMBOL_PASTE_FMT:
132 m_ScaleX = SCH_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to eeschema iu
133 m_ScaleY = -SCH_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is bottom to Top for components in libs
135 break;
136
137 case FOOTPRINT_FMT:
138 m_ScaleX = PCB_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to IU
139 m_ScaleY = PCB_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom in Footprint Editor
140 createOutputData( aLayer );
141 break;
142 }
143
144 bm_free( aPotrace_bitmap );
145 potrace_state_free( st );
146 potrace_param_free( param );
147
148 return 0;
149}
150
151
152void BITMAPCONV_INFO::outputDataHeader( const wxString& aBrdLayerName )
153{
154 double Ypos = ( m_PixmapHeight / 2.0 * m_ScaleY ); // fields Y position in mm
155 double fieldSize; // fields text size in mm
156
157 switch( m_Format )
158 {
159 case POSTSCRIPT_FMT:
160 m_Data += "%!PS-Adobe-3.0 EPSF-3.0\n";
161 m_Data += fmt::format( "%%BoundingBox: 0 0 {} {}\n", m_PixmapWidth, m_PixmapHeight );
162 m_Data += "gsave\n";
163 break;
164
165 case FOOTPRINT_FMT:
166 // fields text size = 1.5 mm
167 // fields text thickness = 1.5 / 5 = 0.3mm
168 m_Data += fmt::format( "(footprint \"{}\" (version 20221018) (generator \"bitmap2component\") (generator_version \"{}\")\n"
169 " (layer \"F.Cu\")\n",
170 m_CmpName.c_str(),
171 GetMajorMinorVersion().ToStdString() );
172
173 m_Data += fmt::format( " (attr board_only exclude_from_pos_files exclude_from_bom)\n" );
174 m_Data += fmt::format( " (fp_text reference \"G***\" (at 0 0) (layer \"{}\")\n"
175 " (effects (font (size 1.5 1.5) (thickness 0.3)))\n"
176 " (uuid {})\n )\n",
177 aBrdLayerName.ToStdString().c_str(),
178 KIID().AsString().ToStdString().c_str() );
179
180 m_Data += fmt::format( " (fp_text value \"{}\" (at 0.75 0) (layer \"{}\") hide\n"
181 " (effects (font (size 1.5 1.5) (thickness 0.3)))\n"
182 " (uuid {})\n )\n",
183 m_CmpName.c_str(),
184 aBrdLayerName.ToStdString().c_str(),
185 KIID().AsString().ToStdString().c_str() );
186 break;
187
189 m_Data += fmt::format( "(kicad_wks (version 20220228) (generator \"bitmap2component\") (generator_version \"{}\")\n",
190 GetMajorMinorVersion().ToStdString() );
191 m_Data += " (setup (textsize 1.5 1.5)(linewidth 0.15)(textlinewidth 0.15)\n";
192 m_Data += " (left_margin 10)(right_margin 10)(top_margin 10)(bottom_margin 10))\n";
193 m_Data += " (polygon (name \"\") (pos 0 0) (linewidth 0.01)\n";
194 break;
195
196 case SYMBOL_FMT:
197 m_Data += fmt::format( "(kicad_symbol_lib (version 20220914) (generator \"bitmap2component\") (generator_version \"{}\")\n",
198 GetMajorMinorVersion().ToStdString() );
200
201 case SYMBOL_PASTE_FMT:
202 fieldSize = 1.27; // fields text size in mm (= 50 mils)
203 Ypos /= SCH_IU_PER_MM;
204 Ypos += fieldSize / 2;
205 m_Data += fmt::format( " (symbol \"{}\" (pin_names (offset 1.016)) (in_bom yes) (on_board yes)\n",
206 m_CmpName.c_str() );
207
208 m_Data += fmt::format( " (property \"Reference\" \"#G\" (at 0 {:g} 0)\n"
209 " (effects (font (size {:g} {:g})) hide)\n )\n",
210 -Ypos,
211 fieldSize,
212 fieldSize );
213
214 m_Data += fmt::format( " (property \"Value\" \"{}\" (at 0 {:g} 0)\n"
215 " (effects (font (size {:g} {:g})) hide)\n )\n",
216 m_CmpName.c_str(),
217 Ypos,
218 fieldSize,
219 fieldSize );
220
221 m_Data += fmt::format( " (property \"Footprint\" \"\" (at 0 0 0)\n"
222 " (effects (font (size {:g} {:g})) hide)\n )\n",
223 fieldSize,
224 fieldSize );
225
226 m_Data += fmt::format( " (property \"Datasheet\" \"\" (at 0 0 0)\n"
227 " (effects (font (size {:g} {:g})) hide)\n )\n",
228 fieldSize,
229 fieldSize );
230
231 m_Data += fmt::format( " (symbol \"{}_0_0\"\n", m_CmpName.c_str() );
232 break;
233 }
234}
235
236
238{
239 switch( m_Format )
240 {
241 case POSTSCRIPT_FMT:
242 m_Data += "grestore\n";
243 m_Data += "%%EOF\n";
244 break;
245
246 case FOOTPRINT_FMT:
247 m_Data += ")\n";
248 break;
249
251 m_Data += " )\n)\n";
252 break;
253
254 case SYMBOL_PASTE_FMT:
255 m_Data += " )\n"; // end symbol_0_0
256 m_Data += " )\n"; // end symbol
257 break;
258
259 case SYMBOL_FMT:
260 m_Data += " )\n"; // end symbol_0_0
261 m_Data += " )\n"; // end symbol
262 m_Data += ")\n"; // end lib
263 break;
264 }
265}
266
267
268void BITMAPCONV_INFO::outputOnePolygon( SHAPE_LINE_CHAIN& aPolygon, const wxString& aBrdLayerName )
269{
270 // write one polygon to output file.
271 // coordinates are expected in target unit.
272 int ii, jj;
273 VECTOR2I currpoint;
274 int offsetX = KiROUND( m_PixmapWidth / 2.0 * m_ScaleX );
275 int offsetY = KiROUND( m_PixmapHeight / 2.0 * m_ScaleY );
276
277 const VECTOR2I startpoint = aPolygon.CPoint( 0 );
278
279 switch( m_Format )
280 {
281 case POSTSCRIPT_FMT:
282 offsetY = (int)( m_PixmapHeight * m_ScaleY );
283 m_Data += fmt::format( "newpath\n{} {} moveto\n", startpoint.x, offsetY - startpoint.y );
284 jj = 0;
285
286 for( ii = 1; ii < aPolygon.PointCount(); ii++ )
287 {
288 currpoint = aPolygon.CPoint( ii );
289 m_Data += fmt::format( " {} {} lineto", currpoint.x, offsetY - currpoint.y );
290
291 if( jj++ > 6 )
292 {
293 jj = 0;
294 m_Data += "\n";
295 }
296 }
297
298 m_Data += "\nclosepath fill\n";
299 break;
300
301 case FOOTPRINT_FMT:
302 m_Data += " (fp_poly\n (pts\n";
303
304 jj = 0;
305
306 for( ii = 0; ii < aPolygon.PointCount(); ii++ )
307 {
308 currpoint = aPolygon.CPoint( ii );
309 m_Data += fmt::format( " (xy {} {})\n",
310 ( currpoint.x - offsetX ) / PCB_IU_PER_MM,
311 ( currpoint.y - offsetY ) / PCB_IU_PER_MM );
312 }
313 // No need to close polygon
314
315 m_Data += " )\n\n";
316 m_Data += fmt::format( " (stroke (width {:f}) (type solid)) (fill solid) (layer \"{}\") (uuid {}))\n",
317 0.0,
318 aBrdLayerName.ToStdString().c_str(),
319 KIID().AsString().ToStdString().c_str() );
320 break;
321
323 m_Data += " (pts";
324
325 // Internal units = micron, file unit = mm
326 jj = 1;
327
328 for( ii = 0; ii < aPolygon.PointCount(); ii++ )
329 {
330 currpoint = aPolygon.CPoint( ii );
331 m_Data += fmt::format( " (xy {:.3f} {:.3f})",
332 ( currpoint.x - offsetX ) / PL_IU_PER_MM,
333 ( currpoint.y - offsetY ) / PL_IU_PER_MM );
334
335 if( jj++ > 4 )
336 {
337 jj = 0;
338 m_Data += "\n ";
339 }
340 }
341
342 // Close polygon
343 m_Data += fmt::format( " (xy {:.3f} {:.3f}) )\n",
344 ( startpoint.x - offsetX ) / PL_IU_PER_MM,
345 ( startpoint.y - offsetY ) / PL_IU_PER_MM );
346 break;
347
348 case SYMBOL_FMT:
349 case SYMBOL_PASTE_FMT:
350 // The polygon outline thickness is fixed here to 0.01 ( 0.0 is the default thickness)
351#define SCH_LINE_THICKNESS_MM 0.01
352 //snprintf( strbuf, sizeof(strbuf), "P %d 0 0 %d", (int) aPolygon.PointCount() + 1, EE_LINE_THICKNESS );
353 m_Data += " (polyline\n (pts\n";
354
355 for( ii = 0; ii < aPolygon.PointCount(); ii++ )
356 {
357 currpoint = aPolygon.CPoint( ii );
358 m_Data += fmt::format( " (xy {:f} {:f})\n",
359 ( currpoint.x - offsetX ) / SCH_IU_PER_MM,
360 ( currpoint.y - offsetY ) / SCH_IU_PER_MM );
361 }
362
363 // Close polygon
364 m_Data += fmt::format( " (xy {:f} {:f})\n",
365 ( startpoint.x - offsetX ) / SCH_IU_PER_MM,
366 ( startpoint.y - offsetY ) / SCH_IU_PER_MM );
367 m_Data += " )\n"; // end pts
368
369 m_Data += fmt::format( " (stroke (width {:g}) (type default))\n"
370 " (fill (type outline))\n",
372 m_Data += " )\n"; // end polyline
373 break;
374 }
375}
376
377
378void BITMAPCONV_INFO::createOutputData( const wxString& aLayer )
379{
380 std::vector <potrace_dpoint_t> cornersBuffer;
381
382 // polyset_areas is a set of polygon to draw
383 SHAPE_POLY_SET polyset_areas;
384
385 // polyset_holes is the set of holes inside polyset_areas outlines
386 SHAPE_POLY_SET polyset_holes;
387
388 potrace_dpoint_t( *c )[3];
389
390 LOCALE_IO toggle; // Temporary switch the locale to standard C to r/w floats
391
392 // The layer name has meaning only for .kicad_mod files.
393 // For these files the header creates 2 invisible texts: value and ref
394 // (needed but not useful) on silk screen layer
395 outputDataHeader( "F.SilkS" );
396
397 bool main_outline = true;
398
399 /* draw each as a polygon with no hole.
400 * Bezier curves are approximated by a polyline
401 */
402 potrace_path_t* paths = m_Paths; // the list of paths
403
404 if( !m_Paths )
405 {
406 m_reporter.Report( _( "No shape in black and white image to convert: no outline created." ),
408 }
409
410 while( paths != nullptr )
411 {
412 int cnt = paths->curve.n;
413 int* tag = paths->curve.tag;
414 c = paths->curve.c;
415 potrace_dpoint_t startpoint = c[cnt - 1][2];
416
417 for( int i = 0; i < cnt; i++ )
418 {
419 switch( tag[i] )
420 {
421 case POTRACE_CORNER:
422 cornersBuffer.push_back( c[i][1] );
423 cornersBuffer.push_back( c[i][2] );
424 startpoint = c[i][2];
425 break;
426
427 case POTRACE_CURVETO:
428 BezierToPolyline( cornersBuffer, startpoint, c[i][0], c[i][1], c[i][2] );
429 startpoint = c[i][2];
430 break;
431 }
432 }
433
434 // Store current path
435 if( main_outline )
436 {
437 main_outline = false;
438
439 // build the current main polygon
440 polyset_areas.NewOutline();
441
442 for( const potrace_dpoint_s& pt : cornersBuffer )
443 {
444 polyset_areas.Append( int( pt.x * m_ScaleX ),
445 int( pt.y * m_ScaleY ) );
446 }
447 }
448 else
449 {
450 // Add current hole in polyset_holes
451 polyset_holes.NewOutline();
452
453 for( const potrace_dpoint_s& pt : cornersBuffer )
454 {
455 polyset_holes.Append( int( pt.x * m_ScaleX ),
456 int( pt.y * m_ScaleY ) );
457 }
458 }
459
460 cornersBuffer.clear();
461
462 // at the end of a group of a positive path and its negative children, fill.
463 if( paths->next == nullptr || paths->next->sign == '+' )
464 {
465 polyset_areas.Simplify();
466 polyset_holes.Simplify();
467 polyset_areas.BooleanSubtract( polyset_holes );
468
469 // Ensure there are no self intersecting polygons
470 if( polyset_areas.NormalizeAreaOutlines() )
471 {
472 // Convert polygon with holes to a unique polygon
473 polyset_areas.Fracture();
474
475 // Output current resulting polygon(s)
476 for( int ii = 0; ii < polyset_areas.OutlineCount(); ii++ )
477 {
478 SHAPE_LINE_CHAIN& poly = polyset_areas.Outline( ii );
479 outputOnePolygon( poly, aLayer );
480 }
481
482 polyset_areas.RemoveAllContours();
483 polyset_holes.RemoveAllContours();
484 main_outline = true;
485 }
486 }
487
488 paths = paths->next;
489 }
490
492}
493
494// a helper function to calculate a square value
495inline double square( double x )
496{
497 return x * x;
498}
499
500
501// a helper function to calculate a cube value
502inline double cube( double x )
503{
504 return x * x * x;
505}
506
507
508/* render a Bezier curve. */
509void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
510 potrace_dpoint_t p1,
511 potrace_dpoint_t p2,
512 potrace_dpoint_t p3,
513 potrace_dpoint_t p4 )
514{
515 double dd0, dd1, dd, delta, e2, epsilon, t;
516
517 // p1 = starting point
518
519 /* we approximate the curve by small line segments. The interval
520 * size, epsilon, is determined on the fly so that the distance
521 * between the true curve and its approximation does not exceed the
522 * desired accuracy delta. */
523
524 delta = 0.25; /* desired accuracy, in pixels */
525
526 /* let dd = maximal value of 2nd derivative over curve - this must
527 * occur at an endpoint. */
528 dd0 = square( p1.x - 2 * p2.x + p3.x ) + square( p1.y - 2 * p2.y + p3.y );
529 dd1 = square( p2.x - 2 * p3.x + p4.x ) + square( p2.y - 2 * p3.y + p4.y );
530 dd = 6 * sqrt( std::max( dd0, dd1 ) );
531 e2 = 8 * delta <= dd ? 8 * delta / dd : 1;
532 epsilon = sqrt( e2 ); /* necessary interval size */
533
534 for( t = epsilon; t<1; t += epsilon )
535 {
536 potrace_dpoint_t intermediate_point;
537 intermediate_point.x = p1.x * cube( 1 - t ) +
538 3* p2.x* square( 1 - t ) * t +
539 3 * p3.x * (1 - t) * square( t ) +
540 p4.x* cube( t );
541
542 intermediate_point.y = p1.y * cube( 1 - t ) +
543 3* p2.y* square( 1 - t ) * t +
544 3 * p3.y * (1 - t) * square( t ) + p4.y* cube( t );
545
546 aCornersBuffer.push_back( intermediate_point );
547 }
548
549 aCornersBuffer.push_back( p4 );
550}
constexpr double SCH_IU_PER_MM
Schematic internal units 1=100nm.
Definition: base_units.h:72
constexpr double PCB_IU_PER_MM
Pcbnew IU is 1 nanometer.
Definition: base_units.h:70
constexpr double PL_IU_PER_MM
Internal units in micron (should be enough).
Definition: base_units.h:71
double square(double x)
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)
double cube(double x)
static void bm_free(potrace_bitmap_t *bm)
#define SCH_LINE_THICKNESS_MM
OUTPUT_FMT_ID
@ DRAWING_SHEET_FMT
@ SYMBOL_FMT
@ SYMBOL_PASTE_FMT
@ POSTSCRIPT_FMT
@ FOOTPRINT_FMT
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition: box2.h:990
wxString GetMajorMinorVersion()
Get only the major and minor version in a string major.minor.
void outputDataEnd()
Function outputDataEnd write to file the last strings depending on file format.
std::string & m_Data
REPORTER & m_reporter
BITMAPCONV_INFO(std::string &aData, REPORTER &aReporter)
potrace_path_t * m_Paths
void outputOnePolygon(SHAPE_LINE_CHAIN &aPolygon, const wxString &aBrdLayerName)
Function outputOnePolygon write one polygon to output file.
std::string m_CmpName
void outputDataHeader(const wxString &aBrdLayerName)
Function outputDataHeader write to file the header depending on file format.
enum OUTPUT_FMT_ID m_Format
int ConvertBitmap(potrace_bitmap_t *aPotrace_bitmap, OUTPUT_FMT_ID aFormat, int aDpi_X, int aDpi_Y, const wxString &aLayer)
Run the conversion of the bitmap.
void createOutputData(const wxString &aBrdLayerName=wxT("F.SilkS"))
Creates the data specified by m_Format.
Definition: kiid.h:49
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:49
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:72
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
void Fracture()
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
int NormalizeAreaOutlines()
Convert a self-intersecting polygon to one (or more) non self-intersecting polygon(s).
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)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
int OutlineCount() const
Return the number of outlines in the set.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
#define _(s)
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition: macros.h:83
@ RPT_SEVERITY_ERROR
const double epsilon
constexpr int delta