KiCad PCB EDA Suite
Loading...
Searching...
No Matches
bitmap_tracing.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <bitmap_tracing.h>
21
22#include <algorithm>
23#include <cerrno>
24#include <cmath>
25#include <cstring>
26#include <vector>
27
28#include <bitmap.h>
29#include <math/util.h>
30#include <potracelib.h>
31#include <reporter.h>
32#include <wx/translation.h>
33
34
35static double square( double x )
36{
37 return x * x;
38}
39
40
41static double cube( double x )
42{
43 return x * x * x;
44}
45
46
47/* render a Bezier curve. */
48static void BezierToPolyline( std::vector<potrace_dpoint_t>& aCornersBuffer, potrace_dpoint_t p1, potrace_dpoint_t p2,
49 potrace_dpoint_t p3, potrace_dpoint_t p4 )
50{
51 double dd0, dd1, dd, delta, e2, epsilon, t;
52
53 // p1 = starting point
54
55 /* we approximate the curve by small line segments. The interval
56 * size, epsilon, is determined on the fly so that the distance
57 * between the true curve and its approximation does not exceed the
58 * desired accuracy delta. */
59
60 delta = 0.25; /* desired accuracy, in pixels */
61
62 /* let dd = maximal value of 2nd derivative over curve - this must
63 * occur at an endpoint. */
64 dd0 = square( p1.x - 2 * p2.x + p3.x ) + square( p1.y - 2 * p2.y + p3.y );
65 dd1 = square( p2.x - 2 * p3.x + p4.x ) + square( p2.y - 2 * p3.y + p4.y );
66 dd = 6 * sqrt( std::max( dd0, dd1 ) );
67 e2 = 8 * delta <= dd ? 8 * delta / dd : 1;
68 epsilon = sqrt( e2 ); /* necessary interval size */
69
70 for( t = epsilon; t < 1; t += epsilon )
71 {
72 potrace_dpoint_t intermediate_point;
73 intermediate_point.x = p1.x * cube( 1 - t ) + 3 * p2.x * square( 1 - t ) * t
74 + 3 * p3.x * ( 1 - t ) * square( t ) + p4.x * cube( t );
75
76 intermediate_point.y = p1.y * cube( 1 - t ) + 3 * p2.y * square( 1 - t ) * t
77 + 3 * p3.y * ( 1 - t ) * square( t ) + p4.y * cube( t );
78
79 aCornersBuffer.push_back( intermediate_point );
80 }
81
82 aCornersBuffer.push_back( p4 );
83}
84
85
86wxImage ConvertImageToBilevel( const wxImage& aImage, double aThreshold, bool aInvert )
87{
88 wxImage bilevel = aImage.ConvertToGreyscale();
89
90 if( !bilevel.IsOk() )
91 return bilevel;
92
93 bilevel.UnShare();
94
95 const int pixelCount = bilevel.GetWidth() * bilevel.GetHeight();
96 const unsigned char* source = aImage.GetData();
97 const unsigned char* alpha = bilevel.HasAlpha() ? bilevel.GetAlpha() : nullptr;
98 unsigned char* grey = bilevel.GetData();
99
100 const bool hasMask = aImage.HasMask();
101 const unsigned char maskRed = hasMask ? aImage.GetMaskRed() : 0;
102 const unsigned char maskGreen = hasMask ? aImage.GetMaskGreen() : 0;
103 const unsigned char maskBlue = hasMask ? aImage.GetMaskBlue() : 0;
104
105 const unsigned char threshold = aThreshold * 255;
106 const unsigned char alphaThreshold = 0.7 * threshold;
107
108 // The whole image is walked again on every move of a threshold slider, so this reads and
109 // writes the raw pixels rather than going through the per pixel accessors.
110 for( int ii = 0; ii < pixelCount; ii++ )
111 {
112 unsigned char level = grey[ii * 3];
113
114 // A masked pixel is see-through, so it stays white whatever colour it carries.
115 if( hasMask && source[ii * 3] == maskRed && source[ii * 3 + 1] == maskGreen && source[ii * 3 + 2] == maskBlue )
116 {
117 level = 255;
118 }
119
120 if( aInvert )
121 level = ~level;
122
123 level = ( level < threshold && ( !alpha || alpha[ii] > alphaThreshold ) ) ? 0 : 255;
124
125 grey[ii * 3] = level;
126 grey[ii * 3 + 1] = level;
127 grey[ii * 3 + 2] = level;
128 }
129
130 return bilevel;
131}
132
133
134wxImage MakeBilevelPreview( const wxImage& aImage, double aThreshold, bool aInvert, const wxSize& aRoom )
135{
136 wxImage preview = ConvertImageToBilevel( aImage, aThreshold, aInvert );
137
138 if( !preview.IsOk() )
139 return preview;
140
141 double fit = std::min( (double) aRoom.x / preview.GetWidth(), (double) aRoom.y / preview.GetHeight() );
142
143 if( fit < 1.0 )
144 {
145 preview = preview.Scale( KiROUND( preview.GetWidth() * fit ), KiROUND( preview.GetHeight() * fit ),
146 wxIMAGE_QUALITY_NORMAL );
147 }
148
149 return preview;
150}
151
152
153SHAPE_POLY_SET TraceBilevelImage( const wxImage& aBilevelImage, double aScaleX, double aScaleY, REPORTER& aReporter )
154{
156
157 potrace_bitmap_t* potraceBitmap = bm_new( aBilevelImage.GetWidth(), aBilevelImage.GetHeight() );
158
159 if( !potraceBitmap )
160 {
161 aReporter.Report( _( "Error allocating memory for potrace bitmap" ), RPT_SEVERITY_ERROR );
162 return result;
163 }
164
165 for( int y = 0; y < aBilevelImage.GetHeight(); y++ )
166 {
167 for( int x = 0; x < aBilevelImage.GetWidth(); x++ )
168 {
169 unsigned char pixel = aBilevelImage.GetGreen( x, y );
170 BM_PUT( potraceBitmap, x, y, pixel ? 0 : 1 );
171 }
172 }
173
174 potrace_param_t* param = potrace_param_default();
175
176 if( !param )
177 {
178 bm_free( potraceBitmap );
179 aReporter.Report(
180 wxString::Format( _( "Error allocating parameters: %s" ), wxString::FromUTF8( strerror( errno ) ) ),
182 return result;
183 }
184
185 // For parameters: see http://potrace.sourceforge.net/potracelib.pdf
186 param->turdsize = 0; // area (in pixels) of largest path to be ignored.
187 // Potrace default is 2
188 param->opttolerance = 0.2; // curve optimization tolerance. Potrace default is 0.2
189
190 potrace_state_t* st = potrace_trace( param, potraceBitmap );
191
192 if( !st || st->status != POTRACE_STATUS_OK )
193 {
194 if( st )
195 potrace_state_free( st );
196
197 potrace_param_free( param );
198 bm_free( potraceBitmap );
199
200 aReporter.Report( wxString::Format( _( "Error tracing bitmap: %s" ), wxString::FromUTF8( strerror( errno ) ) ),
202 return result;
203 }
204
205 std::vector<potrace_dpoint_t> cornersBuffer;
206 SHAPE_POLY_SET areas;
207 SHAPE_POLY_SET holes;
208 bool mainOutline = true;
209 int droppedGroups = 0;
210
211 for( potrace_path_t* path = st->plist; path != nullptr; path = path->next )
212 {
213 int cnt = path->curve.n;
214 int* tag = path->curve.tag;
215 potrace_dpoint_t( *c )[3] = path->curve.c;
216 potrace_dpoint_t startpoint = c[cnt - 1][2];
217
218 for( int ii = 0; ii < cnt; ii++ )
219 {
220 switch( tag[ii] )
221 {
222 case POTRACE_CORNER:
223 cornersBuffer.push_back( c[ii][1] );
224 cornersBuffer.push_back( c[ii][2] );
225 startpoint = c[ii][2];
226 break;
227
228 case POTRACE_CURVETO:
229 BezierToPolyline( cornersBuffer, startpoint, c[ii][0], c[ii][1], c[ii][2] );
230 startpoint = c[ii][2];
231 break;
232 }
233 }
234
235 SHAPE_POLY_SET& target = mainOutline ? areas : holes;
236
237 mainOutline = false;
238 target.NewOutline();
239
240 for( const potrace_dpoint_s& pt : cornersBuffer )
241 target.Append( KiROUND( pt.x * aScaleX ), KiROUND( pt.y * aScaleY ) );
242
243 cornersBuffer.clear();
244
245 // at the end of a group of a positive path and its negative children, fill.
246 if( path->next == nullptr || path->next->sign == '+' )
247 {
248 areas.Simplify();
249 holes.Simplify();
250 areas.BooleanSubtract( holes );
251
252 // Ensure there are no self intersecting polygons
253 if( areas.NormalizeAreaOutlines() )
254 {
255 // Convert polygons with holes to polygons with slits
256 areas.Fracture();
257
258 for( int ii = 0; ii < areas.OutlineCount(); ii++ )
259 result.AddOutline( areas.Outline( ii ) );
260 }
261 else
262 {
263 droppedGroups++;
264 }
265
266 // Start the next group even after a failure, so one bad group loses only itself.
267 areas.RemoveAllContours();
268 holes.RemoveAllContours();
269 mainOutline = true;
270 }
271 }
272
273 potrace_state_free( st );
274 potrace_param_free( param );
275 bm_free( potraceBitmap );
276
277 // A dropped group leaves a hole in the result, so say so rather than let the user hunt
278 // for what went missing.
279 if( droppedGroups > 0 )
280 {
281 aReporter.Report( wxString::Format( wxPLURAL( "%d region of the image could not be traced.",
282 "%d regions of the image could not be traced.", droppedGroups ),
283 droppedGroups ),
285 }
286
287 return result;
288}
wxImage ConvertImageToBilevel(const wxImage &aImage, double aThreshold, bool aInvert)
Reduce an image to pure black and white.
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)
wxImage MakeBilevelPreview(const wxImage &aImage, double aThreshold, bool aInvert, const wxSize &aRoom)
Produce the black and white view of an image, shrunk to fit a preview area.
static double cube(double x)
static double square(double x)
SHAPE_POLY_SET TraceBilevelImage(const wxImage &aBilevelImage, double aScaleX, double aScaleY, REPORTER &aReporter)
Trace the black regions of a black and white image into filled polygons.
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
Represent a set of closed polygons.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
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 Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
#define _(s)
std::string source
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
const double epsilon
std::string path
wxString result
Test unit parsing edge cases and error handling.
int delta