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 along
17 * with this program. If not, see <http://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 "excellon_image.h"
25#include "excellon_defaults.h"
30#include <base_units.h>
31#include <cmath>
32#include <wx/filename.h>
33
34
35bool IsExcellonFile( const wxString& aPath )
36{
37 wxFileName fn( aPath );
38 wxString ext = fn.GetExt().Lower();
39 return ext == wxS( "drl" ) || ext == wxS( "xln" ) || ext == wxS( "exc" ) || ext == wxS( "ncd" );
40}
41
42
44{
45 BOX2I bbox;
46 bool first = true;
47
48 for( GERBER_DRAW_ITEM* item : aImage->GetItems() )
49 {
50 BOX2I itemBox = item->GetBoundingBox();
51
52 if( first )
53 {
54 bbox = itemBox;
55 first = false;
56 }
57 else
58 {
59 bbox.Merge( itemBox );
60 }
61 }
62
63 return bbox;
64}
65
66
67std::unique_ptr<GERBER_FILE_IMAGE> LoadGerberOrExcellon( const wxString& aPath, wxString* aErrorMsg,
68 wxArrayString* aMessages )
69{
70 std::unique_ptr<GERBER_FILE_IMAGE> image;
71
72 if( IsExcellonFile( aPath ) )
73 {
74 auto excellon = std::make_unique<EXCELLON_IMAGE>( 0 );
75 EXCELLON_DEFAULTS defaults;
76
77 if( !excellon->LoadFile( aPath, &defaults ) )
78 {
79 if( aErrorMsg )
80 *aErrorMsg = wxString::Format( wxS( "Failed to load Excellon file: %s" ), aPath );
81
82 return nullptr;
83 }
84
85 image = std::move( excellon );
86 }
87 else
88 {
89 image = std::make_unique<GERBER_FILE_IMAGE>( 0 );
90
91 if( !image->LoadGerberFile( aPath ) )
92 {
93 if( aErrorMsg )
94 *aErrorMsg = wxString::Format( wxS( "Failed to load Gerber file: %s" ), aPath );
95
96 return nullptr;
97 }
98 }
99
100 if( aMessages )
101 *aMessages = image->GetMessages();
102
103 return image;
104}
105
106
107namespace
108{
109
113void RenderItem( GERBER_DRAW_ITEM* aItem, PNG_PLOTTER& aPlotter, const KIGFX::COLOR4D& aColor )
114{
115 SHAPE_POLY_SET itemPoly;
116 bool needsFlashOffset = false;
117
118 if( aItem->m_ShapeAsPolygon.OutlineCount() > 0 )
119 {
120 itemPoly = aItem->m_ShapeAsPolygon;
121 }
122 else if( aItem->m_ShapeType == GBR_SEGMENT )
123 {
124 D_CODE* dcode = aItem->GetDcodeDescr();
125
126 if( dcode && dcode->m_ApertType != APT_RECT )
127 {
128 int arcError = static_cast<int>( gerbIUScale.IU_PER_MM * ARC_LOW_DEF_MM );
129 TransformOvalToPolygon( itemPoly, aItem->m_Start, aItem->m_End,
130 aItem->m_Size.x, arcError, ERROR_INSIDE );
131 }
132 else
133 {
134 aItem->ConvertSegmentToPolygon( &itemPoly );
135 }
136 }
137 else if( aItem->m_ShapeType == GBR_ARC )
138 {
139 aItem->ConvertSegmentToPolygon( &itemPoly );
140 }
141 else if( aItem->m_Flashed )
142 {
143 D_CODE* dcode = aItem->GetDcodeDescr();
144
145 if( dcode )
146 {
147 dcode->ConvertShapeToPolygon( aItem );
148 itemPoly = dcode->m_Polygon;
149 needsFlashOffset = true;
150 }
151 }
152
153 if( itemPoly.OutlineCount() == 0 )
154 return;
155
156 // Flashed shapes from ConvertShapeToPolygon are centered at (0,0).
157 // Offset by the item's position before applying the AB transform.
158 VECTOR2I offset = needsFlashOffset ? VECTOR2I( aItem->m_Start ) : VECTOR2I( 0, 0 );
159
160 aPlotter.SetColor( aColor );
161
162 for( int i = 0; i < itemPoly.OutlineCount(); i++ )
163 {
164 const SHAPE_LINE_CHAIN& outline = itemPoly.COutline( i );
165 std::vector<VECTOR2I> pts;
166 pts.reserve( outline.PointCount() );
167
168 for( int j = 0; j < outline.PointCount(); j++ )
169 pts.push_back( aItem->GetABPosition( outline.CPoint( j ) + offset ) );
170
171 if( pts.size() >= 3 )
172 aPlotter.PlotPoly( pts, FILL_T::FILLED_SHAPE, 0 );
173 }
174}
175
176} // anonymous namespace
177
178
179GERBER_PLOTTER_VIEWPORT CalculatePlotterViewport( const BOX2I& aBBox, int aDpi, int aWidth, int aHeight )
180{
182 vp.width = aWidth;
183 vp.height = aHeight;
184
185 if( vp.width == 0 && vp.height == 0 )
186 {
187 double iuPerInch = gerbIUScale.IU_PER_MM * 25.4;
188 double widthInches = static_cast<double>( aBBox.GetWidth() ) / iuPerInch;
189 double heightInches = static_cast<double>( aBBox.GetHeight() ) / iuPerInch;
190
191 vp.width = static_cast<int>( std::ceil( widthInches * aDpi ) );
192 vp.height = static_cast<int>( std::ceil( heightInches * aDpi ) );
193
194 if( vp.width < MIN_PIXEL_SIZE )
196
197 if( vp.height < MIN_PIXEL_SIZE )
199 }
200 else if( vp.width == 0 )
201 {
202 double aspect = static_cast<double>( aBBox.GetWidth() ) / aBBox.GetHeight();
203 vp.width = static_cast<int>( vp.height * aspect );
204 }
205 else if( vp.height == 0 )
206 {
207 double aspect = static_cast<double>( aBBox.GetHeight() ) / aBBox.GetWidth();
208 vp.height = static_cast<int>( vp.width * aspect );
209 }
210
211 vp.iuPerDecimil = gerbIUScale.IU_PER_MILS / 10.0;
212
213 double scaleX = static_cast<double>( vp.width ) * vp.iuPerDecimil * 10000.0 / ( aBBox.GetWidth() * aDpi );
214 double scaleY = static_cast<double>( vp.height ) * vp.iuPerDecimil * 10000.0 / ( aBBox.GetHeight() * aDpi );
215 vp.plotScale = std::min( scaleX, scaleY );
216 vp.offset = aBBox.GetOrigin();
217
218 return vp;
219}
220
221
222bool RenderGerberToPng( const wxString& aInputPath, const wxString& aOutputPath, const GERBER_RENDER_OPTIONS& aOptions,
223 wxString* aErrorMsg, wxArrayString* aMessages )
224{
225 auto image = LoadGerberOrExcellon( aInputPath, aErrorMsg, aMessages );
226
227 if( !image )
228 return false;
229
230 if( image->GetItemsCount() == 0 )
231 {
232 if( aErrorMsg )
233 *aErrorMsg = wxS( "Gerber file contains no draw items" );
234
235 return false;
236 }
237
238 BOX2I bbox;
239
240 if( aOptions.HasViewportOverride() )
241 {
242 // Viewport is specified in Gerber-native coordinates (Y increases upward).
243 // KiCad stores gerber items with Y negated (Y increases downward), so
244 // negate the origin Y and flip the window vertically.
245 double iuPerMm = gerbIUScale.IU_PER_MM;
246 int ox = static_cast<int>( std::round( aOptions.originXMm * iuPerMm ) );
247 int oy = static_cast<int>( std::round(
248 -( aOptions.originYMm + aOptions.windowHeightMm ) * iuPerMm ) );
249 int w = static_cast<int>( std::round( aOptions.windowWidthMm * iuPerMm ) );
250 int h = static_cast<int>( std::round( aOptions.windowHeightMm * iuPerMm ) );
251
252 bbox = BOX2I( VECTOR2I( ox, oy ), VECTOR2I( w, h ) );
253 }
254 else
255 {
256 bbox = CalculateGerberBoundingBox( image.get() );
257 }
258
259 if( bbox.GetWidth() == 0 || bbox.GetHeight() == 0 )
260 {
261 if( aErrorMsg )
262 *aErrorMsg = wxS( "Gerber file has zero-size bounding box" );
263
264 return false;
265 }
266
267 // When using a viewport override with DPI, calculate pixel dimensions from the window
268 int reqWidth = aOptions.width;
269 int reqHeight = aOptions.height;
270
271 if( aOptions.HasViewportOverride() && reqWidth == 0 && reqHeight == 0 )
272 {
273 double mmPerInch = 25.4;
274 reqWidth = static_cast<int>( std::ceil( aOptions.windowWidthMm / mmPerInch * aOptions.dpi ) );
275 reqHeight = static_cast<int>( std::ceil( aOptions.windowHeightMm / mmPerInch * aOptions.dpi ) );
276 }
277
278 GERBER_PLOTTER_VIEWPORT vp = CalculatePlotterViewport( bbox, aOptions.dpi, reqWidth, reqHeight );
279
280 PNG_PLOTTER plotter;
281 plotter.SetPixelSize( vp.width, vp.height );
282 plotter.SetResolution( aOptions.dpi );
283 plotter.SetAntialias( aOptions.antialias );
284 plotter.SetBackgroundColor( aOptions.backgroundColor );
285 plotter.SetViewport( vp.offset, vp.iuPerDecimil, vp.plotScale, false );
286
287 // Start plotting
288 if( !plotter.StartPlot( wxEmptyString ) )
289 {
290 if( aErrorMsg )
291 *aErrorMsg = wxS( "Failed to start PNG plotter" );
292
293 return false;
294 }
295
296 // Render all items with proper polarity handling.
297 // Negative (clear) polarity items erase copper by drawing with the background color.
298 // On transparent exports the background is alpha=0, so source-over is a no-op.
299 // Switch to CAIRO_OPERATOR_CLEAR so those regions become fully transparent.
300 bool transparentBg = ( aOptions.backgroundColor.a == 0 );
301
302 for( GERBER_DRAW_ITEM* item : image->GetItems() )
303 {
304 if( item->GetLayerPolarity() )
305 {
306 plotter.SetClearCompositing( transparentBg );
307 RenderItem( item, plotter, aOptions.backgroundColor );
308 plotter.SetClearCompositing( false );
309 }
310 else
311 {
312 RenderItem( item, plotter, aOptions.foregroundColor );
313 }
314 }
315
316 plotter.EndPlot();
317
318 // Save the file
319 if( !plotter.SaveFile( aOutputPath ) )
320 {
321 if( aErrorMsg )
322 *aErrorMsg = wxString::Format( wxS( "Failed to save PNG file: %s" ), aOutputPath );
323
324 return false;
325 }
326
327 return true;
328}
329
330
331bool RenderGerberToPng( const wxString& aInputPath, const wxString& aOutputPath, const JOB_GERBER_EXPORT_PNG& aJob,
332 wxString* aErrorMsg, wxArrayString* aMessages )
333{
334 GERBER_RENDER_OPTIONS options;
335 options.dpi = aJob.m_dpi;
336 options.width = aJob.m_width;
337 options.height = aJob.m_height;
338 options.antialias = aJob.m_antialias;
339 options.backgroundColor =
340 aJob.m_transparentBackground ? KIGFX::COLOR4D( 1.0, 1.0, 1.0, 0.0 ) : KIGFX::COLOR4D::WHITE;
341
342 if( !aJob.m_foregroundColor.IsEmpty() )
344
345 if( !aJob.m_backgroundColor.IsEmpty() )
347
348 double toMm = 1.0;
349
350 switch( aJob.m_units )
351 {
352 case JOB_GERBER_EXPORT_PNG::UNITS::INCH: toMm = 25.4; break;
353 case JOB_GERBER_EXPORT_PNG::UNITS::MILS: toMm = 0.0254; break;
354 case JOB_GERBER_EXPORT_PNG::UNITS::MM: toMm = 1.0; break;
355 }
356
357 options.originXMm = aJob.m_originX * toMm;
358 options.originYMm = aJob.m_originY * toMm;
359 options.windowWidthMm = aJob.m_windowWidth * toMm;
360 options.windowHeightMm = aJob.m_windowHeight * toMm;
361
362 return RenderGerberToPng( aInputPath, aOutputPath, options, aErrorMsg, aMessages );
363}
@ ERROR_INSIDE
constexpr EDA_IU_SCALE gerbIUScale
Definition base_units.h:111
constexpr double ARC_LOW_DEF_MM
Definition base_units.h:118
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr const Vec & GetOrigin() const
Definition box2.h:210
A gerber DCODE (also called Aperture) definition.
Definition dcode.h:80
APERTURE_T m_ApertType
Aperture type ( Line, rectangle, circle, oval poly, macro )
Definition dcode.h:202
SHAPE_POLY_SET m_Polygon
Definition dcode.h:217
void ConvertShapeToPolygon(const GERBER_DRAW_ITEM *aParent)
Convert a shape to an equivalent polygon.
Definition dcode.cpp:297
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
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:105
static const COLOR4D WHITE
Definition color4d.h:405
double a
Alpha component.
Definition color4d.h:396
PNG rasterization plotter using Cairo graphics library.
Definition plotter_png.h:34
void SetPixelSize(int aWidth, int aHeight)
Set the output image dimensions in pixels.
Definition plotter_png.h:59
bool SaveFile(const wxString &aPath)
Save the rendered image to a PNG file.
void SetBackgroundColor(const COLOR4D &aColor)
Set the background color for the image.
Definition plotter_png.h:72
void SetResolution(int aDPI)
Set the output resolution in dots per inch.
Definition plotter_png.h:51
virtual bool EndPlot() override
void SetAntialias(bool aEnable)
Enable or disable anti-aliasing.
Definition plotter_png.h:79
virtual void SetColor(const COLOR4D &aColor) override
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
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:50
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:60
@ 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.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687