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 (C) 1992-2023 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 <layer_ids.h>
37
38#include <locale_io.h>
39#include <potracelib.h>
40
41#include <fmt/format.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 != nullptr )
50 {
51 free( bm->map );
52 }
53
54 free( bm );
55}
56
57
58static void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
59 potrace_dpoint_t p1,
60 potrace_dpoint_t p2,
61 potrace_dpoint_t p3,
62 potrace_dpoint_t p4 );
63
65 m_Data( aData )
66{
68 m_PixmapWidth = 0;
70 m_ScaleX = 1.0;
71 m_ScaleY = 1.0;
72 m_Paths = nullptr;
73 m_CmpName = "LOGO";
74}
75
76
77int BITMAPCONV_INFO::ConvertBitmap( potrace_bitmap_t* aPotrace_bitmap, OUTPUT_FMT_ID aFormat,
78 int aDpi_X, int aDpi_Y, 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 m_errors += fmt::format( "Error allocating parameters: {}\n", strerror( errno ) );
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 m_errors += fmt::format( "Error tracing bitmap: {}\n", strerror( errno ) );
110 return 1;
111 }
112
113 m_PixmapWidth = aPotrace_bitmap->w;
114 m_PixmapHeight = aPotrace_bitmap->h; // the bitmap size in pixels
115 m_Paths = st->plist;
116
117 switch( aFormat )
118 {
119 case KICAD_WKS_LOGO:
121 m_ScaleX = PL_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to micron
122 m_ScaleY = PL_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom
124 break;
125
126 case POSTSCRIPT_FMT:
128 m_ScaleX = 1.0; // the conversion scale
130
131 // output vector data, e.g. as a rudimentary EPS file (mainly for tests)
133 break;
134
135 case EESCHEMA_FMT:
137 m_ScaleX = SCH_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to eeschema iu
138 m_ScaleY = -SCH_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is bottom to Top for components in libs
140 break;
141
142 case PCBNEW_KICAD_MOD:
144 m_ScaleX = PCB_IU_PER_MM * 25.4 / aDpi_X; // the conversion scale from PPI to IU
145 m_ScaleY = PCB_IU_PER_MM * 25.4 / aDpi_Y; // Y axis is top to bottom in Footprint Editor
146 createOutputData( aModLayer );
147 break;
148
149 default:
150 break;
151 }
152
153
154 bm_free( aPotrace_bitmap );
155 potrace_state_free( st );
156 potrace_param_free( param );
157
158 return 0;
159}
160
161
163{
164 const char* layerName = "F.SilkS";
165
166 switch( aChoice )
167 {
169 layerName = "F.Mask";
170 break;
171
172 case MOD_LYR_FAB:
173 layerName = "F.Fab";
174 break;
175
176 case MOD_LYR_DRAWINGS:
177 layerName = "Dwgs.User";
178 break;
179
180 case MOD_LYR_COMMENTS:
181 layerName = "Cmts.User";
182 break;
183
184 case MOD_LYR_ECO1:
185 layerName = "Eco1.User";
186 break;
187
188 case MOD_LYR_ECO2:
189 layerName = "Eco2.User";
190 break;
191
192 case MOD_LYR_FSILKS:
193 break;
194 }
195
196 return layerName;
197}
198
199
200void BITMAPCONV_INFO::outputDataHeader( const char * aBrdLayerName )
201{
202 double Ypos = ( m_PixmapHeight / 2 * m_ScaleY ); // fields Y position in mm
203 double fieldSize; // fields text size in mm
204 std::string strbuf;
205
206 switch( m_Format )
207 {
208 case POSTSCRIPT_FMT:
209 /* output vector data, e.g. as a rudimentary EPS file */
210 m_Data += "%!PS-Adobe-3.0 EPSF-3.0\n";
211 strbuf = fmt::format( "%%BoundingBox: 0 0 {} {}\n", m_PixmapWidth, m_PixmapHeight );
212 m_Data += strbuf;
213 m_Data += "gsave\n";
214 break;
215
216 case PCBNEW_KICAD_MOD:
217 // fields text size = 1.5 mm
218 // fields text thickness = 1.5 / 5 = 0.3mm
219 strbuf = fmt::format( "(footprint \"{}\" (version 20221018) (generator \"bitmap2component\") (generator_version \"{}\")\n"
220 " (layer \"F.Cu\")\n",
221 m_CmpName.c_str(), GetMajorMinorVersion().ToStdString() );
222
223 m_Data += strbuf;
224 strbuf = fmt::format(
225 " (attr board_only exclude_from_pos_files exclude_from_bom)\n" );
226 m_Data += strbuf;
227 strbuf = fmt::format(
228 " (fp_text reference \"G***\" (at 0 0) (layer \"{}\")\n"
229 " (effects (font (size 1.5 1.5) (thickness 0.3)))\n"
230 " (uuid {})\n )\n",
231 aBrdLayerName, KIID().AsString().ToStdString().c_str() );
232
233 m_Data += strbuf;
234 strbuf = fmt::format(
235 " (fp_text value \"{}\" (at 0.75 0) (layer \"{}\") hide\n"
236 " (effects (font (size 1.5 1.5) (thickness 0.3)))\n"
237 " (uuid {})\n )\n",
238 m_CmpName.c_str(), aBrdLayerName, KIID().AsString().ToStdString().c_str() );
239
240 m_Data += strbuf;
241 break;
242
243 case KICAD_WKS_LOGO:
244 m_Data += fmt::format("(kicad_wks (version 20220228) (generator \"bitmap2component\") (generator_version \"{}\")\n", GetMajorMinorVersion().ToStdString() );
245 m_Data += " (setup (textsize 1.5 1.5)(linewidth 0.15)(textlinewidth 0.15)\n";
246 m_Data += " (left_margin 10)(right_margin 10)(top_margin 10)(bottom_margin 10))\n";
247 m_Data += " (polygon (name \"\") (pos 0 0) (linewidth 0.01)\n";
248 break;
249
250 case EESCHEMA_FMT:
251 fieldSize = 1.27; // fields text size in mm (= 50 mils)
252 Ypos /= SCH_IU_PER_MM;
253 Ypos += fieldSize / 2;
254 // snprintf( strbuf, sizeof(strbuf), "# pixmap size w = %d, h = %d\n#\n", m_PixmapWidth, m_PixmapHeight );
255 strbuf = fmt::format(
256 "(kicad_symbol_lib (version 20220914) (generator \"bitmap2component\") (generator_version \"{}\")\n"
257 " (symbol \"{}\" (pin_names (offset 1.016)) (in_bom yes) (on_board yes)\n",
258 GetMajorMinorVersion().ToStdString(), m_CmpName.c_str() );
259 m_Data += strbuf;
260
261 strbuf = fmt::format(
262 " (property \"Reference\" \"#G\" (at 0 {:g} 0)\n"
263 " (effects (font (size {:g} {:g})) hide)\n )\n",
264 -Ypos, fieldSize, fieldSize );
265 m_Data += strbuf;
266
267 strbuf = fmt::format(
268 " (property \"Value\" \"{}\" (at 0 {:g} 0)\n"
269 " (effects (font (size {:g} {:g})) hide)\n )\n",
270 m_CmpName.c_str(), Ypos, fieldSize, fieldSize );
271 m_Data += strbuf;
272
273 strbuf = fmt::format(
274 " (property \"Footprint\" \"\" (at 0 0 0)\n"
275 " (effects (font (size {:g} {:g})) hide)\n )\n",
276 fieldSize, fieldSize );
277 m_Data += strbuf;
278
279 strbuf = fmt::format(
280 " (property \"Datasheet\" \"\" (at 0 0 0)\n"
281 " (effects (font (size {:g} {:g})) hide)\n )\n",
282 fieldSize, fieldSize );
283 m_Data += strbuf;
284
285 strbuf = fmt::format( " (symbol \"{}_0_0\"\n", m_CmpName.c_str() );
286 m_Data += strbuf;
287 break;
288 }
289}
290
291
293{
294 switch( m_Format )
295 {
296 case POSTSCRIPT_FMT:
297 m_Data += "grestore\n";
298 m_Data += "%%EOF\n";
299 break;
300
301 case PCBNEW_KICAD_MOD:
302 m_Data += ")\n";
303 break;
304
305 case KICAD_WKS_LOGO:
306 m_Data += " )\n)\n";
307 break;
308
309 case EESCHEMA_FMT:
310 m_Data += " )\n"; // end symbol_0_0
311 m_Data += " )\n"; // end symbol
312 m_Data += ")\n"; // end lib
313 break;
314 }
315}
316
317
318void BITMAPCONV_INFO::outputOnePolygon( SHAPE_LINE_CHAIN& aPolygon, const char* aBrdLayerName )
319{
320 // write one polygon to output file.
321 // coordinates are expected in target unit.
322 int ii, jj;
323 VECTOR2I currpoint;
324 std::string strbuf;
325
326 int offsetX = (int)( m_PixmapWidth / 2 * m_ScaleX );
327 int offsetY = (int)( m_PixmapHeight / 2 * m_ScaleY );
328
329 const VECTOR2I startpoint = aPolygon.CPoint( 0 );
330
331 switch( m_Format )
332 {
333 case POSTSCRIPT_FMT:
334 offsetY = (int)( m_PixmapHeight * m_ScaleY );
335 strbuf = fmt::format( "newpath\n{} {} moveto\n", startpoint.x, offsetY - startpoint.y );
336 m_Data += strbuf;
337 jj = 0;
338
339 for( ii = 1; ii < aPolygon.PointCount(); ii++ )
340 {
341 currpoint = aPolygon.CPoint( ii );
342 strbuf = fmt::format( " {} {} lineto", currpoint.x, offsetY - currpoint.y );
343 m_Data += strbuf;
344
345 if( jj++ > 6 )
346 {
347 jj = 0;
348 m_Data += "\n";
349 }
350 }
351
352 m_Data += "\nclosepath fill\n";
353 break;
354
355 case PCBNEW_KICAD_MOD:
356 {
357 double width = 0.0; // outline thickness in mm: no thickness
358 m_Data += " (fp_poly\n (pts\n";
359
360 jj = 0;
361
362 for( ii = 0; ii < aPolygon.PointCount(); ii++ )
363 {
364 currpoint = aPolygon.CPoint( ii );
365 strbuf = fmt::format( " (xy {} {})\n",
366 ( currpoint.x - offsetX ) / PCB_IU_PER_MM,
367 ( currpoint.y - offsetY ) / PCB_IU_PER_MM );
368 m_Data += strbuf;
369 }
370 // No need to close polygon
371
372 m_Data += " )\n\n";
373 strbuf = fmt::format(
374 " (stroke (width {:f}) (type solid)) (fill solid) (layer \"{}\") (uuid {}))\n",
375 width, aBrdLayerName, KIID().AsString().ToStdString().c_str() );
376
377 m_Data += strbuf;
378 }
379 break;
380
381 case KICAD_WKS_LOGO:
382 m_Data += " (pts";
383
384 // Internal units = micron, file unit = mm
385 jj = 1;
386
387 for( ii = 0; ii < aPolygon.PointCount(); ii++ )
388 {
389 currpoint = aPolygon.CPoint( ii );
390 strbuf = fmt::format( " (xy {:.3f} {:.3f})",
391 ( currpoint.x - offsetX ) / PL_IU_PER_MM,
392 ( currpoint.y - offsetY ) / PL_IU_PER_MM );
393 m_Data += strbuf;
394
395 if( jj++ > 4 )
396 {
397 jj = 0;
398 m_Data += "\n ";
399 }
400 }
401
402 // Close polygon
403 strbuf = fmt::format( " (xy {:.3f} {:.3f}) )\n",
404 ( startpoint.x - offsetX ) / PL_IU_PER_MM,
405 ( startpoint.y - offsetY ) / PL_IU_PER_MM );
406 m_Data += strbuf;
407 break;
408
409 case EESCHEMA_FMT:
410 // The polygon outline thickness is fixed here to 0.01 ( 0.0 is the default thickness)
411#define SCH_LINE_THICKNESS_MM 0.01
412 //snprintf( strbuf, sizeof(strbuf), "P %d 0 0 %d", (int) aPolygon.PointCount() + 1, EE_LINE_THICKNESS );
413 m_Data += " (polyline\n (pts\n";
414
415 for( ii = 0; ii < aPolygon.PointCount(); ii++ )
416 {
417 currpoint = aPolygon.CPoint( ii );
418 strbuf = fmt::format( " (xy {:f} {:f})\n",
419 ( currpoint.x - offsetX ) / SCH_IU_PER_MM,
420 ( currpoint.y - offsetY ) / SCH_IU_PER_MM );
421 m_Data += strbuf;
422 }
423
424 // Close polygon
425 strbuf = fmt::format( " (xy {:f} {:f})\n",
426 ( startpoint.x - offsetX ) / SCH_IU_PER_MM,
427 ( startpoint.y - offsetY ) / SCH_IU_PER_MM );
428 m_Data += strbuf;
429 m_Data += " )\n"; // end pts
430
431 strbuf = fmt::format(
432 " (stroke (width {:g}) (type default))\n (fill (type outline))\n",
434
435 m_Data += strbuf;
436 m_Data += " )\n"; // end polyline
437 break;
438 }
439}
440
441
443{
444 std::vector <potrace_dpoint_t> cornersBuffer;
445
446 // polyset_areas is a set of polygon to draw
447 SHAPE_POLY_SET polyset_areas;
448
449 // polyset_holes is the set of holes inside polyset_areas outlines
450 SHAPE_POLY_SET polyset_holes;
451
452 potrace_dpoint_t( *c )[3];
453
454 LOCALE_IO toggle; // Temporary switch the locale to standard C to r/w floats
455
456 // The layer name has meaning only for .kicad_mod files.
457 // For these files the header creates 2 invisible texts: value and ref
458 // (needed but not useful) on silk screen layer
460
461 bool main_outline = true;
462
463 /* draw each as a polygon with no hole.
464 * Bezier curves are approximated by a polyline
465 */
466 potrace_path_t* paths = m_Paths; // the list of paths
467
468 if(!m_Paths)
469 {
470 m_errors += "No shape in black and white image to convert: no outline created\n";
471 }
472
473 while( paths != nullptr )
474 {
475 int cnt = paths->curve.n;
476 int* tag = paths->curve.tag;
477 c = paths->curve.c;
478 potrace_dpoint_t startpoint = c[cnt - 1][2];
479
480 for( int i = 0; i < cnt; i++ )
481 {
482 switch( tag[i] )
483 {
484 case POTRACE_CORNER:
485 cornersBuffer.push_back( c[i][1] );
486 cornersBuffer.push_back( c[i][2] );
487 startpoint = c[i][2];
488 break;
489
490 case POTRACE_CURVETO:
491 BezierToPolyline( cornersBuffer, startpoint, c[i][0], c[i][1], c[i][2] );
492 startpoint = c[i][2];
493 break;
494 }
495 }
496
497 // Store current path
498 if( main_outline )
499 {
500 main_outline = false;
501
502 // build the current main polygon
503 polyset_areas.NewOutline();
504 for( unsigned int i = 0; i < cornersBuffer.size(); i++ )
505 {
506 polyset_areas.Append( int( cornersBuffer[i].x * m_ScaleX ),
507 int( cornersBuffer[i].y * m_ScaleY ) );
508 }
509 }
510 else
511 {
512 // Add current hole in polyset_holes
513 polyset_holes.NewOutline();
514
515 for( unsigned int i = 0; i < cornersBuffer.size(); i++ )
516 {
517 polyset_holes.Append( int( cornersBuffer[i].x * m_ScaleX ),
518 int( cornersBuffer[i].y * m_ScaleY ) );
519 }
520 }
521
522 cornersBuffer.clear();
523
524 // at the end of a group of a positive path and its negative children, fill.
525 if( paths->next == nullptr || paths->next->sign == '+' )
526 {
529 polyset_areas.BooleanSubtract( polyset_holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
530
531 // Ensure there are no self intersecting polygons
532 if( polyset_areas.NormalizeAreaOutlines() )
533 {
534 // Convert polygon with holes to a unique polygon
536
537 // Output current resulting polygon(s)
538 for( int ii = 0; ii < polyset_areas.OutlineCount(); ii++ )
539 {
540 SHAPE_LINE_CHAIN& poly = polyset_areas.Outline( ii );
541 outputOnePolygon( poly, getBoardLayerName( aModLayer ));
542 }
543
544 polyset_areas.RemoveAllContours();
545 polyset_holes.RemoveAllContours();
546 main_outline = true;
547 }
548 }
549
550 paths = paths->next;
551 }
552
554}
555
556// a helper function to calculate a square value
557inline double square( double x )
558{
559 return x * x;
560}
561
562
563// a helper function to calculate a cube value
564inline double cube( double x )
565{
566 return x * x * x;
567}
568
569
570/* render a Bezier curve. */
571void BezierToPolyline( std::vector <potrace_dpoint_t>& aCornersBuffer,
572 potrace_dpoint_t p1,
573 potrace_dpoint_t p2,
574 potrace_dpoint_t p3,
575 potrace_dpoint_t p4 )
576{
577 double dd0, dd1, dd, delta, e2, epsilon, t;
578
579 // p1 = starting point
580
581 /* we approximate the curve by small line segments. The interval
582 * size, epsilon, is determined on the fly so that the distance
583 * between the true curve and its approximation does not exceed the
584 * desired accuracy delta. */
585
586 delta = 0.25; /* desired accuracy, in pixels */
587
588 /* let dd = maximal value of 2nd derivative over curve - this must
589 * occur at an endpoint. */
590 dd0 = square( p1.x - 2 * p2.x + p3.x ) + square( p1.y - 2 * p2.y + p3.y );
591 dd1 = square( p2.x - 2 * p3.x + p4.x ) + square( p2.y - 2 * p3.y + p4.y );
592 dd = 6 * sqrt( std::max( dd0, dd1 ) );
593 e2 = 8 * delta <= dd ? 8 * delta / dd : 1;
594 epsilon = sqrt( e2 ); /* necessary interval size */
595
596 for( t = epsilon; t<1; t += epsilon )
597 {
598 potrace_dpoint_t intermediate_point;
599 intermediate_point.x = p1.x * cube( 1 - t ) +
600 3* p2.x* square( 1 - t ) * t +
601 3 * p3.x * (1 - t) * square( t ) +
602 p4.x* cube( t );
603
604 intermediate_point.y = p1.y * cube( 1 - t ) +
605 3* p2.y* square( 1 - t ) * t +
606 3 * p3.y * (1 - t) * square( t ) + p4.y* cube( t );
607
608 aCornersBuffer.push_back( intermediate_point );
609 }
610
611 aCornersBuffer.push_back( p4 );
612}
constexpr double SCH_IU_PER_MM
Definition: base_units.h:72
constexpr double PCB_IU_PER_MM
Definition: base_units.h:70
constexpr double PL_IU_PER_MM
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
BMP2CMP_MOD_LAYER
@ MOD_LYR_ECO1
@ MOD_LYR_FSILKS
@ MOD_LYR_ECO2
@ MOD_LYR_DRAWINGS
@ MOD_LYR_FSOLDERMASK
@ MOD_LYR_COMMENTS
@ MOD_LYR_FAB
OUTPUT_FMT_ID
@ KICAD_WKS_LOGO
@ PCBNEW_KICAD_MOD
@ POSTSCRIPT_FMT
@ EESCHEMA_FMT
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
BITMAPCONV_INFO(std::string &aData)
potrace_path_t * m_Paths
std::string m_CmpName
void outputDataHeader(const char *aBrdLayerName)
Function outputDataHeader write to file the header depending on file format.
void outputOnePolygon(SHAPE_LINE_CHAIN &aPolygon, const char *aBrdLayerName)
Function outputOnePolygon write one polygon to output file.
enum OUTPUT_FMT_ID m_Format
void createOutputData(BMP2CMP_MOD_LAYER aModLayer=(BMP2CMP_MOD_LAYER) 0)
Creates the data specified by m_Format.
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.
std::string m_errors
const char * getBoardLayerName(BMP2CMP_MOD_LAYER aChoice)
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
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 BooleanSubtract(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Perform boolean polyset difference For aFastMode meaning, see function booleanOp.
void Fracture(POLYGON_MODE aFastMode)
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(POLYGON_MODE aFastMode)
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections) For aFastMo...
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.
const double epsilon
constexpr int delta