KiCad PCB EDA Suite
Loading...
Searching...
No Matches
diff_renderer_plotter.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 3
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, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
26#include <base_units.h>
28
29#include <plotters/plotter.h>
32
33#include <wx/file.h>
34#include <wx/log.h>
35
36
37namespace KICAD_DIFF
38{
39
40namespace
41{
42
47void renderShapes( PLOTTER& aPlotter, const std::vector<SCENE_SHAPE>& aShapes )
48{
49 for( const SCENE_SHAPE& s : aShapes )
50 {
51 aPlotter.SetColor( s.color );
52
53 if( !s.polygons.empty() )
54 {
55 SHAPE_POLY_SET set = PolySetFromPolygonList( s.polygons );
56
57 if( set.OutlineCount() == 0 )
58 continue;
59
60 // Fracture turns holes into slits. The plotter fills each outline
61 // independently and cannot subtract a separate hole contour.
62 set.Fracture();
63
64 for( int idx = 0; idx < set.OutlineCount(); ++idx )
65 aPlotter.PlotPoly( set.Outline( idx ), FILL_T::FILLED_SHAPE, 0, nullptr );
66
67 continue;
68 }
69
70 aPlotter.Rect( s.bbox.GetOrigin(), s.bbox.GetEnd(), FILL_T::FILLED_SHAPE, 0 );
71 }
72}
73
74
81class PLOTTER_GEOMETRY_SINK : public GEOMETRY_SINK
82{
83public:
84 explicit PLOTTER_GEOMETRY_SINK( PLOTTER& aPlotter ) : m_plotter( aPlotter ) {}
85
86 void FillPolygon( const DOCUMENT_POLYGON& aPoly ) override
87 {
88 if( aPoly.outline.size() < 3 )
89 return;
90
91 m_plotter.SetColor( aPoly.color );
92
93 std::vector<VECTOR2I> pts( aPoly.outline.begin(), aPoly.outline.end() );
94 m_plotter.PlotPoly( pts, aPoly.filled ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL,
95 EffectivePlotWidth( aPoly.lineWidth ), nullptr );
96 }
97
98 void StrokePolygon( const DOCUMENT_POLYGON& ) override {}
99
100 void DrawSegment( const DOCUMENT_SEGMENT& aSegment ) override
101 {
102 m_plotter.SetColor( aSegment.color );
103 m_plotter.ThickSegment( aSegment.start, aSegment.end, EffectivePlotWidth( aSegment.width ),
104 nullptr );
105 }
106
107 void DrawCircle( const DOCUMENT_CIRCLE& aCircle ) override
108 {
109 m_plotter.SetColor( aCircle.color );
110 m_plotter.Circle( aCircle.center, aCircle.radius * 2,
111 aCircle.filled ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL,
112 EffectivePlotWidth( aCircle.lineWidth ) );
113 }
114
115private:
116 PLOTTER& m_plotter;
117};
118
119
120void renderDocumentGeometry( PLOTTER& aPlotter, const DOCUMENT_GEOMETRY& aGeometry )
121{
122 PLOTTER_GEOMETRY_SINK sink( aPlotter );
123 IterateDocumentGeometry( aGeometry, sink );
124}
125
126
131struct PIXEL_VIEWPORT
132{
133 int pixelWidth = 0;
134 int pixelHeight = 0;
135 double plotScale = 0.0;
136 VECTOR2I offsetIU;
137 double iuPerDecimil = 0.0;
138};
139
140
141PIXEL_VIEWPORT computeViewport( const BOX2I& aBBox, const EDA_IU_SCALE& aIuScale, int aDpi,
142 int aRequestedW, int aRequestedH )
143{
144 PIXEL_VIEWPORT vp;
145
146 // 5% margin around the bbox.
147 constexpr double MARGIN = 0.05;
148
149 BOX2I padded = aBBox;
150 int padX = static_cast<int>( aBBox.GetWidth() * MARGIN );
151 int padY = static_cast<int>( aBBox.GetHeight() * MARGIN );
152 padded.Inflate( std::max( padX, 1 ), std::max( padY, 1 ) );
153
154 // Convert the bbox extent to millimetres using the source document's scale.
155 // PCB/footprint IU is 1 nm; schematic/symbol IU is 100 nm. The aspect ratio
156 // is scale-independent but the absolute mm extent (used for auto-sizing) is
157 // not, so the per-document scale must be used here too. Divide by IU_PER_MM
158 // rather than multiply by MM_PER_IU so the PCB branch reproduces the original
159 // `/1e6` arithmetic bit-for-bit (MM_PER_IU is its rounded reciprocal).
160 double widthMM = padded.GetWidth() / aIuScale.IU_PER_MM;
161 double heightMM = padded.GetHeight() / aIuScale.IU_PER_MM;
162
163 // Default output: scale so the longer dimension renders at 1024 px,
164 // unless caller supplied explicit pixel dimensions.
165 int outW = aRequestedW;
166 int outH = aRequestedH;
167
168 if( outW <= 0 && outH <= 0 )
169 {
170 constexpr int TARGET_PX = 1024;
171 double aspect = widthMM / std::max( heightMM, 0.001 );
172
173 if( aspect >= 1.0 )
174 {
175 outW = TARGET_PX;
176 outH = static_cast<int>( TARGET_PX / aspect );
177 }
178 else
179 {
180 outH = TARGET_PX;
181 outW = static_cast<int>( TARGET_PX * aspect );
182 }
183 }
184 else if( outW <= 0 )
185 {
186 outW = static_cast<int>( outH * widthMM / std::max( heightMM, 0.001 ) );
187 }
188 else if( outH <= 0 )
189 {
190 outH = static_cast<int>( outW * heightMM / std::max( widthMM, 0.001 ) );
191 }
192
193 vp.pixelWidth = std::max( outW, 1 );
194 vp.pixelHeight = std::max( outH, 1 );
195
196 // PNG_PLOTTER / SVG_PLOTTER expect iuPerDecimil = the document's internal-
197 // unit-per-decimil ratio. PCB/footprint and schematic/symbol documents carry
198 // different IU scales, so this must follow the source document or schematic
199 // output is silently mis-scaled by the PCB ratio.
200 vp.iuPerDecimil = aIuScale.IU_PER_MILS / 10.0;
201
202 // plotScale is the user-zoom factor; 1.0 means the bbox should fit the
203 // output canvas at the iuPerDecimil ratio. The plotter handles the
204 // remaining math against pixelWidth/Height.
205 vp.plotScale = 1.0;
206 vp.offsetIU = padded.GetOrigin();
207
208 (void) aDpi; // dpi only affects raster output via the plotter's resolution setter
209
210 return vp;
211}
212
213} // namespace
214
215
222static bool writeEmptyPng( const wxString& aOutputPath )
223{
224 static const unsigned char EMPTY_PNG[] = {
225 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
226 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
227 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
228 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4,
229 0x89, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x44, 0x41,
230 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00,
231 0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00,
232 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
233 0x42, 0x60, 0x82
234 };
235
236 wxFile out( aOutputPath, wxFile::write );
237
238 if( !out.IsOpened() )
239 return false;
240
241 return out.Write( EMPTY_PNG, sizeof( EMPTY_PNG ) ) == sizeof( EMPTY_PNG );
242}
243
244
245static bool writeEmptySvg( const wxString& aOutputPath )
246{
247 static const char EMPTY_SVG[] =
248 "<?xml version=\"1.0\" standalone=\"no\"?>\n"
249 "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"/>\n";
250
251 wxFile out( aOutputPath, wxFile::write );
252
253 if( !out.IsOpened() )
254 return false;
255
256 return out.Write( EMPTY_SVG, sizeof( EMPTY_SVG ) - 1 )
257 == sizeof( EMPTY_SVG ) - 1;
258}
259
260
261bool RenderSceneToPng( const DIFF_SCENE& aScene, const wxString& aOutputPath,
262 const PLOTTER_RENDER_OPTIONS& aOptions )
263{
264 // Caller may have populated geometry without growing the change-derived
265 // documentBBox to match. Use a copy that includes the geometry extent.
266 DIFF_SCENE scene = aScene;
267 ExpandBBoxToGeometry( scene );
268
269 if( scene.documentBBox.GetWidth() <= 0 || scene.documentBBox.GetHeight() <= 0 )
270 {
271 // No drawable extent — identical inputs and no geometry. Emit a
272 // placeholder so the file exists and the CLI exit code (success)
273 // matches "no changes".
274 return writeEmptyPng( aOutputPath );
275 }
276
277 PIXEL_VIEWPORT vp = computeViewport( scene.documentBBox, IuScaleForDocKind( scene.docKind ),
278 aOptions.dpi, aOptions.pixelWidth, aOptions.pixelHeight );
279
280 PNG_PLOTTER plotter;
281 plotter.SetColorMode( true );
282 plotter.SetPixelSize( vp.pixelWidth, vp.pixelHeight );
283 plotter.SetResolution( aOptions.dpi );
284 plotter.SetAntialias( aOptions.antialias );
285 plotter.SetBackgroundColor( aOptions.theme.background );
286 plotter.SetViewport( vp.offsetIU, vp.iuPerDecimil, vp.plotScale, false );
287
288 if( !plotter.OpenFile( aOutputPath ) )
289 return false;
290
291 if( !plotter.StartPlot( wxEmptyString ) )
292 return false;
293
294 renderDocumentGeometry( plotter, scene.referenceGeometry );
295 renderDocumentGeometry( plotter, scene.comparisonGeometry );
296
297 for( CATEGORY cat : PAINT_ORDER )
298 renderShapes( plotter, ShapesFor( scene, cat ) );
299
300 plotter.EndPlot();
301 return true;
302}
303
304
305bool RenderSceneToSvg( const DIFF_SCENE& aScene, const wxString& aOutputPath,
306 const PLOTTER_RENDER_OPTIONS& aOptions )
307{
308 DIFF_SCENE scene = aScene;
309 ExpandBBoxToGeometry( scene );
310
311 if( scene.documentBBox.GetWidth() <= 0 || scene.documentBBox.GetHeight() <= 0 )
312 return writeEmptySvg( aOutputPath );
313
314 PIXEL_VIEWPORT vp = computeViewport( scene.documentBBox, IuScaleForDocKind( scene.docKind ),
315 aOptions.dpi, aOptions.pixelWidth, aOptions.pixelHeight );
316
317 SVG_PLOTTER plotter;
318 plotter.SetColorMode( true );
319 plotter.SetViewport( vp.offsetIU, vp.iuPerDecimil, vp.plotScale, false );
320
321 if( !plotter.OpenFile( aOutputPath ) )
322 return false;
323
324 if( !plotter.StartPlot( wxEmptyString ) )
325 return false;
326
327 renderDocumentGeometry( plotter, scene.referenceGeometry );
328 renderDocumentGeometry( plotter, scene.comparisonGeometry );
329
330 for( CATEGORY cat : PAINT_ORDER )
331 renderShapes( plotter, ShapesFor( scene, cat ) );
332
333 plotter.EndPlot();
334 return true;
335}
336
337} // namespace KICAD_DIFF
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:554
constexpr size_type GetWidth() const
Definition box2.h:210
constexpr size_type GetHeight() const
Definition box2.h:211
constexpr const Vec & GetOrigin() const
Definition box2.h:206
virtual bool OpenFile(const wxString &aFullFilename)
Open or create the plot file aFullFilename.
Definition plotter.cpp:73
virtual void Rect(const VECTOR2I &p1, const VECTOR2I &p2, FILL_T fill, int width, int aCornerRadius=0)=0
virtual void SetColorMode(bool aColorMode)
Plot in B/W or color.
Definition plotter.h:160
virtual void PlotPoly(const std::vector< VECTOR2I > &aCornerList, FILL_T aFill, int aWidth, void *aData)=0
Draw a polygon ( filled or not ).
virtual void SetColor(const COLOR4D &color)=0
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 bool OpenFile(const wxString &aFullFilename) override
Open or create the plot file aFullFilename.
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
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
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...
virtual bool StartPlot(const wxString &aPageNumber) override
Create SVG file header.
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 EndPlot() override
@ NO_FILL
Definition eda_shape.h:60
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:61
static bool writeEmptySvg(const wxString &aOutputPath)
constexpr int EffectivePlotWidth(int aWidth)
Return aWidth if positive, otherwise PLOT_HAIRLINE_IU.
const EDA_IU_SCALE & IuScaleForDocKind(DOC_KIND aKind)
Internal-unit scale a document kind's coordinates are expressed in.
Definition diff_scene.h:253
bool RenderSceneToSvg(const DIFF_SCENE &aScene, const wxString &aOutputPath, const PLOTTER_RENDER_OPTIONS &aOptions)
Render a DIFF_SCENE to an SVG file.
bool RenderSceneToPng(const DIFF_SCENE &aScene, const wxString &aOutputPath, const PLOTTER_RENDER_OPTIONS &aOptions)
Render a DIFF_SCENE to a PNG file.
void IterateDocumentGeometry(const DOCUMENT_GEOMETRY &aGeometry, GEOMETRY_SINK &aSink)
Walk a DOCUMENT_GEOMETRY in the canonical render order shared by the GAL and plotter renderers: every...
Definition diff_scene.h:200
static bool writeEmptyPng(const wxString &aOutputPath)
Write a tiny placeholder PNG (single empty pixel) so a "no differences" diff still produces a valid o...
void ExpandBBoxToGeometry(DIFF_SCENE &aScene)
Grow the scene's documentBBox to also include the extent of any background geometry.
CATEGORY
Visual category each ITEM_CHANGE belongs to in the scene.
Definition diff_scene.h:52
const std::vector< SCENE_SHAPE > & ShapesFor(const DIFF_SCENE &aScene, CATEGORY aCategory)
Read-only access to a DIFF_SCENE's shape list for a given category.
constexpr std::array< CATEGORY, 4 > PAINT_ORDER
Paint order.
Definition diff_scene.h:67
SHAPE_POLY_SET PolySetFromPolygonList(const SCENE_SHAPE::PolygonList &aPolygons)
Build a SHAPE_POLY_SET from a SCENE_SHAPE::PolygonList.
void DrawSegment(const ROUND_SEGMENT_2D &aSegment, unsigned int aNrSidesPerCircle)
Draw a thick line segment with rounded ends.
Plotting engines similar to ps (PostScript, Gerber, svg)
const double IU_PER_MM
Definition base_units.h:74
const double IU_PER_MILS
Definition base_units.h:75
DOCUMENT_GEOMETRY referenceGeometry
Background geometry from the two source documents.
Definition diff_scene.h:239
DOCUMENT_GEOMETRY comparisonGeometry
Definition diff_scene.h:240
DOC_KIND docKind
Source document type.
Definition diff_scene.h:227
Aggregate of background geometry extracted from one source document.
Definition diff_scene.h:163
Sink for the shared DOCUMENT_GEOMETRY walk.
Definition diff_scene.h:184
Options controlling the headless PNG/SVG renderer.
Shared rendering model consumed by both the GAL renderer (interactive widget) and the plotter rendere...
Definition diff_scene.h:90
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683