KiCad PCB EDA Suite
Loading...
Searching...
No Matches
PNG_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 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
23#include <trigo.h>
24
25#include <cmath>
26
27
29 m_surface( nullptr ),
30 m_context( nullptr ),
31 m_dpi( 300 ),
32 m_width( 0 ),
33 m_height( 0 ),
34 m_antialias( false ),
35 m_backgroundColor( COLOR4D( 0, 0, 0, 0 ) ),
37{
38}
39
40
42{
43 if( m_context )
44 {
45 cairo_destroy( m_context );
46 m_context = nullptr;
47 }
48
49 if( m_surface )
50 {
51 cairo_surface_destroy( m_surface );
52 m_surface = nullptr;
53 }
54}
55
56
57bool PNG_PLOTTER::StartPlot( const wxString& aPageNumber )
58{
59 if( m_width <= 0 || m_height <= 0 )
60 return false;
61
62 // Clean up any existing surface
63 if( m_context )
64 {
65 cairo_destroy( m_context );
66 m_context = nullptr;
67 }
68
69 if( m_surface )
70 {
71 cairo_surface_destroy( m_surface );
72 m_surface = nullptr;
73 }
74
75 // Create Cairo image surface with ARGB32 format for transparency support
76 m_surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, m_width, m_height );
77
78 if( cairo_surface_status( m_surface ) != CAIRO_STATUS_SUCCESS )
79 {
80 cairo_surface_destroy( m_surface );
81 m_surface = nullptr;
82 return false;
83 }
84
85 m_context = cairo_create( m_surface );
86
87 if( cairo_status( m_context ) != CAIRO_STATUS_SUCCESS )
88 {
89 cairo_destroy( m_context );
90 m_context = nullptr;
91 cairo_surface_destroy( m_surface );
92 m_surface = nullptr;
93 return false;
94 }
95
96 // Configure anti-aliasing
97 if( m_antialias )
98 cairo_set_antialias( m_context, CAIRO_ANTIALIAS_DEFAULT );
99 else
100 cairo_set_antialias( m_context, CAIRO_ANTIALIAS_NONE );
101
102 // Fill with background color if specified (non-transparent)
103 if( m_backgroundColor.a > 0 )
104 {
105 cairo_set_source_rgba( m_context, m_backgroundColor.r, m_backgroundColor.g, m_backgroundColor.b,
107 cairo_paint( m_context );
108 }
109
110 // Set default line properties
111 cairo_set_line_cap( m_context, CAIRO_LINE_CAP_ROUND );
112 cairo_set_line_join( m_context, CAIRO_LINE_JOIN_ROUND );
113
114 return true;
115}
116
117
119{
120 if( !m_context )
121 return false;
122
123 // Flush any pending drawing operations
124 cairo_surface_flush( m_surface );
125
126 return true;
127}
128
129
130bool PNG_PLOTTER::SaveFile( const wxString& aPath )
131{
132 if( !m_surface )
133 return false;
134
135 cairo_status_t status = cairo_surface_write_to_png( m_surface, aPath.ToUTF8().data() );
136
137 return status == CAIRO_STATUS_SUCCESS;
138}
139
140
141void PNG_PLOTTER::SetCurrentLineWidth( int aWidth, void* aData )
142{
143 m_currentPenWidth = aWidth;
144
145 if( m_context )
146 {
147 double deviceWidth = userToDeviceSize( static_cast<double>( aWidth ) );
148 cairo_set_line_width( m_context, deviceWidth > 0 ? deviceWidth : 1.0 );
149 }
150}
151
152
153void PNG_PLOTTER::SetColor( const COLOR4D& aColor )
154{
155 m_currentColor = aColor;
156
157 if( m_context )
158 {
159 cairo_set_source_rgba( m_context, aColor.r, aColor.g, aColor.b, aColor.a );
160 }
161}
162
163
165{
166 if( m_context )
167 cairo_set_operator( m_context, aClear ? CAIRO_OPERATOR_CLEAR : CAIRO_OPERATOR_OVER );
168}
169
170
171void PNG_PLOTTER::SetDash( int aLineWidth, LINE_STYLE aLineStyle )
172{
173 if( !m_context )
174 return;
175
176 // For now, only solid lines are supported in the basic implementation
177 cairo_set_dash( m_context, nullptr, 0, 0 );
178}
179
180
181void PNG_PLOTTER::SetViewport( const VECTOR2I& aOffset, double aIusPerDecimil, double aScale, bool aMirror )
182{
183 m_plotOffset = aOffset;
184 m_IUsPerDecimil = aIusPerDecimil;
185 m_plotScale = aScale;
186 m_plotMirror = aMirror;
187
188 // Calculate device scale factor
189 // DPI defines pixels per inch, and there are 10000 decimils per inch
191}
192
193
194void PNG_PLOTTER::Rect( const VECTOR2I& p1, const VECTOR2I& p2, FILL_T aFill, int aWidth, int aCornerRadius )
195{
196 if( !m_context )
197 return;
198
199 VECTOR2D start = userToDeviceCoordinates( p1 );
201
202 double x = std::min( start.x, end.x );
203 double y = std::min( start.y, end.y );
204 double width = std::abs( end.x - start.x );
205 double height = std::abs( end.y - start.y );
206
207 if( aFill == FILL_T::NO_FILL )
208 {
209 SetCurrentLineWidth( aWidth );
210 strokeRect( x, y, width, height );
211 }
212 else
213 {
214 fillRect( x, y, width, height );
215 }
216}
217
218
219void PNG_PLOTTER::Circle( const VECTOR2I& aCenter, int aDiameter, FILL_T aFill, int aWidth )
220{
221 if( !m_context )
222 return;
223
225 double radius = userToDeviceSize( static_cast<double>( aDiameter ) / 2.0 );
226
227 if( aFill == FILL_T::NO_FILL )
228 {
229 SetCurrentLineWidth( aWidth );
231 }
232 else
233 {
235 }
236}
237
238
239void PNG_PLOTTER::Arc( const VECTOR2D& aCenter, const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle, double aRadius,
240 FILL_T aFill, int aWidth )
241{
242 if( !m_context )
243 return;
244
245 VECTOR2D center = userToDeviceCoordinates( VECTOR2I( aCenter.x, aCenter.y ) );
246 double deviceRadius = userToDeviceSize( aRadius );
247
248 // Cairo uses radians, angles measured from positive X axis
249 // KiCad angles are in degrees/decidegrees, positive is counter-clockwise
250 double startRad = aStartAngle.AsRadians();
251 double endRad = ( aStartAngle + aAngle ).AsRadians();
252
253 // Cairo draws arcs counter-clockwise from start to end angle
254 if( aAngle.AsDegrees() < 0 )
255 cairo_arc_negative( m_context, center.x, center.y, deviceRadius, startRad, endRad );
256 else
257 cairo_arc( m_context, center.x, center.y, deviceRadius, startRad, endRad );
258
259 if( aFill == FILL_T::NO_FILL )
260 {
261 SetCurrentLineWidth( aWidth );
262 cairo_stroke( m_context );
263 }
264 else
265 {
266 cairo_fill( m_context );
267 }
268}
269
270
271void PNG_PLOTTER::PenTo( const VECTOR2I& aPos, char aPlume )
272{
273 if( !m_context )
274 return;
275
276 VECTOR2D pos = userToDeviceCoordinates( aPos );
277
278 switch( aPlume )
279 {
280 case 'U':
281 cairo_move_to( m_context, pos.x, pos.y );
282 m_penState = 'U';
283 break;
284
285 case 'D':
286 if( m_penState == 'U' )
287 cairo_move_to( m_context, m_penLastpos.x, m_penLastpos.y );
288
289 cairo_line_to( m_context, pos.x, pos.y );
290 m_penState = 'D';
291 break;
292
293 case 'Z':
294 cairo_stroke( m_context );
295 m_penState = 'Z';
296 break;
297 }
298
299 m_penLastpos = VECTOR2I( pos.x, pos.y );
300}
301
302
303void PNG_PLOTTER::PlotPoly( const std::vector<VECTOR2I>& aCornerList, FILL_T aFill, int aWidth, void* aData )
304{
305 if( !m_context || aCornerList.size() < 2 )
306 return;
307
308 VECTOR2D start = userToDeviceCoordinates( aCornerList[0] );
309 cairo_move_to( m_context, start.x, start.y );
310
311 for( size_t i = 1; i < aCornerList.size(); i++ )
312 {
313 VECTOR2D pt = userToDeviceCoordinates( aCornerList[i] );
314 cairo_line_to( m_context, pt.x, pt.y );
315 }
316
317 // Close the path for filled polygons
318 if( aFill != FILL_T::NO_FILL )
319 {
320 cairo_close_path( m_context );
321 cairo_fill( m_context );
322 }
323 else
324 {
325 SetCurrentLineWidth( aWidth );
326 cairo_stroke( m_context );
327 }
328}
329
330
331void PNG_PLOTTER::FlashPadCircle( const VECTOR2I& aPadPos, int aDiameter, void* aData )
332{
333 Circle( aPadPos, aDiameter, FILL_T::FILLED_SHAPE, 0 );
334}
335
336
337void PNG_PLOTTER::FlashPadOval( const VECTOR2I& aPadPos, const VECTOR2I& aSize, const EDA_ANGLE& aPadOrient,
338 void* aData )
339{
340 // For simplicity, render oval as a thick line between two semicircle centers
341 // with round end caps (which is what an oval pad is)
342 int width = std::min( aSize.x, aSize.y );
343 int len = std::max( aSize.x, aSize.y ) - width;
344
345 if( len == 0 )
346 {
347 // It's actually a circle
348 FlashPadCircle( aPadPos, width, aData );
349 return;
350 }
351
353
354 if( aSize.x > aSize.y )
355 {
356 delta.x = len / 2;
357 delta.y = 0;
358 }
359 else
360 {
361 delta.x = 0;
362 delta.y = len / 2;
363 }
364
365 // Rotate delta by pad orientation
366 RotatePoint( delta, aPadOrient );
367
368 VECTOR2I start = aPadPos - delta;
369 VECTOR2I end = aPadPos + delta;
370
371 ThickSegment( start, end, width, aData );
372}
373
374
375void PNG_PLOTTER::FlashPadRect( const VECTOR2I& aPadPos, const VECTOR2I& aSize, const EDA_ANGLE& aPadOrient,
376 void* aData )
377{
378 // For rotated rectangles, compute the 4 corners and draw as polygon
379 std::vector<VECTOR2I> corners;
380
381 int dx = aSize.x / 2;
382 int dy = aSize.y / 2;
383
384 corners.push_back( VECTOR2I( -dx, -dy ) );
385 corners.push_back( VECTOR2I( -dx, dy ) );
386 corners.push_back( VECTOR2I( dx, dy ) );
387 corners.push_back( VECTOR2I( dx, -dy ) );
388
389 for( VECTOR2I& corner : corners )
390 {
391 RotatePoint( corner, aPadOrient );
392 corner += aPadPos;
393 }
394
395 PlotPoly( corners, FILL_T::FILLED_SHAPE, 0, aData );
396}
397
398
399void PNG_PLOTTER::FlashPadRoundRect( const VECTOR2I& aPadPos, const VECTOR2I& aSize, int aCornerRadius,
400 const EDA_ANGLE& aOrient, void* aData )
401{
402 // Generate rounded rectangle polygon and draw it
403 SHAPE_POLY_SET outline;
404 TransformRoundChamferedRectToPolygon( outline, aPadPos, aSize, aOrient, aCornerRadius, 0.0, 0, 0,
406
407 if( outline.OutlineCount() > 0 )
408 {
409 const SHAPE_LINE_CHAIN& poly = outline.COutline( 0 );
410 std::vector<VECTOR2I> corners;
411
412 for( int i = 0; i < poly.PointCount(); i++ )
413 corners.push_back( poly.CPoint( i ) );
414
415 PlotPoly( corners, FILL_T::FILLED_SHAPE, 0, aData );
416 }
417}
418
419
420void PNG_PLOTTER::FlashPadCustom( const VECTOR2I& aPadPos, const VECTOR2I& aSize, const EDA_ANGLE& aPadOrient,
421 SHAPE_POLY_SET* aPolygons, void* aData )
422{
423 if( !aPolygons || aPolygons->OutlineCount() == 0 )
424 return;
425
426 for( int i = 0; i < aPolygons->OutlineCount(); i++ )
427 {
428 const SHAPE_LINE_CHAIN& outline = aPolygons->COutline( i );
429 std::vector<VECTOR2I> corners;
430
431 for( int j = 0; j < outline.PointCount(); j++ )
432 corners.push_back( outline.CPoint( j ) );
433
434 PlotPoly( corners, FILL_T::FILLED_SHAPE, 0, aData );
435 }
436}
437
438
439void PNG_PLOTTER::FlashPadTrapez( const VECTOR2I& aPadPos, const VECTOR2I* aCorners, const EDA_ANGLE& aPadOrient,
440 void* aData )
441{
442 std::vector<VECTOR2I> corners;
443
444 for( int i = 0; i < 4; i++ )
445 {
446 VECTOR2I corner = aCorners[i];
447 RotatePoint( corner, aPadOrient );
448 corner += aPadPos;
449 corners.push_back( corner );
450 }
451
452 PlotPoly( corners, FILL_T::FILLED_SHAPE, 0, aData );
453}
454
455
456void PNG_PLOTTER::FlashRegularPolygon( const VECTOR2I& aShapePos, int aDiameter, int aCornerCount,
457 const EDA_ANGLE& aOrient, void* aData )
458{
459 std::vector<VECTOR2I> corners;
460 double radius = aDiameter / 2.0;
461 EDA_ANGLE delta = ANGLE_360 / aCornerCount;
462
463 for( int i = 0; i < aCornerCount; i++ )
464 {
465 EDA_ANGLE angle = aOrient + delta * i;
466 VECTOR2I corner( radius * cos( angle.AsRadians() ), radius * sin( angle.AsRadians() ) );
467 corner += aShapePos;
468 corners.push_back( corner );
469 }
470
471 PlotPoly( corners, FILL_T::FILLED_SHAPE, 0, aData );
472}
473
474
476{
477 VECTOR2D pos( aCoordinate.x, aCoordinate.y );
478
479 // Apply offset
480 pos.x -= m_plotOffset.x;
481 pos.y -= m_plotOffset.y;
482
483 // Apply scale
484 pos.x = pos.x * m_plotScale / m_iuPerDeviceUnit;
485 pos.y = pos.y * m_plotScale / m_iuPerDeviceUnit;
486
487 // Handle mirroring
488 if( m_plotMirror )
489 pos.x = m_width - pos.x;
490
491 // Flip Y axis (screen coordinates have Y increasing downward)
492 pos.y = m_height - pos.y;
493
494 return pos;
495}
496
497
499{
500 return VECTOR2D( userToDeviceSize( static_cast<double>( aSize.x ) ),
501 userToDeviceSize( static_cast<double>( aSize.y ) ) );
502}
503
504
505double PNG_PLOTTER::userToDeviceSize( double aSize ) const
506{
507 return std::abs( aSize * m_plotScale / m_iuPerDeviceUnit );
508}
509
510
511void PNG_PLOTTER::fillRect( double aX, double aY, double aWidth, double aHeight )
512{
513 if( !m_context )
514 return;
515
516 cairo_rectangle( m_context, aX, aY, aWidth, aHeight );
517 cairo_fill( m_context );
518}
519
520
521void PNG_PLOTTER::strokeRect( double aX, double aY, double aWidth, double aHeight )
522{
523 if( !m_context )
524 return;
525
526 cairo_rectangle( m_context, aX, aY, aWidth, aHeight );
527 cairo_stroke( m_context );
528}
529
530
531void PNG_PLOTTER::fillCircle( double aCx, double aCy, double aRadius )
532{
533 if( !m_context )
534 return;
535
536 cairo_arc( m_context, aCx, aCy, aRadius, 0, 2 * M_PI );
537 cairo_fill( m_context );
538}
539
540
541void PNG_PLOTTER::strokeCircle( double aCx, double aCy, double aRadius )
542{
543 if( !m_context )
544 return;
545
546 cairo_arc( m_context, aCx, aCy, aRadius, 0, 2 * M_PI );
547 cairo_stroke( m_context );
548}
@ ERROR_INSIDE
double AsDegrees() const
Definition eda_angle.h:116
double AsRadians() const
Definition eda_angle.h:120
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
double r
Red component.
Definition color4d.h:393
double g
Green component.
Definition color4d.h:394
double a
Alpha component.
Definition color4d.h:396
double b
Blue component.
Definition color4d.h:395
bool m_plotMirror
Definition plotter.h:700
double m_iuPerDeviceUnit
Definition plotter.h:697
VECTOR2I m_plotOffset
Definition plotter.h:699
VECTOR2I m_penLastpos
Definition plotter.h:713
int GetPlotterArcHighDef() const
Definition plotter.h:274
char m_penState
Definition plotter.h:712
int m_currentPenWidth
Definition plotter.h:711
double m_plotScale
Plot scale - chosen by the user (even implicitly with 'fit in a4')
Definition plotter.h:689
virtual void ThickSegment(const VECTOR2I &start, const VECTOR2I &end, int width, void *aData)
Definition plotter.cpp:540
double m_IUsPerDecimil
Definition plotter.h:695
virtual void SetCurrentLineWidth(int aWidth, void *aData=nullptr) override
Set the line width for the next drawing.
void fillCircle(double aCx, double aCy, double aRadius)
virtual void FlashPadRect(const VECTOR2I &aPadPos, const VECTOR2I &aSize, const EDA_ANGLE &aPadOrient, void *aData) override
cairo_surface_t * m_surface
virtual void PenTo(const VECTOR2I &aPos, char aPlume) override
Moveto/lineto primitive, moves the 'pen' to the specified direction.
virtual ~PNG_PLOTTER()
virtual void FlashRegularPolygon(const VECTOR2I &aShapePos, int aDiameter, int aCornerCount, const EDA_ANGLE &aOrient, void *aData) override
Flash a regular polygon.
virtual void SetDash(int aLineWidth, LINE_STYLE aLineStyle) override
virtual void Circle(const VECTOR2I &aCenter, int aDiameter, FILL_T aFill, int aWidth) override
virtual void Rect(const VECTOR2I &p1, const VECTOR2I &p2, FILL_T aFill, int aWidth, int aCornerRadius=0) override
virtual void Arc(const VECTOR2D &aCenter, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle, double aRadius, FILL_T aFill, int aWidth) override
bool SaveFile(const wxString &aPath)
Save the rendered image to a PNG file.
virtual void FlashPadOval(const VECTOR2I &aPadPos, const VECTOR2I &aSize, const EDA_ANGLE &aPadOrient, void *aData) override
COLOR4D m_currentColor
virtual bool EndPlot() override
COLOR4D m_backgroundColor
virtual VECTOR2D userToDeviceCoordinates(const VECTOR2I &aCoordinate) override
Transform coordinates from user space (IU) to device space (pixels).
void fillRect(double aX, double aY, double aWidth, double aHeight)
cairo_t * m_context
void strokeCircle(double aCx, double aCy, double aRadius)
virtual void FlashPadCircle(const VECTOR2I &aPadPos, int aDiameter, void *aData) override
virtual void SetColor(const COLOR4D &aColor) override
virtual void FlashPadTrapez(const VECTOR2I &aPadPos, const VECTOR2I *aCorners, const EDA_ANGLE &aPadOrient, void *aData) override
Flash a trapezoidal pad.
virtual void FlashPadRoundRect(const VECTOR2I &aPadPos, const VECTOR2I &aSize, int aCornerRadius, const EDA_ANGLE &aOrient, void *aData) override
virtual VECTOR2D userToDeviceSize(const VECTOR2I &aSize) override
Transform a size from user space to device space.
virtual void FlashPadCustom(const VECTOR2I &aPadPos, const VECTOR2I &aSize, const EDA_ANGLE &aPadOrient, SHAPE_POLY_SET *aPolygons, void *aData) override
void strokeRect(double aX, double aY, double aWidth, double aHeight)
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
@ BLACK
Definition color4d.h:44
void TransformRoundChamferedRectToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aRotation, int aCornerRadius, double aChamferRatio, int aChamferCorners, int aInflate, int aError, ERROR_LOC aErrorLoc)
Convert a rectangle with rounded corners and/or chamfered corners to a polygon.
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:417
FILL_T
Definition eda_shape.h:58
@ NO_FILL
Definition eda_shape.h:59
@ FILLED_SHAPE
Fill with object color.
Definition eda_shape.h:60
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
LINE_STYLE
Dashed line types.
VECTOR2I center
int radius
VECTOR2I end
int delta
#define M_PI
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:229
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
VECTOR2< double > VECTOR2D
Definition vector2d.h:686