QGIS API Documentation 4.1.0-Master (26185ffb827)
Loading...
Searching...
No Matches
qgsdxfpaintengine.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsdxpaintengine.cpp
3 --------------------
4 begin : November 2013
5 copyright : (C) 2013 by Marco Hugentobler
6 email : marco at sourcepole dot ch
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsdxfpaintengine.h"
19
20#include "qgsdxfexport.h"
21#include "qgsdxfpaintdevice.h"
22#include "qgslogger.h"
23
24#include <QString>
25
26using namespace Qt::StringLiterals;
27
29 : QPaintEngine( QPaintEngine::AllFeatures /*QPaintEngine::PainterPaths | QPaintEngine::PaintOutsidePaintEvent*/ )
30 , mPaintDevice( dxfDevice )
31 , mDxf( dxf )
32{}
33
34bool QgsDxfPaintEngine::begin( QPaintDevice *pdev )
35{
36 Q_UNUSED( pdev )
37 return true;
38}
39
41{
42 return true;
43}
44
45QPaintEngine::Type QgsDxfPaintEngine::type() const
46{
47 return QPaintEngine::User;
48}
49
50void QgsDxfPaintEngine::drawPixmap( const QRectF &r, const QPixmap &pm, const QRectF &sr )
51{
52 Q_UNUSED( r )
53 Q_UNUSED( pm )
54 Q_UNUSED( sr )
55}
56
57void QgsDxfPaintEngine::updateState( const QPaintEngineState &state )
58{
59 if ( state.state() & QPaintEngine::DirtyTransform )
60 mTransform = state.transform();
61
62 if ( state.state() & QPaintEngine::DirtyPen )
63 mPen = state.pen();
64
65 if ( state.state() & QPaintEngine::DirtyBrush )
66 mBrush = state.brush();
67
68 if ( state.state() & QPaintEngine::DirtyOpacity )
69 {
70 mOpacity = state.opacity();
71 }
72
73 // Store the clip in device coordinates so it can be intersected directly
74 // with the device-space polygons we get in clipPolygonLogical().
75 // Region is processed first and path second: when both flags are set in
76 // the same update, the path branch overwrites the region one, matching
77 // QPainter's "setClipPath replaces any region clip" semantics.
78 if ( state.state() & QPaintEngine::DirtyClipRegion )
79 {
80 if ( state.clipOperation() == Qt::NoClip )
81 {
82 mClipPath = QPainterPath();
83 }
84 else
85 {
86 QPainterPath p;
87 p.addRegion( state.clipRegion() );
88 mClipPath = state.transform().map( p );
89 }
90 }
91 if ( state.state() & QPaintEngine::DirtyClipPath )
92 {
93 if ( state.clipOperation() == Qt::NoClip )
94 {
95 mClipPath = QPainterPath();
96 }
97 else
98 {
99 mClipPath = state.transform().map( state.clipPath() );
100 }
101 }
102 if ( state.state() & ( QPaintEngine::DirtyClipPath | QPaintEngine::DirtyClipRegion | QPaintEngine::DirtyClipEnabled ) )
103 {
104 mClipEnabled = state.isClipEnabled() && !mClipPath.isEmpty();
105 }
106}
107
108QList<QPolygonF> QgsDxfPaintEngine::clipPolygonLogical( const QPolygonF &polyLogical ) const
109{
110 if ( !mClipEnabled || mClipPath.isEmpty() || polyLogical.isEmpty() )
111 return { polyLogical };
112
113 // mClipPath is stored in device coords; map the incoming logical polygon
114 // through the current painter transform so the intersection happens in the
115 // same space, then map the clipped subpaths back to logical coords for the
116 // caller (which feeds them into toDxfCoordinates()).
117 const QPolygonF polyDevice = mTransform.map( polyLogical );
118 QPainterPath polyPath;
119 polyPath.addPolygon( polyDevice );
120 const QPainterPath clipped = polyPath.intersected( mClipPath );
121 if ( clipped.isEmpty() )
122 return {};
123
124 bool invertible = false;
125 const QTransform inv = mTransform.inverted( &invertible );
126 if ( !invertible )
127 return { polyLogical };
128
129 QList<QPolygonF> result;
130 const QList<QPolygonF> subs = clipped.toSubpathPolygons();
131 result.reserve( subs.size() );
132 for ( const QPolygonF &sub : subs )
133 {
134 if ( sub.size() < 2 )
135 continue;
136 result << inv.map( sub );
137 }
138 return result;
139}
140
141void QgsDxfPaintEngine::setRing( QgsPointSequence &polyline, const QPointF *points, int pointCount )
142{
143 polyline.clear();
144 for ( int i = 0; i < pointCount; ++i )
145 polyline.append( toDxfCoordinates( points[i] ) );
146}
147
148void QgsDxfPaintEngine::drawPolygon( const QPointF *points, int pointCount, PolygonDrawMode mode )
149{
150 Q_UNUSED( mode )
151 if ( !mDxf || !mPaintDevice )
152 return;
153
154 QPolygonF inputPoly;
155 inputPoly.reserve( pointCount );
156 for ( int i = 0; i < pointCount; ++i )
157 inputPoly << points[i];
158
159 const QList<QPolygonF> polys = clipPolygonLogical( inputPoly );
160 for ( const QPolygonF &sub : polys )
161 {
162 if ( sub.size() < 2 )
163 continue;
164
165 QgsRingSequence polygon;
166 polygon << QgsPointSequence();
167 setRing( polygon.last(), sub.constData(), sub.size() );
168
169 if ( mode == QPaintEngine::PolylineMode )
170 {
171 if ( mPen.style() != Qt::NoPen && mPen.brush().style() != Qt::NoBrush )
172 mDxf->writePolyline( polygon.at( 0 ), mLayer, u"CONTINUOUS"_s, penColor(), currentWidth() );
173 }
174 else
175 {
176 if ( mBrush.style() != Qt::NoBrush )
177 mDxf->writePolygon( polygon, mLayer, u"SOLID"_s, brushColor() );
178 }
179 }
180}
181
182void QgsDxfPaintEngine::drawPath( const QPainterPath &path )
183{
184 const int pathLength = path.elementCount();
185 for ( int i = 0; i < pathLength; ++i )
186 {
187 const QPainterPath::Element &pathElem = path.elementAt( i );
188 if ( pathElem.type == QPainterPath::MoveToElement )
189 {
190 moveTo( pathElem.x, pathElem.y );
191 }
192 else if ( pathElem.type == QPainterPath::LineToElement )
193 {
194 lineTo( pathElem.x, pathElem.y );
195 }
196 else if ( pathElem.type == QPainterPath::CurveToElement )
197 {
198 curveTo( pathElem.x, pathElem.y );
199 }
200 else if ( pathElem.type == QPainterPath::CurveToDataElement )
201 {
202 mCurrentCurve.append( QPointF( pathElem.x, pathElem.y ) );
203 }
204 }
205 endCurve();
206 endPolygon();
207
208 if ( !mPolygon.isEmpty() && mBrush.style() != Qt::NoBrush )
209 mDxf->writePolygon( mPolygon, mLayer, u"SOLID"_s, brushColor() );
210
211 mPolygon.clear();
212}
213
214void QgsDxfPaintEngine::moveTo( double dx, double dy )
215{
216 endCurve();
217 endPolygon();
218 mCurrentPolygon.append( QPointF( dx, dy ) );
219}
220
221void QgsDxfPaintEngine::lineTo( double dx, double dy )
222{
223 endCurve();
224 mCurrentPolygon.append( QPointF( dx, dy ) );
225}
226
227void QgsDxfPaintEngine::curveTo( double dx, double dy )
228{
229 endCurve();
230 if ( !mCurrentPolygon.isEmpty() )
231 mCurrentCurve.append( mCurrentPolygon.last() );
232
233 mCurrentCurve.append( QPointF( dx, dy ) );
234}
235
236void QgsDxfPaintEngine::endPolygon()
237{
238 if ( mCurrentPolygon.size() > 1 )
239 {
240 if ( mPen.style() != Qt::NoPen )
241 drawPolygon( mCurrentPolygon.constData(), mCurrentPolygon.size(), QPaintEngine::PolylineMode );
242
243 const QList<QPolygonF> clipped = clipPolygonLogical( mCurrentPolygon );
244 for ( const QPolygonF &sub : clipped )
245 {
246 if ( sub.size() < 2 )
247 continue;
248 mPolygon << QgsPointSequence();
249 setRing( mPolygon.last(), sub.constData(), sub.size() );
250 }
251 }
252 mCurrentPolygon.clear();
253}
254
255void QgsDxfPaintEngine::endCurve()
256{
257 if ( mCurrentCurve.empty() )
258 return;
259
260 if ( mCurrentPolygon.empty() )
261 {
262 mCurrentCurve.clear();
263 return;
264 }
265
266 if ( mCurrentCurve.size() >= 3 )
267 {
268 double t = 0.05;
269 for ( int i = 1; i <= 20; ++i ) //approximate curve with 20 segments
270 {
271 mCurrentPolygon.append( bezierPoint( mCurrentCurve, t ) );
272 t += 0.05;
273 }
274 }
275 else if ( mCurrentCurve.size() == 2 )
276 {
277 mCurrentPolygon.append( mCurrentCurve.at( 1 ) );
278 }
279 mCurrentCurve.clear();
280}
281
282void QgsDxfPaintEngine::drawLines( const QLineF *lines, int lineCount )
283{
284 if ( !mDxf || !mPaintDevice || !lines || mPen.style() == Qt::NoPen )
285 return;
286
287 for ( int i = 0; i < lineCount; ++i )
288 {
289 mDxf->writeLine( toDxfCoordinates( lines[i].p1() ), toDxfCoordinates( lines[i].p2() ), mLayer, u"CONTINUOUS"_s, penColor(), currentWidth() );
290 }
291}
292
293QgsPoint QgsDxfPaintEngine::toDxfCoordinates( QPointF pt ) const
294{
295 if ( !mPaintDevice || !mDxf )
296 return QgsPoint( pt.x(), pt.y() );
297
298 const QPointF dxfPt = mPaintDevice->dxfCoordinates( mTransform.map( pt ) ) + mShift;
299 return QgsPoint( dxfPt.x(), dxfPt.y() );
300}
301
302
303double QgsDxfPaintEngine::currentWidth() const
304{
305 if ( !mPaintDevice )
306 return 1;
307
308 return mPen.widthF() * mPaintDevice->widthScaleFactor();
309}
310
311QPointF QgsDxfPaintEngine::bezierPoint( const QList<QPointF> &controlPolygon, double t )
312{
313 double x = 0;
314 double y = 0;
315 const int cPolySize = controlPolygon.size();
316 double bPoly = 0;
317
318 QList<QPointF>::const_iterator it = controlPolygon.constBegin();
319 int i = 0;
320 for ( ; it != controlPolygon.constEnd(); ++it )
321 {
322 bPoly = bernsteinPoly( cPolySize - 1, i, t );
323 x += ( it->x() * bPoly );
324 y += ( it->y() * bPoly );
325 ++i;
326 }
327
328 return QPointF( x, y );
329}
330
331double QgsDxfPaintEngine::bernsteinPoly( int n, int i, double t )
332{
333 if ( i < 0 )
334 return 0;
335
336 return lower( n, i ) * power( t, i ) * power( ( 1 - t ), ( n - i ) );
337}
338
339int QgsDxfPaintEngine::lower( int n, int i )
340{
341 if ( i >= 0 && i <= n )
342 {
343 return faculty( n ) / ( faculty( i ) * faculty( n - i ) );
344 }
345 else
346 {
347 return 0;
348 }
349}
350
351double QgsDxfPaintEngine::power( double a, int b )
352{
353 if ( b == 0 )
354 return 1;
355
356 const double tmp = a;
357 for ( int i = 2; i <= std::abs( b ); i++ )
358 a *= tmp;
359
360 if ( b > 0 )
361 return a;
362 else
363 return 1.0 / a;
364}
365
366int QgsDxfPaintEngine::faculty( int n )
367{
368 if ( n < 0 ) //Is faculty also defined for negative integers?
369 return 0;
370
371 int i;
372 int result = n;
373
374 if ( n == 0 || n == 1 )
375 return 1; //faculty of 0 is 1!
376
377 for ( i = n - 1; i >= 2; i-- )
378 result *= i;
379
380 return result;
381}
382
383QColor QgsDxfPaintEngine::penColor() const
384{
385 if ( qgsDoubleNear( mOpacity, 1.0 ) )
386 {
387 return mPen.color();
388 }
389 QColor c = mPen.color();
390 c.setAlphaF( c.alphaF() * mOpacity );
391 return c;
392}
393
394QColor QgsDxfPaintEngine::brushColor() const
395{
396 QColor c;
397 switch ( mBrush.style() )
398 {
399 // DXF doesn't support gradients — use the middle color as a fallback for
400 // the brush color
401 case Qt::LinearGradientPattern:
402 case Qt::RadialGradientPattern:
403 case Qt::ConicalGradientPattern:
404 {
405 const QGradientStops stops = mBrush.gradient() ? mBrush.gradient()->stops() : QGradientStops();
406 if ( !stops.isEmpty() )
407 {
408 c = stops.at( stops.size() / 2 ).second;
409 }
410 else
411 {
412 c = mBrush.color();
413 }
414 break;
415 }
416 default:
417 c = mBrush.color();
418 break;
419 }
420
421 if ( !qgsDoubleNear( mOpacity, 1.0 ) )
422 {
423 c.setAlphaF( static_cast<float>( c.alphaF() * mOpacity ) );
424 }
425 return c;
426}
Exports QGIS layers to the DXF format.
A paint device for drawing into dxf files.
QPointF dxfCoordinates(QPointF pt) const
Converts a point from device coordinates to dxf coordinates.
void drawPath(const QPainterPath &path) override
bool begin(QPaintDevice *pdev) override
QgsDxfPaintEngine(const QgsDxfPaintDevice *dxfDevice, QgsDxfExport *dxf)
void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override
void updateState(const QPaintEngineState &state) override
void drawLines(const QLineF *lines, int lineCount) override
QPaintEngine::Type type() const override
void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) override
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7257
QVector< QgsPointSequence > QgsRingSequence
QVector< QgsPoint > QgsPointSequence