KiCad PCB EDA Suite
Loading...
Searching...
No Matches
gerber_to_png.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) 2025 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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 "gerber_to_png.h"
21#include "gerber_file_image.h"
22#include "gerber_draw_item.h"
23#include "dcode.h"
24#include "aperture_macro.h"
25#include "excellon_image.h"
26#include "excellon_defaults.h"
31#include <base_units.h>
32#include <trigo.h>
33#include <cmath>
34#include <wx/filename.h>
35
36
37bool IsExcellonFile( const wxString& aPath )
38{
39 wxFileName fn( aPath );
40 wxString ext = fn.GetExt().Lower();
41 return ext == wxS( "drl" ) || ext == wxS( "xln" ) || ext == wxS( "exc" ) || ext == wxS( "ncd" );
42}
43
44
46{
47 BOX2I bbox;
48 bool first = true;
49
50 for( GERBER_DRAW_ITEM* item : aImage->GetItems() )
51 {
52 BOX2I itemBox = item->GetBoundingBox();
53
54 if( first )
55 {
56 bbox = itemBox;
57 first = false;
58 }
59 else
60 {
61 bbox.Merge( itemBox );
62 }
63 }
64
65 return bbox;
66}
67
68
69std::unique_ptr<GERBER_FILE_IMAGE> LoadGerberOrExcellon( const wxString& aPath, wxString* aErrorMsg,
70 wxArrayString* aMessages )
71{
72 std::unique_ptr<GERBER_FILE_IMAGE> image;
73
74 if( IsExcellonFile( aPath ) )
75 {
76 auto excellon = std::make_unique<EXCELLON_IMAGE>( 0 );
77 EXCELLON_DEFAULTS defaults;
78
79 if( !excellon->LoadFile( aPath, &defaults ) )
80 {
81 if( aErrorMsg )
82 *aErrorMsg = wxString::Format( wxS( "Failed to load Excellon file: %s" ), aPath );
83
84 return nullptr;
85 }
86
87 image = std::move( excellon );
88 }
89 else
90 {
91 image = std::make_unique<GERBER_FILE_IMAGE>( 0 );
92
93 if( !image->LoadGerberFile( aPath ) )
94 {
95 if( aErrorMsg )
96 *aErrorMsg = wxString::Format( wxS( "Failed to load Gerber file: %s" ), aPath );
97
98 return nullptr;
99 }
100 }
101
102 if( aMessages )
103 *aMessages = image->GetMessages();
104
105 return image;
106}
107
108
109namespace
110{
111
115void RenderItem( GERBER_DRAW_ITEM* aItem, PNG_PLOTTER& aPlotter, const KIGFX::COLOR4D& aColor )
116{
117 SHAPE_POLY_SET itemPoly;
118 bool needsFlashOffset = false;
119 bool alreadyInABCoordinates = false;
120
121 if( aItem->m_ShapeAsPolygon.OutlineCount() > 0 )
122 {
123 itemPoly = aItem->m_ShapeAsPolygon;
124 }
125 else if( aItem->m_ShapeType == GBR_SEGMENT )
126 {
127 D_CODE* dcode = aItem->GetDcodeDescr();
128
129 if( dcode && dcode->m_ApertType != APT_RECT )
130 {
131 int arcError = static_cast<int>( gerbIUScale.IU_PER_MM * ARC_LOW_DEF_MM );
132 TransformOvalToPolygon( itemPoly, aItem->m_Start, aItem->m_End,
133 aItem->m_Size.x, arcError, ERROR_INSIDE );
134 }
135 else
136 {
137 aItem->ConvertSegmentToPolygon( &itemPoly );
138 }
139 }
140 else if( aItem->m_ShapeType == GBR_ARC )
141 {
142 const int arcError = gerbIUScale.mmToIU( 0.005 );
143
144 if( aItem->m_Start == aItem->m_End )
145 {
146 int radius = KiROUND( aItem->m_Start.Distance( aItem->m_ArcCentre ) );
147 TransformRingToPolygon( itemPoly, aItem->m_ArcCentre, radius, aItem->m_Size.x,
148 arcError, ERROR_INSIDE );
149 }
150 else
151 {
152 double startAngle = atan2( static_cast<double>( aItem->m_Start.y - aItem->m_ArcCentre.y ),
153 static_cast<double>( aItem->m_Start.x - aItem->m_ArcCentre.x ) );
154 double endAngle = atan2( static_cast<double>( aItem->m_End.y - aItem->m_ArcCentre.y ),
155 static_cast<double>( aItem->m_End.x - aItem->m_ArcCentre.x ) );
156
157 if( startAngle > endAngle )
158 endAngle += 2.0 * M_PI;
159
160 VECTOR2I mid = GetRotated( aItem->m_Start, aItem->m_ArcCentre,
161 -EDA_ANGLE( ( endAngle - startAngle ) / 2.0, RADIANS_T ) );
162
163 TransformArcToPolygon( itemPoly, aItem->m_Start, mid, aItem->m_End, aItem->m_Size.x,
164 arcError, ERROR_INSIDE );
165 }
166 }
167 else if( aItem->m_Flashed )
168 {
169 D_CODE* dcode = aItem->GetDcodeDescr();
170
171 if( dcode )
172 {
173 if( dcode->m_ApertType == APT_MACRO )
174 {
175 APERTURE_MACRO* macro = dcode->GetMacro();
176
177 if( macro )
178 {
179 // Aperture macro polygons are returned in absolute AB coordinates, matching
180 // GERBVIEW_PAINTER::drawApertureMacro(). Do not offset/transform them again.
181 if( aItem->m_AbsolutePolygon.OutlineCount() == 0 )
182 aItem->m_AbsolutePolygon = *macro->GetApertureMacroShape( aItem, aItem->m_Start );
183
184 itemPoly = aItem->m_AbsolutePolygon;
185 alreadyInABCoordinates = true;
186 }
187 }
188 else
189 {
190 dcode->ConvertShapeToPolygon( aItem );
191 itemPoly = dcode->m_Polygon;
192 needsFlashOffset = true;
193 }
194 }
195 }
196
197 if( itemPoly.OutlineCount() == 0 )
198 return;
199
200 // Flashed shapes from ConvertShapeToPolygon are centered at (0,0).
201 // Offset by the item's position before applying the AB transform.
202 VECTOR2I offset = needsFlashOffset ? VECTOR2I( aItem->m_Start ) : VECTOR2I( 0, 0 );
203
204 aPlotter.SetColor( aColor );
205
206 for( int i = 0; i < itemPoly.OutlineCount(); i++ )
207 {
208 const SHAPE_LINE_CHAIN& outline = itemPoly.COutline( i );
209 std::vector<VECTOR2I> pts;
210 pts.reserve( outline.PointCount() );
211
212 for( int j = 0; j < outline.PointCount(); j++ )
213 {
214 if( alreadyInABCoordinates )
215 pts.push_back( outline.CPoint( j ) );
216 else
217 pts.push_back( aItem->GetABPosition( outline.CPoint( j ) + offset ) );
218 }
219
220 if( pts.size() >= 3 )
221 aPlotter.PlotPoly( pts, FILL_T::FILLED_SHAPE, 0 );
222 }
223}
224
225} // anonymous namespace
226
227
228GERBER_PLOTTER_VIEWPORT CalculatePlotterViewport( const BOX2I& aBBox, int aDpi, int aWidth, int aHeight )
229{
231 vp.width = aWidth;
232 vp.height = aHeight;
233
234 if( vp.width == 0 && vp.height == 0 )
235 {
236 double iuPerInch = gerbIUScale.IU_PER_MM * 25.4;
237 double widthInches = static_cast<double>( aBBox.GetWidth() ) / iuPerInch;
238 double heightInches = static_cast<double>( aBBox.GetHeight() ) / iuPerInch;
239
240 vp.width = static_cast<int>( std::ceil( widthInches * aDpi ) );
241 vp.height = static_cast<int>( std::ceil( heightInches * aDpi ) );
242
243 if( vp.width < MIN_PIXEL_SIZE )
245
246 if( vp.height < MIN_PIXEL_SIZE )
248 }
249 else if( vp.width == 0 )
250 {
251 double aspect = static_cast<double>( aBBox.GetWidth() ) / aBBox.GetHeight();
252 vp.width = static_cast<int>( vp.height * aspect );
253 }
254 else if( vp.height == 0 )
255 {
256 double aspect = static_cast<double>( aBBox.GetHeight() ) / aBBox.GetWidth();
257 vp.height = static_cast<int>( vp.width * aspect );
258 }
259
260 vp.iuPerDecimil = gerbIUScale.IU_PER_MILS / 10.0;
261
262 double scaleX = static_cast<double>( vp.width ) * vp.iuPerDecimil * 10000.0 / ( aBBox.GetWidth() * aDpi );
263 double scaleY = static_cast<double>( vp.height ) * vp.iuPerDecimil * 10000.0 / ( aBBox.GetHeight() * aDpi );
264 vp.plotScale = std::min( scaleX, scaleY );
265 vp.offset = aBBox.GetOrigin();
266
267 return vp;
268}
269
270
271bool RenderGerberToPng( const wxString& aInputPath, const wxString& aOutputPath, const GERBER_RENDER_OPTIONS& aOptions,
272 wxString* aErrorMsg, wxArrayString* aMessages )
273{
274 auto image = LoadGerberOrExcellon( aInputPath, aErrorMsg, aMessages );
275
276 if( !image )
277 return false;
278
279 if( image->GetItemsCount() == 0 )
280 {
281 if( aErrorMsg )
282 *aErrorMsg = wxS( "Gerber file contains no draw items" );
283
284 return false;
285 }
286
287 BOX2I bbox;
288
289 if( aOptions.HasViewportOverride() )
290 {
291 // Viewport is specified in Gerber-native coordinates (Y increases upward).
292 // KiCad stores gerber items with Y negated (Y increases downward), so
293 // negate the origin Y and flip the window vertically.
294 double iuPerMm = gerbIUScale.IU_PER_MM;
295 int ox = static_cast<int>( std::round( aOptions.originXMm * iuPerMm ) );
296 int oy = static_cast<int>( std::round(
297 -( aOptions.originYMm + aOptions.windowHeightMm ) * iuPerMm ) );
298 int w = static_cast<int>( std::round( aOptions.windowWidthMm * iuPerMm ) );
299 int h = static_cast<int>( std::round( aOptions.windowHeightMm * iuPerMm ) );
300
301 bbox = BOX2I( VECTOR2I( ox, oy ), VECTOR2I( w, h ) );
302 }
303 else
304 {
305 bbox = CalculateGerberBoundingBox( image.get() );
306 }
307
308 if( bbox.GetWidth() == 0 || bbox.GetHeight() == 0 )
309 {
310 if( aErrorMsg )
311 *aErrorMsg = wxS( "Gerber file has zero-size bounding box" );
312
313 return false;
314 }
315
316 // When using a viewport override with DPI, calculate pixel dimensions from the window
317 int reqWidth = aOptions.width;
318 int reqHeight = aOptions.height;
319
320 if( aOptions.HasViewportOverride() && reqWidth == 0 && reqHeight == 0 )
321 {
322 double mmPerInch = 25.4;
323 reqWidth = static_cast<int>( std::ceil( aOptions.windowWidthMm / mmPerInch * aOptions.dpi ) );
324 reqHeight = static_cast<int>( std::ceil( aOptions.windowHeightMm / mmPerInch * aOptions.dpi ) );
325 }
326
327 GERBER_PLOTTER_VIEWPORT vp = CalculatePlotterViewport( bbox, aOptions.dpi, reqWidth, reqHeight );
328
329 PNG_PLOTTER plotter;
330 plotter.SetColorMode( true );
331 plotter.SetPixelSize( vp.width, vp.height );
332 plotter.SetResolution( aOptions.dpi );
333 plotter.SetAntialias( aOptions.antialias );
334 plotter.SetBackgroundColor( aOptions.backgroundColor );
335 plotter.SetViewport( vp.offset, vp.iuPerDecimil, vp.plotScale, false );
336 plotter.OpenFile( aOutputPath );
337
338 if( !plotter.StartPlot( wxEmptyString ) )
339 {
340 if( aErrorMsg )
341 *aErrorMsg = wxS( "Failed to start PNG plotter" );
342
343 return false;
344 }
345
346 // Render all items with proper polarity handling.
347 // Negative (clear) polarity items erase copper by drawing with the background color.
348 // On transparent exports the background is alpha=0, so source-over is a no-op.
349 // Switch to CAIRO_OPERATOR_CLEAR so those regions become fully transparent.
350 bool transparentBg = ( aOptions.backgroundColor.a == 0 );
351
352 for( GERBER_DRAW_ITEM* item : image->GetItems() )
353 {
354 if( item->GetLayerPolarity() )
355 {
356 plotter.SetClearCompositing( transparentBg );
357 RenderItem( item, plotter, aOptions.backgroundColor );
358 plotter.SetClearCompositing( false );
359 }
360 else
361 {
362 RenderItem( item, plotter, aOptions.foregroundColor );
363 }
364 }
365
366 if( !plotter.EndPlot() )
367 {
368 if( aErrorMsg )
369 *aErrorMsg = wxString::Format( wxS( "Failed to save PNG file: %s" ), aOutputPath );
370
371 return false;
372 }
373
374 return true;
375}
376
377
378bool RenderGerberToPng( const wxString& aInputPath, const wxString& aOutputPath, const JOB_GERBER_EXPORT_PNG& aJob,
379 wxString* aErrorMsg, wxArrayString* aMessages )
380{
381 GERBER_RENDER_OPTIONS options;
382 options.dpi = aJob.m_dpi;
383 options.width = aJob.m_width;
384 options.height = aJob.m_height;
385 options.antialias = aJob.m_antialias;
386 options.backgroundColor =
387 aJob.m_transparentBackground ? KIGFX::COLOR4D( 1.0, 1.0, 1.0, 0.0 ) : KIGFX::COLOR4D::WHITE;
388
389 if( !aJob.m_foregroundColor.IsEmpty() )
391
392 if( !aJob.m_backgroundColor.IsEmpty() )
394
395 double toMm = 1.0;
396
397 switch( aJob.m_units )
398 {
399 case JOB_GERBER_EXPORT_PNG::UNITS::INCH: toMm = 25.4; break;
400 case JOB_GERBER_EXPORT_PNG::UNITS::MILS: toMm = 0.0254; break;
401 case JOB_GERBER_EXPORT_PNG::UNITS::MM: toMm = 1.0; break;
402 }
403
404 options.originXMm = aJob.m_originX * toMm;
405 options.originYMm = aJob.m_originY * toMm;
406 options.windowWidthMm = aJob.m_windowWidth * toMm;
407 options.windowHeightMm = aJob.m_windowHeight * toMm;
408
409 return RenderGerberToPng( aInputPath, aOutputPath, options, aErrorMsg, aMessages );
410}
@ ERROR_INSIDE
constexpr EDA_IU_SCALE gerbIUScale
Definition base_units.h:120
constexpr double ARC_LOW_DEF_MM
Definition base_units.h:127
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
Support the "aperture macro" defined within standard RS274X.
SHAPE_POLY_SET * GetApertureMacroShape(const GERBER_DRAW_ITEM *aParent, const VECTOR2I &aShapePos)
Calculate the primitive shape for flashed items.
constexpr size_type GetWidth() const
Definition box2.h:210
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:654
constexpr size_type GetHeight() const
Definition box2.h:211
constexpr const Vec & GetOrigin() const
Definition box2.h:206
A gerber DCODE (also called Aperture) definition.
Definition dcode.h:76
APERTURE_MACRO * GetMacro() const
Definition dcode.h:132
APERTURE_T m_ApertType
Aperture type ( Line, rectangle, circle, oval poly, macro )
Definition dcode.h:198
SHAPE_POLY_SET m_Polygon
Definition dcode.h:213
void ConvertShapeToPolygon(const GERBER_DRAW_ITEM *aParent)
Convert a shape to an equivalent polygon.
Definition dcode.cpp:288
D_CODE * GetDcodeDescr() const
Return the GetDcodeDescr of this object, or NULL.
VECTOR2I GetABPosition(const VECTOR2I &aXYPosition) const
Return the image position of aPosition for this object.
SHAPE_POLY_SET m_ShapeAsPolygon
void ConvertSegmentToPolygon()
Convert a line to an equivalent polygon.
GBR_BASIC_SHAPE_TYPE m_ShapeType
SHAPE_POLY_SET m_AbsolutePolygon
Hold the image data and parameters for one gerber file and layer parameters.
GERBER_DRAW_ITEMS & GetItems()
Job to convert Gerber/Excellon files to PNG images.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
static const COLOR4D WHITE
Definition color4d.h:401
double a
Alpha component.
Definition color4d.h:392
virtual void SetColorMode(bool aColorMode)
Plot in B/W or color.
Definition plotter.h:160
PNG rasterization plotter using Cairo graphics library.
Definition plotter_png.h:40
void SetPixelSize(int aWidth, int aHeight)
Set the output image dimensions in pixels.
Definition plotter_png.h:64
void SetBackgroundColor(const COLOR4D &aColor)
Set the background color for the image.
Definition plotter_png.h:77
void SetResolution(int aDPI)
Set the output resolution in dots per inch.
Definition plotter_png.h:56
virtual bool EndPlot() override
void SetAntialias(bool aEnable)
Enable or disable anti-aliasing.
Definition plotter_png.h:84
virtual void SetColor(const COLOR4D &aColor) override
virtual bool OpenFile(const wxString &aFullFilename) override
Open or create the plot file aFullFilename.
virtual void PlotPoly(const std::vector< VECTOR2I > &aCornerList, FILL_T aFill, int aWidth, void *aData=nullptr) override
Draw a polygon ( filled or not ).
void SetClearCompositing(bool aClear)
Switch the Cairo compositing operator between CLEAR and OVER.
virtual void SetViewport(const VECTOR2I &aOffset, double aIusPerDecimil, double aScale, bool aMirror) override
Set the plot offset and scaling for the current plot.
virtual bool StartPlot(const wxString &aPageNumber) override
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.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:549
void TransformRingToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aCentre, int aRadius, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arcs to multiple straight segments.
void TransformArcToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arc to multiple straight segments.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
@ APT_RECT
Definition dcode.h:46
@ APT_MACRO
Definition dcode.h:50
@ RADIANS_T
Definition eda_angle.h:32
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:61
@ GBR_SEGMENT
@ GBR_ARC
GERBER_PLOTTER_VIEWPORT CalculatePlotterViewport(const BOX2I &aBBox, int aDpi, int aWidth, int aHeight)
Compute pixel dimensions and plotter scale from a bounding box and DPI/size settings.
bool RenderGerberToPng(const wxString &aInputPath, const wxString &aOutputPath, const GERBER_RENDER_OPTIONS &aOptions, wxString *aErrorMsg, wxArrayString *aMessages)
Render a Gerber or Excellon file to PNG.
bool IsExcellonFile(const wxString &aPath)
Determine if a file is an Excellon drill file based on extension.
BOX2I CalculateGerberBoundingBox(GERBER_FILE_IMAGE *aImage)
Calculate bounding box for all draw items in a gerber image.
std::unique_ptr< GERBER_FILE_IMAGE > LoadGerberOrExcellon(const wxString &aPath, wxString *aErrorMsg, wxArrayString *aMessages)
Load a Gerber or Excellon file, auto-detecting by extension.
static constexpr int MIN_PIXEL_SIZE
management of default values used to read a Excellon (.nc) drill file Some important parameters are n...
Computed plotter viewport parameters from a bounding box and render settings.
Render options for Gerber to PNG conversion.
double windowWidthMm
Viewport width in mm (> 0 enables viewport mode)
KIGFX::COLOR4D foregroundColor
KIGFX::COLOR4D backgroundColor
Transparent white.
int height
0 = calculate from DPI
int width
0 = calculate from DPI
double originYMm
Viewport origin Y in mm.
bool HasViewportOverride() const
double windowHeightMm
Viewport height in mm (> 0 enables viewport mode)
double originXMm
Viewport origin X in mm.
int radius
#define M_PI
VECTOR2I GetRotated(const VECTOR2I &aVector, const EDA_ANGLE &aAngle)
Return a new VECTOR2I that is the result of rotating aVector by aAngle.
Definition trigo.h:73
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683