QGIS API Documentation 3.43.0-Master (56aa1fd18d7)
qgssymbol.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssymbol.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include <QColor>
17#include <QImage>
18#include <QPainter>
19#include <QSize>
20#include <QSvgGenerator>
21#include <QPicture>
22
23#include <cmath>
24#include <map>
25#include <random>
26
27#include "qgssymbol.h"
29#include "qgsrectangle.h"
30#include "qgssymbollayer.h"
31
34#include "qgslogger.h"
35#include "qgsrendercontext.h" // for bigSymbolPreview
36#include "qgsproject.h"
38#include "qgsstyle.h"
39#include "qgspainteffect.h"
40#include "qgsvectorlayer.h"
41#include "qgsfeature.h"
42#include "qgsgeometry.h"
43#include "qgsmultipoint.h"
45#include "qgslinestring.h"
46#include "qgspolygon.h"
47#include "qgsclipper.h"
48#include "qgsproperty.h"
50#include "qgsapplication.h"
53#include "qgslegendpatchshape.h"
54#include "qgsgeos.h"
55#include "qgsmarkersymbol.h"
56#include "qgslinesymbol.h"
57#include "qgsfillsymbol.h"
58#include "qgsfillsymbollayer.h"
59#include "qgscolorutils.h"
60#include "qgsunittypes.h"
62#include "qgspainting.h"
63#include "qgssldexportcontext.h"
64
65QgsPropertiesDefinition QgsSymbol::sPropertyDefinitions;
66
67
68//
69// QgsSymbolBufferSettings
70//
71
73{
74 mFillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << new QgsSimpleFillSymbolLayer( QColor( 255, 255, 255 ), Qt::SolidPattern, QColor( 200, 200, 200 ), Qt::NoPen ) );
75}
76
78 : mEnabled( other.mEnabled )
79 , mSize( other.mSize )
80 , mSizeUnit( other.mSizeUnit )
81 , mSizeMapUnitScale( other.mSizeMapUnitScale )
82 , mJoinStyle( other.mJoinStyle )
83 , mFillSymbol( other.mFillSymbol ? other.mFillSymbol->clone() : nullptr )
84{
85
86}
87
89{
90 mEnabled = other.mEnabled;
91 mSize = other.mSize;
92 mSizeUnit = other.mSizeUnit;
93 mSizeMapUnitScale = other.mSizeMapUnitScale;
94 mJoinStyle = other.mJoinStyle;
95 mFillSymbol.reset( other.mFillSymbol ? other.mFillSymbol->clone() : nullptr );
96 return *this;
97}
98
100{
101 return mFillSymbol.get();
102}
103
105{
106 mFillSymbol.reset( symbol );
107}
108
110
111void QgsSymbolBufferSettings::writeXml( QDomElement &element, const QgsReadWriteContext &context ) const
112{
113 QDomElement symbolBufferElem = element.ownerDocument().createElement( QStringLiteral( "buffer" ) );
114 symbolBufferElem.setAttribute( QStringLiteral( "enabled" ), mEnabled );
115 symbolBufferElem.setAttribute( QStringLiteral( "size" ), mSize );
116 symbolBufferElem.setAttribute( QStringLiteral( "sizeUnits" ), QgsUnitTypes::encodeUnit( mSizeUnit ) );
117 symbolBufferElem.setAttribute( QStringLiteral( "sizeMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale ) );
118 symbolBufferElem.setAttribute( QStringLiteral( "joinStyle" ), static_cast< unsigned int >( mJoinStyle ) );
119
120 if ( mFillSymbol )
121 {
122 QDomDocument document = element.ownerDocument();
123 const QDomElement fillElem = QgsSymbolLayerUtils::saveSymbol( QString(), mFillSymbol.get(), document, context );
124 symbolBufferElem.appendChild( fillElem );
125 }
126
127 element.appendChild( symbolBufferElem );
128}
129
130void QgsSymbolBufferSettings::readXml( const QDomElement &element, const QgsReadWriteContext &context )
131{
132 const QDomElement symbolBufferElem = element.firstChildElement( QStringLiteral( "buffer" ) );
133 mEnabled = symbolBufferElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
134 mSize = symbolBufferElem.attribute( QStringLiteral( "size" ), QStringLiteral( "1" ) ).toDouble();
135 mSizeUnit = QgsUnitTypes::decodeRenderUnit( symbolBufferElem.attribute( QStringLiteral( "sizeUnits" ) ) );
136 mSizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( symbolBufferElem.attribute( QStringLiteral( "sizeMapUnitScale" ) ) );
137 mJoinStyle = static_cast< Qt::PenJoinStyle >( symbolBufferElem.attribute( QStringLiteral( "joinStyle" ), QString::number( Qt::RoundJoin ) ).toUInt() );
138
139 const QDomElement fillSymbolElem = symbolBufferElem.firstChildElement( QStringLiteral( "symbol" ) );
140 if ( !fillSymbolElem.isNull() )
141 {
142 mFillSymbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( fillSymbolElem, context );
143 }
144 else
145 {
146 mFillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << new QgsSimpleFillSymbolLayer( QColor( 255, 255, 255 ), Qt::SolidPattern, QColor( 200, 200, 200 ), Qt::NoPen ) );
147 }
148}
149
150
151//
152// QgsSymbol
153//
154
155Q_NOWARN_DEPRECATED_PUSH // because of deprecated mLayer
157 : mType( type )
158 , mLayers( layers )
159{
160
161 // check they're all correct symbol layers
162 for ( int i = 0; i < mLayers.count(); i++ )
163 {
164 if ( !mLayers.at( i ) )
165 {
166 mLayers.removeAt( i-- );
167 }
168 else if ( !mLayers.at( i )->isCompatibleWithSymbol( this ) )
169 {
170 delete mLayers.at( i );
171 mLayers.removeAt( i-- );
172 }
173 }
174}
176
177QPolygonF QgsSymbol::_getLineString( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
178{
179 if ( curve.is3D() )
180 return _getLineString3d( context, curve, clipToExtent );
181 else
182 return _getLineString2d( context, curve, clipToExtent );
183}
184
185QPolygonF QgsSymbol::_getLineString3d( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
186{
187 const unsigned int nPoints = curve.numPoints();
188
190 const QgsMapToPixel &mtp = context.mapToPixel();
191 QVector< double > pointsX;
192 QVector< double > pointsY;
193 QVector< double > pointsZ;
194
195 // apply clipping for large lines to achieve a better rendering performance
196 if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) )
197 {
198 const QgsRectangle e = context.extent();
199 const double cw = e.width() / 10;
200 const double ch = e.height() / 10;
201 const QgsBox3D clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
202
203 const QgsLineString *lineString = nullptr;
204 std::unique_ptr< QgsLineString > segmentized;
205 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( &curve ) )
206 {
207 lineString = ls;
208 }
209 else
210 {
211 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
212 lineString = segmentized.get();
213 }
214
215 QgsClipper::clipped3dLine( lineString->xVector(), lineString->yVector(), lineString->zVector(), pointsX, pointsY, pointsZ, clipRect );
216 }
217 else
218 {
219 // clone...
220 if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( &curve ) )
221 {
222 pointsX = ls->xVector();
223 pointsY = ls->yVector();
224 pointsZ = ls->zVector();
225 }
226 else
227 {
228 std::unique_ptr< QgsLineString > segmentized;
229 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
230
231 pointsX = segmentized->xVector();
232 pointsY = segmentized->yVector();
233 pointsZ = segmentized->zVector();
234 }
235 }
236
237 // transform the points to screen coordinates
238 const QVector< double > preTransformPointsZ = pointsZ;
239 bool wasTransformed = false;
240 if ( ct.isValid() )
241 {
242 //create x, y arrays
243 const int nVertices = pointsX.size();
244 wasTransformed = true;
245
246 try
247 {
248 ct.transformCoords( nVertices, pointsX.data(), pointsY.data(), pointsZ.data(), Qgis::TransformDirection::Forward );
249 }
250 catch ( QgsCsException & )
251 {
252 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
253 }
254 }
255
256 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
257 {
258 const int size = pointsX.size();
259
260 const double *xIn = pointsX.data();
261 const double *yIn = pointsY.data();
262 const double *zIn = pointsZ.data();
263
264 const double *preTransformZIn = wasTransformed ? preTransformPointsZ.constData() : nullptr;
265
266 double *xOut = pointsX.data();
267 double *yOut = pointsY.data();
268 double *zOut = pointsZ.data();
269 int outSize = 0;
270 for ( int i = 0; i < size; ++i )
271 {
272 bool pointOk = std::isfinite( *xIn ) && std::isfinite( *yIn );
273
274 // skip z points which have been made non-finite during transformations only. Ie if:
275 // - we did no transformation, then always render even if non-finite z
276 // - we did transformation and z is finite then render
277 // - we did transformation and z is non-finite BUT input z was also non finite then render
278 // - we did transformation and z is non-finite AND input z WAS finite then skip
279 pointOk &= !wasTransformed || std::isfinite( *zIn ) || !std::isfinite( *preTransformZIn );
280
281 if ( pointOk )
282 {
283 *xOut++ = *xIn++;
284 *yOut++ = *yIn++;
285 *zOut++ = *zIn++;
286 outSize++;
287 }
288 else
289 {
290 xIn++;
291 yIn++;
292 zIn++;
293 }
294
295 if ( preTransformZIn )
296 preTransformZIn++;
297 }
298 pointsX.resize( outSize );
299 pointsY.resize( outSize );
300 pointsZ.resize( outSize );
301 }
302
303 if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection )
304 {
305 // early clipping was not possible, so we have to apply it here after transformation
306 const QgsRectangle e = context.mapExtent();
307 const double cw = e.width() / 10;
308 const double ch = e.height() / 10;
309 const QgsBox3D clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
310
311 QVector< double > tempX;
312 QVector< double > tempY;
313 QVector< double > tempZ;
314 QgsClipper::clipped3dLine( pointsX, pointsY, pointsZ, tempX, tempY, tempZ, clipRect );
315 pointsX = tempX;
316 pointsY = tempY;
317 pointsZ = tempZ;
318 }
319
320 const int polygonSize = pointsX.size();
321 QPolygonF out( polygonSize );
322 const double *x = pointsX.constData();
323 const double *y = pointsY.constData();
324 QPointF *dest = out.data();
325 for ( int i = 0; i < polygonSize; ++i )
326 {
327 double screenX = *x++;
328 double screenY = *y++;
329 mtp.transformInPlace( screenX, screenY );
330 *dest++ = QPointF( screenX, screenY );
331 }
332
333 return out;
334}
335
336QPolygonF QgsSymbol::_getLineString2d( QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent )
337{
338 const unsigned int nPoints = curve.numPoints();
339
341 const QgsMapToPixel &mtp = context.mapToPixel();
342 QPolygonF pts;
343
344 // apply clipping for large lines to achieve a better rendering performance
345 if ( clipToExtent && nPoints > 1 && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) )
346 {
347 const QgsRectangle e = context.extent();
348 const double cw = e.width() / 10;
349 const double ch = e.height() / 10;
350 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
351 pts = QgsClipper::clippedLine( curve, clipRect );
352 }
353 else
354 {
355 pts = curve.asQPolygonF();
356 }
357
358 // transform the QPolygonF to screen coordinates
359 if ( ct.isValid() )
360 {
361 try
362 {
363 ct.transformPolygon( pts );
364 }
365 catch ( QgsCsException & )
366 {
367 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
368 }
369 }
370
371 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
372 pts.erase( std::remove_if( pts.begin(), pts.end(),
373 []( const QPointF point )
374 {
375 return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
376 } ), pts.end() );
377
378 if ( clipToExtent && nPoints > 1 && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection )
379 {
380 // early clipping was not possible, so we have to apply it here after transformation
381 const QgsRectangle e = context.mapExtent();
382 const double cw = e.width() / 10;
383 const double ch = e.height() / 10;
384 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
385 pts = QgsClipper::clippedLine( pts, clipRect );
386 }
387
388 QPointF *ptr = pts.data();
389 for ( int i = 0; i < pts.size(); ++i, ++ptr )
390 {
391 mtp.transformInPlace( ptr->rx(), ptr->ry() );
392 }
393
394 return pts;
395}
396
397
398QPolygonF QgsSymbol::_getPolygonRing( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
399{
400 if ( curve.is3D() )
401 return _getPolygonRing3d( context, curve, clipToExtent, isExteriorRing, correctRingOrientation );
402 else
403 return _getPolygonRing2d( context, curve, clipToExtent, isExteriorRing, correctRingOrientation );
404}
405
406QPolygonF QgsSymbol::_getPolygonRing3d( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
407{
408 const QgsCoordinateTransform ct = context.coordinateTransform();
409 const QgsMapToPixel &mtp = context.mapToPixel();
410
411 QVector< double > pointsX;
412 QVector< double > pointsY;
413 QVector< double > pointsZ;
414
415 if ( curve.numPoints() < 1 )
416 return QPolygonF();
417
418 bool reverseRing = false;
419 if ( correctRingOrientation )
420 {
421 // ensure consistent polygon ring orientation
422 if ( ( isExteriorRing && curve.orientation() != Qgis::AngularDirection::Clockwise ) || ( !isExteriorRing && curve.orientation() != Qgis::AngularDirection::CounterClockwise ) )
423 {
424 reverseRing = true;
425 }
426 }
427
428 //clip close to view extent, if needed
429 if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( curve.boundingBox() ) )
430 {
431 const QgsRectangle e = context.extent();
432 const double cw = e.width() / 10;
433 const double ch = e.height() / 10;
434 const QgsBox3D clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
435
436 const QgsLineString *lineString = nullptr;
437 std::unique_ptr< QgsLineString > segmentized;
438 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( &curve ) )
439 {
440 lineString = ls;
441 }
442 else
443 {
444 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
445 lineString = segmentized.get();
446 }
447
448 pointsX = lineString->xVector();
449 pointsY = lineString->yVector();
450 pointsZ = lineString->zVector();
451
452 QgsClipper::trimPolygon( pointsX, pointsY, pointsZ, clipRect );
453 }
454 else
455 {
456 // clone...
457 if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( &curve ) )
458 {
459 pointsX = ls->xVector();
460 pointsY = ls->yVector();
461 pointsZ = ls->zVector();
462 }
463 else
464 {
465 std::unique_ptr< QgsLineString > segmentized;
466 segmentized.reset( qgsgeometry_cast< QgsLineString * >( curve.segmentize( ) ) );
467
468 pointsX = segmentized->xVector();
469 pointsY = segmentized->yVector();
470 pointsZ = segmentized->zVector();
471 }
472 }
473
474 if ( reverseRing )
475 {
476 std::reverse( pointsX.begin(), pointsX.end() );
477 std::reverse( pointsY.begin(), pointsY.end() );
478 std::reverse( pointsZ.begin(), pointsZ.end() );
479 }
480
481 //transform the QPolygonF to screen coordinates
482 const QVector< double > preTransformPointsZ = pointsZ;
483 bool wasTransformed = false;
484 if ( ct.isValid() )
485 {
486 const int nVertices = pointsX.size();
487 wasTransformed = true;
488 try
489 {
490 ct.transformCoords( nVertices, pointsX.data(), pointsY.data(), pointsZ.data(), Qgis::TransformDirection::Forward );
491 }
492 catch ( QgsCsException & )
493 {
494 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
495 }
496 }
497
498 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
499 {
500 const int size = pointsX.size();
501
502 const double *xIn = pointsX.data();
503 const double *yIn = pointsY.data();
504 const double *zIn = pointsZ.data();
505
506 const double *preTransformZIn = wasTransformed ? preTransformPointsZ.constData() : nullptr;
507
508 double *xOut = pointsX.data();
509 double *yOut = pointsY.data();
510 double *zOut = pointsZ.data();
511 int outSize = 0;
512 for ( int i = 0; i < size; ++i )
513 {
514 bool pointOk = std::isfinite( *xIn ) && std::isfinite( *yIn );
515 // skip z points which have been made non-finite during transformations only. Ie if:
516 // - we did no transformation, then always render even if non-finite z
517 // - we did transformation and z is finite then render
518 // - we did transformation and z is non-finite BUT input z was also non finite then render
519 // - we did transformation and z is non-finite AND input z WAS finite then skip
520 pointOk &= !wasTransformed || std::isfinite( *zIn ) || !std::isfinite( *preTransformZIn );
521
522 if ( pointOk )
523 {
524 *xOut++ = *xIn++;
525 *yOut++ = *yIn++;
526 *zOut++ = *zIn++;
527 outSize++;
528 }
529 else
530 {
531 xIn++;
532 yIn++;
533 zIn++;
534 }
535
536 if ( preTransformZIn )
537 preTransformZIn++;
538 }
539 pointsX.resize( outSize );
540 pointsY.resize( outSize );
541 pointsZ.resize( outSize );
542 }
543
544 if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( curve.boundingBox() ) )
545 {
546 // early clipping was not possible, so we have to apply it here after transformation
547 const QgsRectangle e = context.mapExtent();
548 const double cw = e.width() / 10;
549 const double ch = e.height() / 10;
550 const QgsBox3D clipRect( e.xMinimum() - cw, e.yMinimum() - ch, -HUGE_VAL, e.xMaximum() + cw, e.yMaximum() + ch, HUGE_VAL ); // TODO also need to be clipped according to z axis
551
552 QgsClipper::trimPolygon( pointsX, pointsY, pointsZ, clipRect );
553 }
554
555 const int polygonSize = pointsX.size();
556 QPolygonF out( polygonSize );
557 const double *x = pointsX.constData();
558 const double *y = pointsY.constData();
559 QPointF *dest = out.data();
560 for ( int i = 0; i < polygonSize; ++i )
561 {
562 double screenX = *x++;
563 double screenY = *y++;
564 mtp.transformInPlace( screenX, screenY );
565 *dest++ = QPointF( screenX, screenY );
566 }
567
568 if ( !out.empty() && !out.isClosed() )
569 out << out.at( 0 );
570
571 return out;
572}
573
574
575QPolygonF QgsSymbol::_getPolygonRing2d( QgsRenderContext &context, const QgsCurve &curve, const bool clipToExtent, const bool isExteriorRing, const bool correctRingOrientation )
576{
577 const QgsCoordinateTransform ct = context.coordinateTransform();
578 const QgsMapToPixel &mtp = context.mapToPixel();
579
580 QPolygonF poly = curve.asQPolygonF();
581
582 if ( curve.numPoints() < 1 )
583 return QPolygonF();
584
585 if ( correctRingOrientation )
586 {
587 // ensure consistent polygon ring orientation
588 if ( isExteriorRing && curve.orientation() != Qgis::AngularDirection::Clockwise )
589 std::reverse( poly.begin(), poly.end() );
590 else if ( !isExteriorRing && curve.orientation() != Qgis::AngularDirection::CounterClockwise )
591 std::reverse( poly.begin(), poly.end() );
592 }
593
594 //clip close to view extent, if needed
595 if ( clipToExtent && !( context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection ) && !context.extent().contains( poly.boundingRect() ) )
596 {
597 const QgsRectangle e = context.extent();
598 const double cw = e.width() / 10;
599 const double ch = e.height() / 10;
600 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
601 QgsClipper::trimPolygon( poly, clipRect );
602 }
603
604 //transform the QPolygonF to screen coordinates
605 if ( ct.isValid() )
606 {
607 try
608 {
609 ct.transformPolygon( poly );
610 }
611 catch ( QgsCsException & )
612 {
613 // we don't abort the rendering here, instead we remove any invalid points and just plot those which ARE valid
614 }
615 }
616
617 // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
618 poly.erase( std::remove_if( poly.begin(), poly.end(),
619 []( const QPointF point )
620 {
621 return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
622 } ), poly.end() );
623
624 if ( clipToExtent && context.flags() & Qgis::RenderContextFlag::ApplyClipAfterReprojection && !context.mapExtent().contains( poly.boundingRect() ) )
625 {
626 // early clipping was not possible, so we have to apply it here after transformation
627 const QgsRectangle e = context.mapExtent();
628 const double cw = e.width() / 10;
629 const double ch = e.height() / 10;
630 const QgsRectangle clipRect( e.xMinimum() - cw, e.yMinimum() - ch, e.xMaximum() + cw, e.yMaximum() + ch );
631 QgsClipper::trimPolygon( poly, clipRect );
632 }
633
634 QPointF *ptr = poly.data();
635 for ( int i = 0; i < poly.size(); ++i, ++ptr )
636 {
637 mtp.transformInPlace( ptr->rx(), ptr->ry() );
638 }
639
640 if ( !poly.empty() && !poly.isClosed() )
641 poly << poly.at( 0 );
642
643 return poly;
644}
645
646void QgsSymbol::_getPolygon( QPolygonF &pts, QVector<QPolygonF> &holes, QgsRenderContext &context, const QgsPolygon &polygon, const bool clipToExtent, const bool correctRingOrientation )
647{
648 holes.clear();
649
650 pts = _getPolygonRing( context, *polygon.exteriorRing(), clipToExtent, true, correctRingOrientation );
651 const int ringCount = polygon.numInteriorRings();
652 holes.reserve( ringCount );
653 for ( int idx = 0; idx < ringCount; idx++ )
654 {
655 const QPolygonF hole = _getPolygonRing( context, *( polygon.interiorRing( idx ) ), clipToExtent, false, correctRingOrientation );
656 if ( !hole.isEmpty() )
657 holes.append( hole );
658 }
659}
660
662{
663 switch ( type )
664 {
666 return QObject::tr( "Marker" );
668 return QObject::tr( "Line" );
670 return QObject::tr( "Fill" );
672 return QObject::tr( "Hybrid" );
673 }
674 return QString();
675}
676
693
695{
696 QgsSymbol::initPropertyDefinitions();
697 return sPropertyDefinitions;
698}
699
701{
702 // delete all symbol layers (we own them, so it's okay)
703 qDeleteAll( mLayers );
704}
705
707{
708 if ( mLayers.empty() )
709 {
711 }
712
713 QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
714
715 Qgis::RenderUnit unit = ( *it )->outputUnit();
716
717 for ( ; it != mLayers.constEnd(); ++it )
718 {
719 if ( ( *it )->outputUnit() != unit )
720 {
722 }
723 }
724 return unit;
725}
726
728{
729 if ( mLayers.empty() )
730 {
731 return false;
732 }
733
734 for ( const QgsSymbolLayer *layer : mLayers )
735 {
736 if ( layer->usesMapUnits() )
737 {
738 return true;
739 }
740 }
741 return false;
742}
743
745{
746 if ( mLayers.empty() )
747 {
748 return QgsMapUnitScale();
749 }
750
751 QgsSymbolLayerList::const_iterator it = mLayers.constBegin();
752 if ( it == mLayers.constEnd() )
753 return QgsMapUnitScale();
754
755 QgsMapUnitScale scale = ( *it )->mapUnitScale();
756 ++it;
757
758 for ( ; it != mLayers.constEnd(); ++it )
759 {
760 if ( ( *it )->mapUnitScale() != scale )
761 {
762 return QgsMapUnitScale();
763 }
764 }
765 return scale;
766}
767
769{
770 const auto constMLayers = mLayers;
771 for ( QgsSymbolLayer *layer : constMLayers )
772 {
773 layer->setOutputUnit( u );
774 }
775}
776
778{
779 const auto constMLayers = mLayers;
780 for ( QgsSymbolLayer *layer : constMLayers )
781 {
782 layer->setMapUnitScale( scale );
783 }
784}
785
790
792{
793 return mBufferSettings.get();
794}
795
797{
798 if ( mBufferSettings.get() == settings )
799 return;
800 mBufferSettings.reset( settings );
801}
802
807
812
814{
815 mAnimationSettings = settings;
816}
817
819{
820 std::unique_ptr< QgsSymbol > s;
821
822 // override global default if project has a default for this type
823 switch ( geomType )
824 {
826 s.reset( QgsProject::instance()->styleSettings()->defaultSymbol( Qgis::SymbolType::Marker ) ); // skip-keyword-check
827 break;
829 s.reset( QgsProject::instance()->styleSettings()->defaultSymbol( Qgis::SymbolType::Line ) ); // skip-keyword-check
830 break;
832 s.reset( QgsProject::instance()->styleSettings()->defaultSymbol( Qgis::SymbolType::Fill ) ); // skip-keyword-check
833 break;
834 default:
835 break;
836 }
837
838 // if no default found for this type, get global default (as previously)
839 if ( !s )
840 {
841 switch ( geomType )
842 {
844 s = std::make_unique< QgsMarkerSymbol >();
845 break;
847 s = std::make_unique< QgsLineSymbol >();
848 break;
850 s = std::make_unique< QgsFillSymbol >();
851 break;
852 default:
853 QgsDebugError( QStringLiteral( "unknown layer's geometry type" ) );
854 break;
855 }
856 }
857
858 if ( !s )
859 return nullptr;
860
861 // set opacity
862 s->setOpacity( QgsProject::instance()->styleSettings()->defaultSymbolOpacity() ); // skip-keyword-check
863
864 // set random color, it project prefs allow
865 if ( QgsProject::instance()->styleSettings()->randomizeDefaultSymbolColor() ) // skip-keyword-check
866 {
867 s->setColor( QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor() );
868 }
869
870 const bool isCmyk = QgsProject::instance()->styleSettings() && QgsProject::instance()->styleSettings()->colorModel() == Qgis::ColorModel::Cmyk; // skip-keyword-check
871 if ( s->color().spec() == QColor::Spec::Rgb && isCmyk )
872 {
873 s->setColor( s->color().toCmyk() );
874 }
875 else if ( s->color().spec() == QColor::Spec::Cmyk && !isCmyk )
876 {
877 s->setColor( s->color().toRgb() );
878 }
879
880 return s.release();
881}
882
884{
885 return mLayers.value( layer );
886}
887
888const QgsSymbolLayer *QgsSymbol::symbolLayer( int layer ) const
889{
890 return mLayers.value( layer );
891}
892
894{
895 if ( index < 0 || index > mLayers.count() ) // can be added also after the last index
896 return false;
897
898 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
899 return false;
900
901 mLayers.insert( index, layer );
902 return true;
903}
904
905
907{
908 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
909 return false;
910
911 mLayers.append( layer );
912 return true;
913}
914
915
917{
918 if ( index < 0 || index >= mLayers.count() )
919 return false;
920
921 delete mLayers.at( index );
922 mLayers.removeAt( index );
923 return true;
924}
925
926
928{
929 if ( index < 0 || index >= mLayers.count() )
930 return nullptr;
931
932 return mLayers.takeAt( index );
933}
934
935
937{
938 QgsSymbolLayer *oldLayer = mLayers.value( index );
939
940 if ( oldLayer == layer )
941 return false;
942
943 if ( !layer || !layer->isCompatibleWithSymbol( this ) )
944 return false;
945
946 delete oldLayer; // first delete the original layer
947 mLayers[index] = layer; // set new layer
948 return true;
949}
950
951
952void QgsSymbol::startRender( QgsRenderContext &context, const QgsFields &fields )
953{
954 Q_ASSERT_X( !mStarted, "startRender", "Rendering has already been started for this symbol instance!" );
955 mStarted = true;
956
958
959 mSymbolRenderContext.reset( new QgsSymbolRenderContext( context, Qgis::RenderUnit::Unknown, mOpacity, false, renderHints, nullptr, fields ) );
960
961 // Why do we need a copy here ? Is it to make sure the symbol layer rendering does not mess with the symbol render context ?
962 // Or is there another profound reason ?
963 QgsSymbolRenderContext symbolContext( context, Qgis::RenderUnit::Unknown, mOpacity, false, renderHints, nullptr, fields );
964
965 std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::updateSymbolScope( this, new QgsExpressionContextScope() ) );
966
968 {
969 const long long mapFrameNumber = context.currentFrame();
970 double animationTimeSeconds = 0;
971 if ( mapFrameNumber >= 0 && context.frameRate() > 0 )
972 {
973 // render is part of an animation, so we base the calculated frame on that
974 animationTimeSeconds = mapFrameNumber / context.frameRate();
975 }
976 else
977 {
978 // render is outside of animation, so base the calculated frame on the current epoch
979 animationTimeSeconds = QDateTime::currentMSecsSinceEpoch() / 1000.0;
980 }
981
982 const long long symbolFrame = static_cast< long long >( std::floor( animationTimeSeconds * mAnimationSettings.frameRate() ) );
983 scope->setVariable( QStringLiteral( "symbol_frame" ), symbolFrame, true );
984 }
985
986 mSymbolRenderContext->setExpressionContextScope( scope.release() );
987
988 mDataDefinedProperties.prepare( context.expressionContext() );
989
990 if ( mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol() )
991 {
992 mBufferSettings->fillSymbol()->startRender( context, fields );
993 }
994
995 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
996 {
997 if ( !layer->enabled() || !context.isSymbolLayerEnabled( layer ) )
998 continue;
999
1000 layer->prepareExpressions( symbolContext );
1001
1002 // We prepare "entire map" clip masks in advance only in certain circumstances. These are non-optimal,
1003 // because the entire map mask will be applied once for every feature rendered, resulting in overly complex
1004 // clipping paths with paths which fall well outside of the map area that is actually being drawn on for the
1005 // feature. These circumstances are:
1006 // 1. If we are rendering a sub symbol. The current logic relating to calculating per-feature masks
1007 // is not designed to handle sub symbol rendering where layers from the subsymbol have their own set of
1008 // clipping paths, so we just fallback to the non-optimal approach always for these cases.
1009 // TODO:
1010 // - we could add another special condition here to check whether the subsymbol actually does have unique
1011 // clipping paths in its symbol layers, or whether they are identical to the parent symbol layer's clipping paths.
1012 // 2. When the symbol layer type doesn't explicitly state that it's compatible with per-feature mask geometries
1013 // 3. When per feature mask geometry is explicitly disabled for the render context
1014 // In other circumstances we do NOT prepare masks in advance, and instead calculate them in renderFeature().
1018 layer->prepareMasks( symbolContext );
1019 layer->startRender( symbolContext );
1020 }
1021}
1022
1024{
1025 Q_ASSERT_X( mStarted, "startRender", "startRender was not called for this symbol instance!" );
1026 mStarted = false;
1027
1028 Q_UNUSED( context )
1029 if ( mSymbolRenderContext )
1030 {
1031 const auto constMLayers = mLayers;
1032 for ( QgsSymbolLayer *layer : constMLayers )
1033 {
1034 if ( !layer->enabled() || !context.isSymbolLayerEnabled( layer ) )
1035 continue;
1036
1037 layer->stopRender( *mSymbolRenderContext );
1038 }
1039 }
1040
1041 if ( mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol() )
1042 {
1043 mBufferSettings->fillSymbol()->stopRender( context );
1044 }
1045
1046 mSymbolRenderContext.reset( nullptr );
1047
1049 mLayer = nullptr;
1051}
1052
1053void QgsSymbol::setColor( const QColor &color ) const
1054{
1055 const auto constMLayers = mLayers;
1056 for ( QgsSymbolLayer *layer : constMLayers )
1057 {
1058 if ( !layer->isLocked() )
1059 layer->setColor( color );
1060 }
1061}
1062
1063QColor QgsSymbol::color() const
1064{
1065 for ( const QgsSymbolLayer *layer : mLayers )
1066 {
1067 // return color of the first unlocked layer
1068 if ( !layer->isLocked() )
1069 {
1070 const QColor layerColor = layer->color();
1071 if ( layerColor.isValid() )
1072 return layerColor;
1073 }
1074 }
1075 return QColor( 0, 0, 0 );
1076}
1077
1078void QgsSymbol::drawPreviewIcon( QPainter *painter, QSize size, QgsRenderContext *customContext, bool selected, const QgsExpressionContext *expressionContext, const QgsLegendPatchShape *patchShape, const QgsScreenProperties &screen )
1079{
1080 QgsRenderContext *context = customContext;
1081 std::unique_ptr< QgsRenderContext > tempContext;
1082 if ( !context )
1083 {
1084 tempContext.reset( new QgsRenderContext( QgsRenderContext::fromQPainter( painter ) ) );
1085 context = tempContext.get();
1087 }
1088
1089 if ( screen.isValid() )
1090 {
1091 screen.updateRenderContextForScreen( *context );
1092 }
1093
1094 const bool prevForceVector = context->forceVectorOutput();
1095 context->setForceVectorOutput( true );
1096
1097 const double opacity = expressionContext ? dataDefinedProperties().valueAsDouble( QgsSymbol::Property::Opacity, *expressionContext, mOpacity * 100 ) * 0.01 : mOpacity;
1098
1099 QgsSymbolRenderContext symbolContext( *context, Qgis::RenderUnit::Unknown, opacity, false, renderHints(), nullptr );
1100 symbolContext.setSelected( selected );
1101 switch ( mType )
1102 {
1105 break;
1108 break;
1111 break;
1114 break;
1115 }
1116
1117 if ( patchShape )
1118 symbolContext.setPatchShape( *patchShape );
1119
1120 if ( !customContext && expressionContext )
1121 {
1122 context->setExpressionContext( *expressionContext );
1123 }
1124 else if ( !customContext )
1125 {
1126 // if no render context was passed, build a minimal expression context
1127 QgsExpressionContext expContext;
1129 context->setExpressionContext( expContext );
1130 }
1131
1132 const bool usingBuffer = mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol();
1133 // handle symbol buffers -- we do this by deferring the rendering of the symbol and redirecting
1134 // to QPictures, and then using the actual rendered shape from the QPictures to determine the buffer shape.
1135 QPainter *originalTargetPainter = nullptr;
1136 // this is an array, we need to separate out the symbol layers if we're drawing only one symbol level
1137 std::unique_ptr< QPicture > pictureForDeferredRendering;
1138 std::unique_ptr< QPainter > deferredRenderingPainter;
1139 if ( usingBuffer )
1140 {
1141 originalTargetPainter = context->painter();
1142 pictureForDeferredRendering = std::make_unique< QPicture >();
1143 deferredRenderingPainter = std::make_unique< QPainter >( pictureForDeferredRendering.get() );
1144 context->setPainter( deferredRenderingPainter.get() );
1145 }
1146
1147 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
1148 {
1149 if ( !layer->enabled() || ( customContext && !customContext->isSymbolLayerEnabled( layer ) ) )
1150 continue;
1151
1153 {
1154 // line symbol layer would normally draw just a line
1155 // so we override this case to force it to draw a polygon stroke
1156 QgsLineSymbolLayer *lsl = dynamic_cast<QgsLineSymbolLayer *>( layer );
1157 if ( lsl )
1158 {
1159 // from QgsFillSymbolLayer::drawPreviewIcon() -- would be nicer to add the
1160 // symbol type to QgsSymbolLayer::drawPreviewIcon so this logic could be avoided!
1161
1162 // hmm... why was this using size -1 ??
1163 const QSizeF targetSize = QSizeF( size.width() - 1, size.height() - 1 );
1164
1165 const QList< QList< QPolygonF > > polys = patchShape ? patchShape->toQPolygonF( Qgis::SymbolType::Fill, targetSize )
1167
1168 lsl->startRender( symbolContext );
1169 QgsPaintEffect *effect = lsl->paintEffect();
1170
1171 std::unique_ptr< QgsEffectPainter > effectPainter;
1172 if ( effect && effect->enabled() )
1173 effectPainter = std::make_unique< QgsEffectPainter >( symbolContext.renderContext(), effect );
1174
1175 for ( const QList< QPolygonF > &poly : polys )
1176 {
1177 QVector< QPolygonF > rings;
1178 rings.reserve( poly.size() );
1179 for ( int i = 1; i < poly.size(); ++i )
1180 rings << poly.at( i );
1181 lsl->renderPolygonStroke( poly.value( 0 ), &rings, symbolContext );
1182 }
1183
1184 effectPainter.reset();
1185 lsl->stopRender( symbolContext );
1186 }
1187 }
1188 else
1189 layer->drawPreviewIcon( symbolContext, size );
1190 }
1191
1192 // if required, render the calculated buffer below the symbol
1193 if ( usingBuffer )
1194 {
1195 deferredRenderingPainter->end();
1196 deferredRenderingPainter.reset();
1197
1198 QgsGeometryPaintDevice geometryPaintDevice;
1199 QPainter geometryPainter( &geometryPaintDevice );
1200 QgsPainting::drawPicture( &geometryPainter, QPointF( 0, 0 ), *pictureForDeferredRendering );
1201 geometryPainter.end();
1202
1203 // retrieve the shape of the rendered symbol
1204 const QgsGeometry renderedShape( geometryPaintDevice.geometry().clone() );
1205
1206 context->setPainter( originalTargetPainter );
1207
1208 // next, buffer out the rendered shape, and draw!
1209 const double bufferSize = context->convertToPainterUnits( mBufferSettings->size(), mBufferSettings->sizeUnit(), mBufferSettings->sizeMapUnitScale() );
1211 switch ( mBufferSettings->joinStyle() )
1212 {
1213 case Qt::MiterJoin:
1214 case Qt::SvgMiterJoin:
1215 joinStyle = Qgis::JoinStyle::Miter;
1216 break;
1217 case Qt::BevelJoin:
1218 joinStyle = Qgis::JoinStyle::Bevel;
1219 break;
1220 case Qt::RoundJoin:
1221 joinStyle = Qgis::JoinStyle::Round;
1222 break;
1223
1224 case Qt::MPenJoinStyle:
1225 break;
1226 }
1227
1228 const QgsGeometry bufferedGeometry = renderedShape.buffer( bufferSize, 8, Qgis::EndCapStyle::Round, joinStyle, 2 );
1229 const QList<QList<QPolygonF> > polygons = QgsSymbolLayerUtils::toQPolygonF( bufferedGeometry, Qgis::SymbolType::Fill );
1230
1231 mBufferSettings->fillSymbol()->startRender( *context );
1232 for ( const QList< QPolygonF > &polygon : polygons )
1233 {
1234 QVector< QPolygonF > rings;
1235 for ( int i = 1; i < polygon.size(); ++i )
1236 rings << polygon.at( i );
1237 mBufferSettings->fillSymbol()->renderPolygon( polygon.value( 0 ), &rings, nullptr, *context );
1238 }
1239 mBufferSettings->fillSymbol()->stopRender( *context );
1240
1241 // finally, draw the actual rendered symbol on top
1242 QgsPainting::drawPicture( context->painter(), QPointF( 0, 0 ), *pictureForDeferredRendering );
1243 }
1244
1245 context->setForceVectorOutput( prevForceVector );
1246}
1247
1248void QgsSymbol::exportImage( const QString &path, const QString &format, QSize size )
1249{
1250 if ( format.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
1251 {
1252 QSvgGenerator generator;
1253 generator.setFileName( path );
1254 generator.setSize( size );
1255 generator.setViewBox( QRect( 0, 0, size.width(), size.height() ) );
1256
1257 QPainter painter( &generator );
1258 drawPreviewIcon( &painter, size );
1259 painter.end();
1260 }
1261 else
1262 {
1263 QImage image = asImage( size );
1264 image.save( path );
1265 }
1266}
1267
1268QImage QgsSymbol::asImage( QSize size, QgsRenderContext *customContext )
1269{
1270 QImage image( size, QImage::Format_ARGB32_Premultiplied );
1271 image.fill( 0 );
1272
1273 QPainter p( &image );
1274 p.setRenderHint( QPainter::Antialiasing );
1275 p.setRenderHint( QPainter::SmoothPixmapTransform );
1276
1277 drawPreviewIcon( &p, size, customContext );
1278
1279 return image;
1280}
1281
1282
1284{
1285 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
1286 QImage preview( QSize( 100, 100 ) * devicePixelRatio, QImage::Format_ARGB32_Premultiplied );
1287 preview.fill( 0 );
1288 preview.setDevicePixelRatio( devicePixelRatio );
1289
1290 QPainter p( &preview );
1291 p.setRenderHint( QPainter::Antialiasing );
1292 p.translate( 0.5, 0.5 ); // shift by half a pixel to avoid blurring due antialiasing
1293
1295 {
1296 p.setPen( QPen( Qt::gray ) );
1297 p.drawLine( QLineF( 0, 50, 100, 50 ) );
1298 p.drawLine( QLineF( 50, 0, 50, 100 ) );
1299 }
1300
1305 context.setPainterFlagsUsingContext( &p );
1306
1307 if ( screen.isValid() )
1308 {
1309 screen.updateRenderContextForScreen( context );
1310 }
1311
1312 if ( expressionContext )
1313 context.setExpressionContext( *expressionContext );
1314
1315 context.setIsGuiPreview( true );
1316 startRender( context );
1317
1319 {
1320 QPolygonF poly;
1321 poly << QPointF( 0, 50 ) << QPointF( 99, 50 );
1322 static_cast<QgsLineSymbol *>( this )->renderPolyline( poly, nullptr, context );
1323 }
1324 else if ( mType == Qgis::SymbolType::Fill )
1325 {
1326 QPolygonF polygon;
1327 polygon << QPointF( 20, 20 ) << QPointF( 80, 20 ) << QPointF( 80, 80 ) << QPointF( 20, 80 ) << QPointF( 20, 20 );
1328 static_cast<QgsFillSymbol *>( this )->renderPolygon( polygon, nullptr, nullptr, context );
1329 }
1330 else // marker
1331 {
1332 static_cast<QgsMarkerSymbol *>( this )->renderPoint( QPointF( 50, 50 ), nullptr, context );
1333 }
1334
1335 stopRender( context );
1336 return preview;
1337}
1338
1339QImage QgsSymbol::bigSymbolPreviewImage( QgsExpressionContext *expressionContext, int flags )
1340{
1341 return bigSymbolPreviewImage( expressionContext, static_cast< Qgis::SymbolPreviewFlags >( flags ) );
1342}
1343
1344QString QgsSymbol::dump() const
1345{
1346 QString t;
1347 switch ( type() )
1348 {
1350 t = QStringLiteral( "MARKER" );
1351 break;
1353 t = QStringLiteral( "LINE" );
1354 break;
1356 t = QStringLiteral( "FILL" );
1357 break;
1358 default:
1359 Q_ASSERT( false && "unknown symbol type" );
1360 }
1361 QString s = QStringLiteral( "%1 SYMBOL (%2 layers) color %3" ).arg( t ).arg( mLayers.count() ).arg( QgsColorUtils::colorToString( color() ) );
1362
1363 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
1364 {
1365 // TODO:
1366 }
1367 return s;
1368}
1369
1370void QgsSymbol::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
1371{
1372 QgsSldExportContext context;
1373 context.setExtraProperties( props );
1374 toSld( doc, element, context );
1375}
1376
1377bool QgsSymbol::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
1378{
1379 const QVariantMap oldProps = context.extraProperties();
1380 QVariantMap props = oldProps;
1381 //TODO move these to proper getters/setters in QgsSldExportContext
1382 props[ QStringLiteral( "alpha" )] = QString::number( opacity() );
1383 double scaleFactor = 1.0;
1384 props[ QStringLiteral( "uom" )] = QgsSymbolLayerUtils::encodeSldUom( outputUnit(), &scaleFactor );
1385 props[ QStringLiteral( "uomScale" )] = ( !qgsDoubleNear( scaleFactor, 1.0 ) ? qgsDoubleToString( scaleFactor ) : QString() );
1386
1387 context.setExtraProperties( props );
1388 bool result = true;
1389 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
1390 {
1391 if ( !( *it )->toSld( doc, element, context ) )
1392 result = false;
1393 }
1394 context.setExtraProperties( oldProps );
1395 return result;
1396}
1397
1399{
1401 for ( QgsSymbolLayerList::const_iterator it = mLayers.begin(); it != mLayers.end(); ++it )
1402 {
1403 QgsSymbolLayer *layer = ( *it )->clone();
1404 layer->setLocked( ( *it )->isLocked() );
1405 layer->setRenderingPass( ( *it )->renderingPass() );
1406 layer->setEnabled( ( *it )->enabled() );
1407 layer->setId( ( *it )->id() );
1408 layer->setUserFlags( ( *it )->userFlags() );
1409 lst.append( layer );
1410 }
1411 return lst;
1412}
1413
1414void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext &context, Qgis::GeometryType geometryType, const QPolygonF *points, const QVector<QPolygonF> *rings )
1415{
1416 Q_ASSERT( layer->type() == Qgis::SymbolType::Hybrid );
1417
1418 if ( layer->dataDefinedProperties().hasActiveProperties() && !layer->dataDefinedProperties().valueAsBool( QgsSymbolLayer::Property::LayerEnabled, context.renderContext().expressionContext(), true ) )
1419 return;
1420
1421 QgsGeometryGeneratorSymbolLayer *generatorLayer = static_cast<QgsGeometryGeneratorSymbolLayer *>( layer );
1422
1423 QgsPaintEffect *effect = generatorLayer->paintEffect();
1424 if ( effect && effect->enabled() )
1425 {
1426 QgsEffectPainter p( context.renderContext(), effect );
1427 generatorLayer->render( context, geometryType, points, rings );
1428 }
1429 else
1430 {
1431 generatorLayer->render( context, geometryType, points, rings );
1432 }
1433}
1434
1435QSet<QString> QgsSymbol::usedAttributes( const QgsRenderContext &context ) const
1436{
1437 // calling referencedFields() with ignoreContext=true because in our expression context
1438 // we do not have valid QgsFields yet - because of that the field names from expressions
1439 // wouldn't get reported
1440 QSet<QString> attributes = mDataDefinedProperties.referencedFields( context.expressionContext(), true );
1441 QgsSymbolLayerList::const_iterator sIt = mLayers.constBegin();
1442 for ( ; sIt != mLayers.constEnd(); ++sIt )
1443 {
1444 if ( *sIt )
1445 {
1446 attributes.unite( ( *sIt )->usedAttributes( context ) );
1447 }
1448 }
1449 if ( mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol() )
1450 {
1451 attributes.unite( mBufferSettings->fillSymbol()->usedAttributes( context ) );
1452 }
1453 return attributes;
1454}
1455
1457{
1458 mDataDefinedProperties.setProperty( key, property );
1459}
1460
1462{
1463 if ( mDataDefinedProperties.hasActiveProperties() )
1464 return true;
1465
1466 for ( QgsSymbolLayer *layer : mLayers )
1467 {
1468 if ( layer->hasDataDefinedProperties() )
1469 return true;
1470 }
1471 return false;
1472}
1473
1475{
1476 for ( QgsSymbolLayer *layer : mLayers )
1477 {
1478 if ( layer->canCauseArtifactsBetweenAdjacentTiles() )
1479 return true;
1480 }
1481 return false;
1482}
1483
1490
1497
1499
1503class ExpressionContextScopePopper
1504{
1505 public:
1506
1507 ExpressionContextScopePopper() = default;
1508
1509 ~ExpressionContextScopePopper()
1510 {
1511 if ( context )
1512 context->popScope();
1513 }
1514
1515 QgsExpressionContext *context = nullptr;
1516};
1517
1521class GeometryRestorer
1522{
1523 public:
1524 GeometryRestorer( QgsRenderContext &context )
1525 : mContext( context ),
1526 mGeometry( context.geometry() )
1527 {}
1528
1529 ~GeometryRestorer()
1530 {
1531 mContext.setGeometry( mGeometry );
1532 }
1533
1534 private:
1535 QgsRenderContext &mContext;
1536 const QgsAbstractGeometry *mGeometry;
1537};
1539
1540void QgsSymbol::renderFeature( const QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
1541{
1542 if ( context.renderingStopped() )
1543 return;
1544
1545 const QgsGeometry geom = feature.geometry();
1546 if ( geom.isNull() )
1547 {
1548 return;
1549 }
1550
1551 GeometryRestorer geomRestorer( context );
1552
1553 bool usingSegmentizedGeometry = false;
1554 context.setGeometry( geom.constGet() );
1555
1556 if ( geom.type() != Qgis::GeometryType::Point && !geom.boundingBox().isNull() )
1557 {
1558 try
1559 {
1560 const QPointF boundsOrigin = _getPoint( context, QgsPoint( geom.boundingBox().xMinimum(), geom.boundingBox().yMinimum() ) );
1561 if ( std::isfinite( boundsOrigin.x() ) && std::isfinite( boundsOrigin.y() ) )
1562 context.setTextureOrigin( boundsOrigin );
1563 }
1564 catch ( QgsCsException & )
1565 {
1566
1567 }
1568 }
1569
1570 bool clippingEnabled = clipFeaturesToExtent();
1571 // do any symbol layers prevent feature clipping?
1572 for ( QgsSymbolLayer *layer : std::as_const( mLayers ) )
1573 {
1575 {
1576 clippingEnabled = false;
1577 break;
1578 }
1579 }
1581 if ( clippingEnabled && context.testFlag( Qgis::RenderContextFlag::RenderMapTile ) )
1582 {
1583 // If the "avoid artifacts between adjacent tiles" flag is set (RenderMapTile), then we'll force disable
1584 // the geometry clipping IF (and only if) this symbol can potentially have rendering artifacts when rendered as map tiles.
1585 // If the symbol won't have any artifacts anyway, then it's pointless and incredibly expensive to skip the clipping!
1587 {
1588 clippingEnabled = false;
1589 }
1590 }
1591 if ( context.extent().isEmpty() )
1592 clippingEnabled = false;
1593
1594 mSymbolRenderContext->setGeometryPartCount( geom.constGet()->partCount() );
1595 mSymbolRenderContext->setGeometryPartNum( 1 );
1596
1597 const bool needsExpressionContext = hasDataDefinedProperties();
1598 ExpressionContextScopePopper scopePopper;
1599 if ( mSymbolRenderContext->expressionContextScope() )
1600 {
1601 if ( needsExpressionContext )
1602 {
1603 // this is somewhat nasty - by appending this scope here it's now owned
1604 // by both mSymbolRenderContext AND context.expressionContext()
1605 // the RAII scopePopper is required to make sure it always has ownership transferred back
1606 // from context.expressionContext(), even if exceptions of other early exits occur in this
1607 // function
1608 context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
1609 scopePopper.context = &context.expressionContext();
1610
1611 QgsExpressionContextUtils::updateSymbolScope( this, mSymbolRenderContext->expressionContextScope() );
1612 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, mSymbolRenderContext->geometryPartCount(), true ) );
1613 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, 1, true ) );
1614 }
1615 }
1616
1617 // Collection of markers to paint, only used for no curve types.
1618 QPolygonF markers;
1619
1620 QgsGeometry renderedBoundsGeom;
1621
1622 // Step 1 - collect the set of painter coordinate geometries to render.
1623 // We do this upfront, because we only want to ever do this once, regardless how many symbol layers we need to render.
1624
1625 struct PointInfo
1626 {
1627 QPointF renderPoint;
1628 const QgsPoint *originalGeometry = nullptr;
1629 };
1630 QVector< PointInfo > pointsToRender;
1631
1632 struct LineInfo
1633 {
1634 QPolygonF renderLine;
1635 const QgsCurve *originalGeometry = nullptr;
1636 };
1637 QVector< LineInfo > linesToRender;
1638
1639 struct PolygonInfo
1640 {
1641 QPolygonF renderExterior;
1642 QVector< QPolygonF > renderRings;
1643 const QgsCurvePolygon *originalGeometry = nullptr;
1644 int originalPartIndex = 0;
1645 };
1646 QVector< PolygonInfo > polygonsToRender;
1647
1648 std::function< void ( const QgsAbstractGeometry *, int partIndex )> getPartGeometry;
1649 getPartGeometry = [&pointsToRender, &linesToRender, &polygonsToRender, &getPartGeometry, &context, &clippingEnabled, &markers, &feature, &usingSegmentizedGeometry, this]( const QgsAbstractGeometry * part, int partIndex = 0 )
1650 {
1651 Q_UNUSED( feature )
1652
1653 if ( !part )
1654 return;
1655
1656 // geometry preprocessing
1657 QgsGeometry temporaryGeometryContainer;
1658 const QgsAbstractGeometry *processedGeometry = nullptr;
1659
1660 const bool isMultiPart = qgsgeometry_cast< const QgsGeometryCollection * >( part ) && qgsgeometry_cast< const QgsGeometryCollection * >( part )->numGeometries() > 1;
1661
1662 if ( !isMultiPart )
1663 {
1664 // segmentize curved geometries
1665 const bool needsSegmentizing = QgsWkbTypes::isCurvedType( part->wkbType() ) || part->hasCurvedSegments();
1666 if ( needsSegmentizing )
1667 {
1668 std::unique_ptr< QgsAbstractGeometry > segmentizedPart( part->segmentize( context.segmentationTolerance(), context.segmentationToleranceType() ) );
1669 if ( !segmentizedPart )
1670 {
1671 return;
1672 }
1673 temporaryGeometryContainer.set( segmentizedPart.release() );
1674 processedGeometry = temporaryGeometryContainer.constGet();
1675 usingSegmentizedGeometry = true;
1676 }
1677 else
1678 {
1679 // no segmentation required
1680 processedGeometry = part;
1681 }
1682
1683 // Simplify the geometry, if needed.
1685 {
1686 const int simplifyHints = context.vectorSimplifyMethod().simplifyHints();
1687 const QgsMapToPixelSimplifier simplifier( simplifyHints, context.vectorSimplifyMethod().tolerance(),
1689
1690 std::unique_ptr< QgsAbstractGeometry > simplified( simplifier.simplify( processedGeometry ) );
1691 if ( simplified )
1692 {
1693 temporaryGeometryContainer.set( simplified.release() );
1694 processedGeometry = temporaryGeometryContainer.constGet();
1695 }
1696 }
1697
1698 // clip geometry to render context clipping regions
1699 if ( !context.featureClipGeometry().isEmpty() )
1700 {
1701 // apply feature clipping from context to the rendered geometry only -- just like the render time simplification,
1702 // we should NEVER apply this to the geometry attached to the feature itself. Doing so causes issues with certain
1703 // renderer settings, e.g. if polygons are being rendered using a rule based renderer based on the feature's area,
1704 // then we need to ensure that the original feature area is used instead of the clipped area..
1705 QgsGeos geos( processedGeometry );
1706 std::unique_ptr< QgsAbstractGeometry > clippedGeom( geos.intersection( context.featureClipGeometry().constGet() ) );
1707 if ( clippedGeom )
1708 {
1709 temporaryGeometryContainer.set( clippedGeom.release() );
1710 processedGeometry = temporaryGeometryContainer.constGet();
1711 }
1712 }
1713 }
1714 else
1715 {
1716 // for multipart geometries, the processing is deferred till we're rendering the actual part...
1717 processedGeometry = part;
1718 }
1719
1720 if ( !processedGeometry )
1721 {
1722 // shouldn't happen!
1723 QgsDebugError( QStringLiteral( "No processed geometry to render for part!" ) );
1724 return;
1725 }
1726
1727 switch ( QgsWkbTypes::flatType( processedGeometry->wkbType() ) )
1728 {
1730 {
1732 {
1733 QgsDebugMsgLevel( QStringLiteral( "point can be drawn only with marker symbol!" ), 2 );
1734 break;
1735 }
1736
1737 PointInfo info;
1738 info.originalGeometry = qgsgeometry_cast< const QgsPoint * >( part );
1739 info.renderPoint = _getPoint( context, *info.originalGeometry );
1740 pointsToRender << info;
1741 break;
1742 }
1743
1745 {
1747 {
1748 QgsDebugMsgLevel( QStringLiteral( "linestring can be drawn only with line symbol!" ), 2 );
1749 break;
1750 }
1751
1752 LineInfo info;
1753 info.originalGeometry = qgsgeometry_cast<const QgsCurve *>( part );
1754 info.renderLine = _getLineString( context, *qgsgeometry_cast<const QgsCurve *>( processedGeometry ), clippingEnabled );
1755 linesToRender << info;
1756 break;
1757 }
1758
1761 {
1762 QPolygonF pts;
1764 {
1765 QgsDebugMsgLevel( QStringLiteral( "polygon can be drawn only with fill symbol!" ), 2 );
1766 break;
1767 }
1768
1769 PolygonInfo info;
1770 info.originalGeometry = qgsgeometry_cast<const QgsCurvePolygon *>( part );
1771 info.originalPartIndex = partIndex;
1772 if ( !qgsgeometry_cast<const QgsPolygon *>( processedGeometry )->exteriorRing() )
1773 {
1774 QgsDebugError( QStringLiteral( "cannot render polygon with no exterior ring" ) );
1775 break;
1776 }
1777
1778 _getPolygon( info.renderExterior, info.renderRings, context, *qgsgeometry_cast<const QgsPolygon *>( processedGeometry ), clippingEnabled, mForceRHR );
1779 polygonsToRender << info;
1780 break;
1781 }
1782
1784 {
1785 const QgsMultiPoint *mp = qgsgeometry_cast< const QgsMultiPoint * >( processedGeometry );
1786 markers.reserve( mp->numGeometries() );
1787 }
1788 [[fallthrough]];
1792 {
1793 const QgsGeometryCollection *geomCollection = qgsgeometry_cast<const QgsGeometryCollection *>( processedGeometry );
1794
1795 const unsigned int num = geomCollection->numGeometries();
1796 for ( unsigned int i = 0; i < num; ++i )
1797 {
1798 if ( context.renderingStopped() )
1799 break;
1800
1801 getPartGeometry( geomCollection->geometryN( i ), i );
1802 }
1803 break;
1804 }
1805
1808 {
1810 {
1811 QgsDebugMsgLevel( QStringLiteral( "multi-polygon can be drawn only with fill symbol!" ), 2 );
1812 break;
1813 }
1814
1815 QPolygonF pts;
1816
1817 const QgsGeometryCollection *geomCollection = dynamic_cast<const QgsGeometryCollection *>( processedGeometry );
1818 const unsigned int num = geomCollection->numGeometries();
1819
1820 // Sort components by approximate area (probably a bit faster than using
1821 // area() )
1822 std::map<double, QList<unsigned int> > thisAreaToPartNum;
1823 for ( unsigned int i = 0; i < num; ++i )
1824 {
1825 const QgsRectangle r( geomCollection->geometryN( i )->boundingBox() );
1826 thisAreaToPartNum[ r.width() * r.height()] << i;
1827 }
1828
1829 // Draw starting with larger parts down to smaller parts, so that in
1830 // case of a part being incorrectly inside another part, it is drawn
1831 // on top of it (#15419)
1832 std::map<double, QList<unsigned int> >::const_reverse_iterator iter = thisAreaToPartNum.rbegin();
1833 for ( ; iter != thisAreaToPartNum.rend(); ++iter )
1834 {
1835 const QList<unsigned int> &listPartIndex = iter->second;
1836 for ( int idx = 0; idx < listPartIndex.size(); ++idx )
1837 {
1838 const unsigned i = listPartIndex[idx];
1839 getPartGeometry( geomCollection->geometryN( i ), i );
1840 }
1841 }
1842 break;
1843 }
1844
1846 case Qgis::WkbType::TIN:
1847 {
1848 const QgsPolyhedralSurface *polySurface = qgsgeometry_cast<const QgsPolyhedralSurface *>( processedGeometry );
1849
1850 const int num = polySurface->numPatches();
1851 for ( int i = 0; i < num; ++i )
1852 {
1853 if ( context.renderingStopped() )
1854 break;
1855
1856 getPartGeometry( polySurface->patchN( i ), i );
1857 }
1858 break;
1859 }
1860
1861 default:
1862 QgsDebugError( QStringLiteral( "feature %1: unsupported wkb type %2/%3 for rendering" )
1863 .arg( feature.id() )
1864 .arg( QgsWkbTypes::displayString( part->wkbType() ) )
1865 .arg( static_cast< quint32>( part->wkbType() ), 0, 16 ) );
1866 }
1867 };
1868
1869 // Use the simplified type ref when rendering -- this avoids some unnecessary cloning/geometry modification
1870 // (e.g. if the original geometry is a compound curve containing only a linestring curve, we don't have
1871 // to segmentize the geometry before rendering)
1872 getPartGeometry( geom.constGet()->simplifiedTypeRef(), 0 );
1873
1874 // If we're drawing using symbol levels, we only draw buffers for the bottom most level
1875 const bool usingBuffer = ( layer == -1 || layer == 0 ) && mBufferSettings && mBufferSettings->enabled() && mBufferSettings->fillSymbol();
1876
1877 // step 2 - determine which layers to render
1878 std::vector< int > layerToRender;
1879 if ( layer == -1 )
1880 {
1881 layerToRender.reserve( mLayers.count() );
1882 for ( int i = 0; i < mLayers.count(); ++i )
1883 layerToRender.emplace_back( i );
1884 }
1885 else
1886 {
1887 // if we're rendering using a buffer, then we'll need to draw ALL symbol layers in order to calculate the
1888 // buffer shape, but then ultimately we'll ONLY draw the target layer on top.
1889 if ( usingBuffer )
1890 {
1891 layerToRender.reserve( mLayers.count() );
1892 for ( int i = 0; i < mLayers.count(); ++i )
1893 layerToRender.emplace_back( i );
1894 }
1895 else
1896 {
1897 layerToRender.emplace_back( layer );
1898 }
1899 }
1900
1901 // step 3 - render these geometries using the desired symbol layers.
1902
1903 if ( needsExpressionContext )
1904 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_count" ), mLayers.count(), true ) );
1905
1906 const bool maskGeometriesDisabledForSymbol = context.testFlag( Qgis::RenderContextFlag::AlwaysUseGlobalMasks )
1908
1909 // handle symbol buffers -- we do this by deferring the rendering of the symbol and redirecting
1910 // to QPictures, and then using the actual rendered shape from the QPictures to determine the buffer shape.
1911 QPainter *originalTargetPainter = nullptr;
1912 // this is an array, we need to separate out the symbol layers if we're drawing only one symbol level
1913 std::vector< QPicture > picturesForDeferredRendering;
1914 std::unique_ptr< QPainter > deferredRenderingPainter;
1915 if ( usingBuffer )
1916 {
1917 originalTargetPainter = context.painter();
1918 picturesForDeferredRendering.emplace_back( QPicture() );
1919 deferredRenderingPainter = std::make_unique< QPainter >( &picturesForDeferredRendering.front() );
1920 context.setPainter( deferredRenderingPainter.get() );
1921 }
1922
1923 const bool prevExcludeBuffers = mSymbolRenderContext->renderHints().testFlag( Qgis::SymbolRenderHint::ExcludeSymbolBuffers );
1924 // disable buffers when calling subclass render methods -- we've already handled them here
1925 mSymbolRenderContext->setRenderHint( Qgis::SymbolRenderHint::ExcludeSymbolBuffers, true );
1926
1927 for ( const int symbolLayerIndex : layerToRender )
1928 {
1929 if ( deferredRenderingPainter && layer != -1 && symbolLayerIndex != layerToRender.front() )
1930 {
1931 // if we're using deferred rendering along with symbol level drawing, we
1932 // start a new picture for each symbol layer drawn
1933 deferredRenderingPainter->end();
1934 picturesForDeferredRendering.emplace_back( QPicture() );
1935 deferredRenderingPainter->begin( &picturesForDeferredRendering.back() );
1936 }
1937
1938 QgsSymbolLayer *symbolLayer = mLayers.value( symbolLayerIndex );
1939 if ( !symbolLayer || !symbolLayer->enabled() )
1940 continue;
1941
1942 if ( needsExpressionContext )
1943 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_index" ), symbolLayerIndex + 1, true ) );
1944
1945 // if this symbol layer has associated clip masks, we need to render it to a QPicture first so that we can
1946 // determine the actual rendered bounds of the symbol. We'll then use that to retrieve the clip masks we need
1947 // to apply when painting the symbol via this QPicture.
1948 const bool hasClipGeometries = !maskGeometriesDisabledForSymbol
1951 QPainter *previousPainter = nullptr;
1952 std::unique_ptr< QPicture > renderedPicture;
1953 std::unique_ptr< QPainter > picturePainter;
1954 if ( hasClipGeometries )
1955 {
1956 previousPainter = context.painter();
1957 renderedPicture = std::make_unique< QPicture >();
1958 picturePainter = std::make_unique< QPainter >( renderedPicture.get() );
1959 context.setPainter( picturePainter.get() );
1960 }
1961
1962 symbolLayer->startFeatureRender( feature, context );
1963
1964 switch ( mType )
1965 {
1967 {
1968 int geometryPartNumber = 0;
1969 for ( const PointInfo &point : std::as_const( pointsToRender ) )
1970 {
1971 if ( context.renderingStopped() )
1972 break;
1973
1974 mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1975 if ( needsExpressionContext )
1976 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1977
1978 static_cast<QgsMarkerSymbol *>( this )->renderPoint( point.renderPoint, &feature, context, symbolLayerIndex, selected );
1979 geometryPartNumber++;
1980 }
1981
1982 break;
1983 }
1984
1986 {
1987 if ( linesToRender.empty() )
1988 break;
1989
1990 int geometryPartNumber = 0;
1991 for ( const LineInfo &line : std::as_const( linesToRender ) )
1992 {
1993 if ( context.renderingStopped() )
1994 break;
1995
1996 mSymbolRenderContext->setGeometryPartNum( geometryPartNumber + 1 );
1997 if ( needsExpressionContext )
1998 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, geometryPartNumber + 1, true ) );
1999
2000 context.setGeometry( line.originalGeometry );
2001 static_cast<QgsLineSymbol *>( this )->renderPolyline( line.renderLine, &feature, context, symbolLayerIndex, selected );
2002 geometryPartNumber++;
2003 }
2004 break;
2005 }
2006
2008 {
2009 for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
2010 {
2011 if ( context.renderingStopped() )
2012 break;
2013
2014 mSymbolRenderContext->setGeometryPartNum( info.originalPartIndex + 1 );
2015 if ( needsExpressionContext )
2016 mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, info.originalPartIndex + 1, true ) );
2017
2018 context.setGeometry( info.originalGeometry );
2019 static_cast<QgsFillSymbol *>( this )->renderPolygon( info.renderExterior, ( !info.renderRings.isEmpty() ? &info.renderRings : nullptr ), &feature, context, symbolLayerIndex, selected );
2020 }
2021
2022 break;
2023 }
2024
2026 break;
2027 }
2028
2029 symbolLayer->stopFeatureRender( feature, context );
2030
2031 if ( hasClipGeometries )
2032 {
2033 // restore previous painter
2034 context.setPainter( previousPainter );
2035 picturePainter->end();
2036 picturePainter.reset();
2037
2038 // determine actual rendered bounds of symbol layer, and then buffer out a little to be safe
2039 QRectF maximalBounds = renderedPicture->boundingRect();
2040 constexpr double BOUNDS_MARGIN = 0.05;
2041 maximalBounds.adjust( -maximalBounds.width() * BOUNDS_MARGIN, -maximalBounds.height() * BOUNDS_MARGIN, maximalBounds.width() * BOUNDS_MARGIN, maximalBounds.height() * BOUNDS_MARGIN );
2042
2043 const bool hadClipping = context.painter()->hasClipping();
2044 const QPainterPath oldClipPath = hadClipping ? context.painter()->clipPath() : QPainterPath();
2045
2046 const bool isMasked = symbolLayer->installMasks( context, false, maximalBounds );
2047
2048 context.painter()->drawPicture( QPointF( 0, 0 ), *renderedPicture );
2049
2050 if ( isMasked )
2051 {
2052 context.painter()->setClipPath( oldClipPath );
2053 context.painter()->setClipping( hadClipping );
2054 }
2055 }
2056 }
2057
2058 // step 4 - if required, render the calculated buffer below the symbol
2059 if ( usingBuffer )
2060 {
2061 deferredRenderingPainter->end();
2062 deferredRenderingPainter.reset();
2063
2064 QgsGeometryPaintDevice geometryPaintDevice;
2065 QPainter geometryPainter( &geometryPaintDevice );
2066 // render all the symbol layers onto the geometry painter, so we can calculate a single
2067 // buffer for ALL of them
2068 for ( const auto &deferredPicture : picturesForDeferredRendering )
2069 {
2070 QgsPainting::drawPicture( &geometryPainter, QPointF( 0, 0 ), deferredPicture );
2071 }
2072 geometryPainter.end();
2073
2074 // retrieve the shape of the rendered symbol
2075 const QgsGeometry renderedShape( geometryPaintDevice.geometry().clone() );
2076
2077 context.setPainter( originalTargetPainter );
2078
2079 // next, buffer out the rendered shape, and draw!
2080 const double bufferSize = context.convertToPainterUnits( mBufferSettings->size(), mBufferSettings->sizeUnit(), mBufferSettings->sizeMapUnitScale() );
2082 switch ( mBufferSettings->joinStyle() )
2083 {
2084 case Qt::MiterJoin:
2085 case Qt::SvgMiterJoin:
2086 joinStyle = Qgis::JoinStyle::Miter;
2087 break;
2088 case Qt::BevelJoin:
2089 joinStyle = Qgis::JoinStyle::Bevel;
2090 break;
2091 case Qt::RoundJoin:
2092 joinStyle = Qgis::JoinStyle::Round;
2093 break;
2094
2095 case Qt::MPenJoinStyle:
2096 break;
2097 }
2098
2099 const QgsGeometry bufferedGeometry = renderedShape.buffer( bufferSize, 8, Qgis::EndCapStyle::Round, joinStyle, 2 );
2100 const QList<QList<QPolygonF> > polygons = QgsSymbolLayerUtils::toQPolygonF( bufferedGeometry, Qgis::SymbolType::Fill );
2101 for ( const QList< QPolygonF > &polygon : polygons )
2102 {
2103 QVector< QPolygonF > rings;
2104 for ( int i = 1; i < polygon.size(); ++i )
2105 rings << polygon.at( i );
2106 mBufferSettings->fillSymbol()->renderPolygon( polygon.value( 0 ), &rings, nullptr, context );
2107 }
2108
2109 // finally, draw the actual rendered symbol on top. If symbol levels are at play then this will ONLY
2110 // be the target symbol level, not all of them.
2111 QgsPainting::drawPicture( context.painter(), QPointF( 0, 0 ), picturesForDeferredRendering.front() );
2112 }
2113
2114 // step 5 - handle post processing steps
2115 switch ( mType )
2116 {
2118 {
2119 markers.reserve( pointsToRender.size() );
2120 for ( const PointInfo &info : std::as_const( pointsToRender ) )
2121 {
2123 {
2124 const QRectF bounds = static_cast<QgsMarkerSymbol *>( this )->bounds( info.renderPoint, context, feature );
2125 if ( context.hasRenderedFeatureHandlers() )
2126 {
2127 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromRect( bounds )
2128 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromRect( QgsRectangle( bounds ) ) << renderedBoundsGeom );
2129 }
2131 {
2132 //draw debugging rect
2133 context.painter()->setPen( Qt::red );
2134 context.painter()->setBrush( QColor( 255, 0, 0, 100 ) );
2135 context.painter()->drawRect( bounds );
2136 }
2137 }
2138
2139 if ( drawVertexMarker && !usingSegmentizedGeometry )
2140 {
2141 markers.append( info.renderPoint );
2142 }
2143 }
2144 break;
2145 }
2146
2148 {
2149 for ( const LineInfo &info : std::as_const( linesToRender ) )
2150 {
2151 if ( context.hasRenderedFeatureHandlers() && !info.renderLine.empty() )
2152 {
2153 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderLine )
2154 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderLine ) << renderedBoundsGeom );
2155 }
2156
2157 if ( drawVertexMarker && !usingSegmentizedGeometry )
2158 {
2159 markers << info.renderLine;
2160 }
2161 }
2162 break;
2163 }
2164
2166 {
2167 for ( const PolygonInfo &info : std::as_const( polygonsToRender ) )
2168 {
2169 if ( context.hasRenderedFeatureHandlers() && !info.renderExterior.empty() )
2170 {
2171 renderedBoundsGeom = renderedBoundsGeom.isNull() ? QgsGeometry::fromQPolygonF( info.renderExterior )
2172 : QgsGeometry::collectGeometry( QVector< QgsGeometry>() << QgsGeometry::fromQPolygonF( info.renderExterior ) << renderedBoundsGeom );
2173 // TODO: consider holes?
2174 }
2175
2176 if ( drawVertexMarker && !usingSegmentizedGeometry )
2177 {
2178 markers << info.renderExterior;
2179
2180 for ( const QPolygonF &hole : info.renderRings )
2181 {
2182 markers << hole;
2183 }
2184 }
2185 }
2186 break;
2187 }
2188
2190 break;
2191 }
2192
2193 mSymbolRenderContext->setRenderHint( Qgis::SymbolRenderHint::ExcludeSymbolBuffers, prevExcludeBuffers );
2194
2195 if ( context.hasRenderedFeatureHandlers() && !renderedBoundsGeom.isNull() )
2196 {
2198 const QList< QgsRenderedFeatureHandlerInterface * > handlers = context.renderedFeatureHandlers();
2199 for ( QgsRenderedFeatureHandlerInterface *handler : handlers )
2200 handler->handleRenderedFeature( feature, renderedBoundsGeom, featureContext );
2201 }
2202
2203 if ( drawVertexMarker )
2204 {
2205 if ( !markers.isEmpty() && !context.renderingStopped() )
2206 {
2207 const auto constMarkers = markers;
2208 for ( QPointF marker : constMarkers )
2209 {
2210 renderVertexMarker( marker, context, currentVertexMarkerType, currentVertexMarkerSize );
2211 }
2212 }
2213 else
2214 {
2216 const QgsMapToPixel &mtp = context.mapToPixel();
2217
2218 QgsPoint vertexPoint;
2219 QgsVertexId vertexId;
2220 double x, y, z;
2221 QPointF mapPoint;
2222 while ( geom.constGet()->nextVertex( vertexId, vertexPoint ) )
2223 {
2224 //transform
2225 x = vertexPoint.x();
2226 y = vertexPoint.y();
2227 z = 0.0;
2228 if ( ct.isValid() )
2229 {
2230 ct.transformInPlace( x, y, z );
2231 }
2232 mapPoint.setX( x );
2233 mapPoint.setY( y );
2234 mtp.transformInPlace( mapPoint.rx(), mapPoint.ry() );
2235 renderVertexMarker( mapPoint, context, currentVertexMarkerType, currentVertexMarkerSize );
2236 }
2237 }
2238 }
2239}
2240
2242{
2243 return mSymbolRenderContext.get();
2244}
2245
2247{
2248 return mExtentBuffer;
2249}
2250
2251void QgsSymbol::setExtentBuffer( double extentBuffer )
2252{
2253 if ( extentBuffer < 0 )
2254 mExtentBuffer = 0;
2255 else
2257}
2258
2259
2260void QgsSymbol::renderVertexMarker( QPointF pt, QgsRenderContext &context, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize )
2261{
2262 int markerSize = context.convertToPainterUnits( currentVertexMarkerSize, Qgis::RenderUnit::Millimeters );
2263 QgsSymbolLayerUtils::drawVertexMarker( pt.x(), pt.y(), *context.painter(), currentVertexMarkerType, markerSize );
2264}
2265
2266void QgsSymbol::initPropertyDefinitions()
2267{
2268 if ( !sPropertyDefinitions.isEmpty() )
2269 return;
2270
2271 QString origin = QStringLiteral( "symbol" );
2272
2273 sPropertyDefinitions = QgsPropertiesDefinition
2274 {
2275 { static_cast< int >( QgsSymbol::Property::Opacity ), QgsPropertyDefinition( "alpha", QObject::tr( "Opacity" ), QgsPropertyDefinition::Opacity, origin )},
2276 { static_cast< int >( QgsSymbol::Property::ExtentBuffer ), QgsPropertyDefinition( "extent_buffer", QObject::tr( "Extent buffer" ), QgsPropertyDefinition::DoublePositive, origin )},
2277 };
2278}
2279
2280void QgsSymbol::startFeatureRender( const QgsFeature &feature, QgsRenderContext &context, const int layer )
2281{
2282 if ( layer != -1 )
2283 {
2285 if ( symbolLayer && symbolLayer->enabled() )
2286 {
2287 symbolLayer->startFeatureRender( feature, context );
2288 }
2289 return;
2290 }
2291 else
2292 {
2293 const QList< QgsSymbolLayer * > layers = mLayers;
2294 for ( QgsSymbolLayer *symbolLayer : layers )
2295 {
2296 if ( !symbolLayer->enabled() )
2297 continue;
2298
2299 symbolLayer->startFeatureRender( feature, context );
2300 }
2301 }
2302}
2303
2304void QgsSymbol::stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context, int layer )
2305{
2306 if ( layer != -1 )
2307 {
2309 if ( symbolLayer && symbolLayer->enabled() )
2310 {
2311 symbolLayer->stopFeatureRender( feature, context );
2312 }
2313 return;
2314 }
2315 else
2316 {
2317 const QList< QgsSymbolLayer * > layers = mLayers;
2318 for ( QgsSymbolLayer *symbolLayer : layers )
2319 {
2320 if ( !symbolLayer->enabled() )
2321 continue;
2322
2323 symbolLayer->stopFeatureRender( feature, context );
2324 }
2325 }
2326}
2327
2329{
2330 mOpacity = other->mOpacity;
2332 mForceRHR = other->mForceRHR;
2333 mDataDefinedProperties = other->mDataDefinedProperties;
2334 mSymbolFlags = other->mSymbolFlags;
2338 if ( other->mBufferSettings )
2339 mBufferSettings = std::make_unique< QgsSymbolBufferSettings >( *other->mBufferSettings );
2340 else
2341 mBufferSettings.reset();
2342
2344 mLayer = other->mLayer;
2346}
2347
2349{
2351 if ( mBufferSettings && mBufferSettings->enabled() )
2352 {
2353 hints.setFlag( Qgis::SymbolRenderHint::ForceVectorRendering, true );
2354 }
2355 return hints;
2356
2357}
2358
2360{
2362 for ( const QgsSymbolLayer *layer : mLayers )
2363 {
2365 {
2366 res.setFlag( Qgis::SymbolFlag::AffectsLabeling );
2367 }
2368 }
2369 return res;
2370}
@ CounterClockwise
Counter-clockwise direction.
@ Clockwise
Clockwise direction.
@ ExcludeSymbolBuffers
Do not render symbol buffers.
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
@ ForceVectorRendering
Symbol must be rendered using vector methods, and optimisations like pre-rendered images must be disa...
@ DisableFeatureClipping
If present, indicates that features should never be clipped to the map extent during rendering.
@ AffectsLabeling
If present, indicates that the symbol layer will participate in the map labeling problem.
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
QFlags< SymbolPreviewFlag > SymbolPreviewFlags
Symbol preview flags.
Definition qgis.h:831
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:337
@ Polygon
Polygons.
@ Unknown
Unknown types.
@ Null
No geometry.
JoinStyle
Join styles for buffers.
Definition qgis.h:2084
@ Bevel
Use beveled joins.
@ Round
Use rounded joins.
@ Miter
Use mitered joins.
RenderUnit
Rendering size units.
Definition qgis.h:5029
@ Millimeters
Millimeters.
@ Unknown
Mixed or unknown units.
@ Round
Round cap.
@ DisableSymbolClippingToExtent
Force symbol clipping to map extent to be disabled in all situations. This will result in slower rend...
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ ApplyClipAfterReprojection
Feature geometry clipping to mapExtent() must be performed after the geometries are transformed using...
@ DrawSymbolBounds
Draw bounds of symbols (for debugging/testing)
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ AlwaysUseGlobalMasks
When applying clipping paths for selective masking, always use global ("entire map") paths,...
@ Antialiasing
Use antialiasing while drawing.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
@ FlagIncludeCrosshairsForMarkerSymbols
Include a crosshairs reference image in the background of marker symbol previews.
VertexMarkerType
Editing vertex markers, used for showing vertices during a edit operation.
Definition qgis.h:1796
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:741
QFlags< SymbolFlag > SymbolFlags
Symbol flags.
Definition qgis.h:817
SymbolType
Symbol types.
Definition qgis.h:574
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ Hybrid
Hybrid symbol.
@ Cmyk
CMYK color model.
@ AffectsLabeling
If present, indicates that the symbol will participate in the map labeling problem.
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ Triangle
Triangle.
@ MultiLineString
MultiLineString.
@ GeometryCollection
GeometryCollection.
@ MultiCurve
MultiCurve.
@ PolyhedralSurface
PolyhedralSurface.
@ MultiSurface
MultiSurface.
@ Forward
Forward transform (from source to destination)
Abstract base class for all geometries.
virtual const QgsAbstractGeometry * simplifiedTypeRef() const
Returns a reference to the simplest lossless representation of this geometry, e.g.
virtual QgsRectangle boundingBox() const
Returns the minimal bounding box for the geometry.
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
Qgis::WkbType wkbType() const
Returns the WKB type of the geometry.
virtual int partCount() const =0
Returns count of parts contained in the geometry.
virtual bool hasCurvedSegments() const
Returns true if the geometry contains curved segments.
virtual bool nextVertex(QgsVertexId &id, QgsPoint &vertex) const =0
Returns next vertex id and coordinates.
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application's color scheme registry, used for managing color schemes.
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:43
static void trimPolygon(QPolygonF &pts, const QgsRectangle &clipRect)
Trims the given polygon to a rectangular box, by modifying the given polygon in place.
Definition qgsclipper.h:282
static QPolygonF clippedLine(const QgsCurve &curve, const QgsRectangle &clipExtent)
Takes a linestring and clips it to clipExtent.
static void clipped3dLine(const QVector< double > &xIn, const QVector< double > &yIn, const QVector< double > &zIn, QVector< double > &x, QVector< double > &y, QVector< double > &z, const QgsBox3D &clipExtent)
Takes a line with 3D coordinates and clips it to clipExtent.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Handles coordinate transforms between two coordinate systems.
void transformCoords(int numPoint, double *x, double *y, double *z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform an array of coordinates to the destination CRS.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
void transformPolygon(QPolygonF &polygon, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms a polygon to the destination coordinate system.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
Custom exception class for Coordinate Reference System related exceptions.
Curve polygon geometry type.
int numInteriorRings() const
Returns the number of interior rings contained with the curve polygon.
const QgsCurve * exteriorRing() const
Returns the curve polygon's exterior ring.
const QgsCurve * interiorRing(int i) const
Retrieves an interior ring from the curve polygon.
Abstract base class for curved geometry type.
Definition qgscurve.h:35
Qgis::AngularDirection orientation() const
Returns the curve's orientation, e.g.
Definition qgscurve.cpp:286
virtual int numPoints() const =0
Returns the number of points in the curve.
QgsCurve * segmentize(double tolerance=M_PI_2/90, SegmentationToleranceType toleranceType=MaximumAngle) const override
Returns a geometry without curves.
Definition qgscurve.cpp:175
virtual QPolygonF asQPolygonF() const
Returns a QPolygonF representing the points.
Definition qgscurve.cpp:266
Manages painter saving and restoring required for effect drawing.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * updateSymbolScope(const QgsSymbol *symbol, QgsExpressionContextScope *symbolScope=nullptr)
Updates a symbol scope related to a QgsSymbol to an expression context.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static const QString EXPR_GEOMETRY_PART_COUNT
Inbuilt variable name for geometry part count variable.
static const QString EXPR_GEOMETRY_PART_NUM
Inbuilt variable name for geometry part number variable.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
QgsFeatureId id
Definition qgsfeature.h:66
QgsGeometry geometry
Definition qgsfeature.h:69
Container of fields for a vector layer.
Definition qgsfields.h:46
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
int numGeometries() const
Returns the number of geometries within the collection.
const QgsAbstractGeometry * geometryN(int n) const
Returns a const reference to a geometry from within the collection.
A symbol layer subclass which alters rendered feature shapes through the use of QGIS expressions.
void render(QgsSymbolRenderContext &context, Qgis::GeometryType geometryType=Qgis::GeometryType::Unknown, const QPolygonF *points=nullptr, const QVector< QPolygonF > *rings=nullptr)
Will render this symbol layer using the context.
A paint device which converts everything renderer to a QgsGeometry representation of the rendered sha...
const QgsAbstractGeometry & geometry() const
Returns the rendered geometry.
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
Qgis::GeometryType type
void set(QgsAbstractGeometry *geometry)
Sets the underlying geometry store.
QgsGeometry buffer(double distance, int segments) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Does vector analysis using the GEOS library and handles import, export, and exception handling.
Definition qgsgeos.h:139
Represents a patch shape for use in map legends.
QList< QList< QPolygonF > > toQPolygonF(Qgis::SymbolType type, QSizeF size) const
Converts the patch shape to a set of QPolygonF objects representing how the patch should be drawn for...
Line string geometry type, with support for z-dimension and m-values.
QVector< double > xVector() const
Returns the x vertex values as a vector.
QVector< double > yVector() const
Returns the y vertex values as a vector.
QVector< double > zVector() const
Returns the z vertex values as a vector.
Abstract base class for line symbol layers.
virtual void renderPolygonStroke(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context)
Renders the line symbol layer along the outline of polygon, using the given render context.
A line symbol type, for rendering LineString and MultiLineString geometries.
bool setId(const QString &id)
Sets the layer's id.
QgsMapLayer::LayerFlags flags() const
Returns the flags for this layer.
Qgis::LayerType type
Definition qgsmaplayer.h:87
Implementation of a geometry simplifier using the "MapToPixel" algorithm.
QgsGeometry simplify(const QgsGeometry &geometry) const override
Returns a simplified version the specified geometry.
Perform transforms between map coordinates and device coordinates.
void transformInPlace(double &x, double &y) const
Transforms map coordinates to device coordinates.
Struct for storing maximum and minimum scales for measurements in map units.
A marker symbol type, for rendering Point and MultiPoint geometries.
Multi point geometry collection.
Base class for visual effects which can be applied to QPicture drawings.
bool enabled() const
Returns whether the effect is enabled.
static void drawPicture(QPainter *painter, const QPointF &point, const QPicture &picture)
Draws a picture onto a painter, correctly applying workarounds to avoid issues with incorrect scaling...
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double x
Definition qgspoint.h:52
double y
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:33
Polyhedral surface geometry type.
int numPatches() const
Returns the number of patches contained with the polyhedral surface.
const QgsPolygon * patchN(int i) const
Retrieves a patch from the polyhedral surface.
Qgis::ColorModel colorModel() const
Returns the project's color model.
static QgsProject * instance()
Returns the QgsProject singleton instance.
const QgsProjectStyleSettings * styleSettings() const
Returns the project's style settings, which contains settings and properties relating to how a QgsPro...
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const final
Prepares the collection against a specified expression context.
bool hasActiveProperties() const final
Returns true if the collection has any active properties, or false if all properties within the colle...
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext(), bool ignoreContext=false) const final
Returns the set of any fields referenced by the active properties from the collection.
Definition for a property.
Definition qgsproperty.h:45
@ Opacity
Opacity (0-100)
Definition qgsproperty.h:60
@ DoublePositive
Positive double value (including 0)
Definition qgsproperty.h:56
A store for object properties.
A container for the context for various read/write operations on objects.
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
double xMinimum
double yMinimum
double xMaximum
double yMaximum
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
void setTextureOrigin(const QPointF &origin)
Sets the texture origin, which should be used as a brush transform when rendering using QBrush object...
bool symbolLayerHasClipGeometries(const QString &symbolLayerId) const
Returns true if the symbol layer with matching ID has any associated clip geometries.
bool hasRenderedFeatureHandlers() const
Returns true if the context has any rendered feature handlers.
QgsVectorSimplifyMethod & vectorSimplifyMethod()
Returns the simplification settings to use when rendering vector layers.
double segmentationTolerance() const
Gets the segmentation tolerance applied when rendering curved geometries.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsExpressionContext & expressionContext()
Gets the expression context.
void setGeometry(const QgsAbstractGeometry *geometry)
Sets pointer to original (unsegmentized) geometry.
QgsGeometry featureClipGeometry() const
Returns the geometry to use to clip features at render time.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
bool testFlag(Qgis::RenderContextFlag flag) const
Check whether a particular flag is enabled.
bool forceVectorOutput() const
Returns true if rendering operations should use vector operations instead of any faster raster shortc...
long long currentFrame() const
Returns the current frame number of the map (in frames per second), for maps which are part of an ani...
void setIsGuiPreview(bool preview)
Sets GUI preview mode.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
QList< QgsRenderedFeatureHandlerInterface * > renderedFeatureHandlers() const
Returns the list of rendered feature handlers to use while rendering map layers.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
double frameRate() const
Returns the frame rate of the map, for maps which are part of an animation.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
bool isSymbolLayerEnabled(const QgsSymbolLayer *layer) const
When rendering a map layer in a second pass (for selective masking), some symbol layers may be disabl...
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
QgsAbstractGeometry::SegmentationToleranceType segmentationToleranceType() const
Gets segmentation tolerance type (maximum angle or maximum difference between curve and approximation...
An interface for classes which provide custom handlers for features rendered as part of a map render ...
Stores properties relating to a screen.
double devicePixelRatio() const
Returns the ratio between physical pixels and device-independent pixels for the screen.
bool isValid() const
Returns true if the properties are valid.
void updateRenderContextForScreen(QgsRenderContext &context) const
Updates the settings in a render context to match the screen settings.
Renders polygons using a single fill and stroke color.
Holds SLD export options and other information related to SLD export of a QGIS layer style.
void setExtraProperties(const QVariantMap &properties)
Sets the open ended set of properties that can drive/inform the SLD encoding.
QVariantMap extraProperties() const
Returns the open ended set of properties that can drive/inform the SLD encoding.
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:146
QList< QList< QPolygonF > > defaultPatchAsQPolygonF(Qgis::SymbolType type, QSizeF size) const
Returns the default patch geometry for the given symbol type and size as a set of QPolygonF objects (...
Contains settings relating to symbol animation.
Definition qgssymbol.h:41
bool isAnimated() const
Returns true if the symbol is animated.
Definition qgssymbol.h:64
double frameRate() const
Returns the symbol animation frame rate (in frames per second).
Definition qgssymbol.h:78
Contains settings relating to symbol buffers, which draw a "halo" effect around the symbol.
Definition qgssymbol.h:97
QgsFillSymbol * fillSymbol() const
Returns the fill symbol used to render the buffer.
Definition qgssymbol.cpp:99
void setFillSymbol(QgsFillSymbol *symbol)
Sets the fill symbol used to render the buffer.
void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes the buffer settings to an XML element.
QgsSymbolBufferSettings & operator=(const QgsSymbolBufferSettings &)
Definition qgssymbol.cpp:88
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the buffer settings from an XML element.
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static void drawVertexMarker(double x, double y, QPainter &p, Qgis::VertexMarkerType type, int markerSize)
Draws a vertex symbol at (painter) coordinates x, y.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QString encodeSldUom(Qgis::RenderUnit unit, double *scaleFactor)
Encodes a render unit into an SLD unit of measure string.
static QList< QList< QPolygonF > > toQPolygonF(const QgsGeometry &geometry, Qgis::SymbolType type)
Converts a geometry to a set of QPolygonF objects representing how the geometry should be drawn for a...
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
Abstract base class for symbol layers.
virtual void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context)
Called before the layer will be rendered for a particular feature.
bool installMasks(QgsRenderContext &context, bool recursive, const QRectF &rect=QRectF())
When rendering, install masks on context painter.
@ LayerEnabled
Whether symbol layer is enabled.
QgsPaintEffect * paintEffect() const
Returns the current paint effect for the layer.
virtual void startRender(QgsSymbolRenderContext &context)=0
Called before a set of rendering operations commences on the supplied render context.
bool enabled() const
Returns true if symbol layer is enabled and will be drawn.
virtual void stopRender(QgsSymbolRenderContext &context)=0
Called after a set of rendering operations has finished on the supplied render context.
QString id() const
Returns symbol layer identifier This id is unique in the whole project.
virtual void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context)
Called after the layer has been rendered for a particular feature.
virtual Qgis::SymbolLayerFlags flags() const
Returns flags which control the symbol layer's behavior.
Encapsulates the context in which a symbol is being rendered.
void setSelected(bool selected)
Sets whether symbols should be rendered using the selected symbol coloring and style.
void setOriginalGeometryType(Qgis::GeometryType type)
Sets the geometry type for the original feature geometry being rendered.
void setPatchShape(const QgsLegendPatchShape &shape)
Sets the symbol patch shape, to use if rendering symbol preview icons.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
void renderUsingLayer(QgsSymbolLayer *layer, QgsSymbolRenderContext &context, Qgis::GeometryType geometryType=Qgis::GeometryType::Unknown, const QPolygonF *points=nullptr, const QVector< QPolygonF > *rings=nullptr)
Renders a context using a particular symbol layer without passing in a geometry.
Qgis::SymbolFlags mSymbolFlags
Symbol flags.
Definition qgssymbol.h:1022
QgsSymbolLayerList cloneLayers() const
Retrieve a cloned list of all layers that make up this symbol.
void setOutputUnit(Qgis::RenderUnit unit) const
Sets the units to use for sizes and widths within the symbol.
Property
Data definable properties.
Definition qgssymbol.h:270
@ ExtentBuffer
Extent buffer.
QgsSymbolRenderContext * symbolRenderContext()
Returns the symbol render context.
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
Qgis::SymbolRenderHints renderHints() const
Returns the rendering hint flags for the symbol.
void copyCommonProperties(const QgsSymbol *other)
Copies common properties from an other symbol to this symbol.
void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the symbol.
void renderFeature(const QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false, Qgis::VertexMarkerType currentVertexMarkerType=Qgis::VertexMarkerType::SemiTransparentCircle, double currentVertexMarkerSize=0.0)
Render a feature.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol's property collection, used for data defined overrides.
Definition qgssymbol.h:799
static QPolygonF _getLineString(QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent=true)
Creates a line string in screen coordinates from a QgsCurve in map coordinates.
void setExtentBuffer(double extentBuffer)
Sets the symbol's extent buffer.
void stopRender(QgsRenderContext &context)
Ends the rendering process.
qreal mOpacity
Symbol opacity (in the range 0 - 1)
Definition qgssymbol.h:1013
Q_DECL_DEPRECATED const QgsVectorLayer * mLayer
Definition qgssymbol.h:1030
static QPolygonF _getPolygonRing(QgsRenderContext &context, const QgsCurve &curve, bool clipToExtent, bool isExteriorRing=false, bool correctRingOrientation=false)
Creates a polygon ring in screen coordinates from a QgsCurve in map coordinates.
QgsSymbolAnimationSettings & animationSettings()
Returns a reference to the symbol animation settings.
void renderVertexMarker(QPointF pt, QgsRenderContext &context, Qgis::VertexMarkerType currentVertexMarkerType, double currentVertexMarkerSize)
Render editing vertex marker at specified point.
static QPointF _getPoint(QgsRenderContext &context, const QgsPoint &point)
Creates a point in screen coordinates from a QgsPoint in map coordinates.
Definition qgssymbol.h:933
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the symbol property definitions.
bool appendSymbolLayer(QgsSymbolLayer *layer)
Appends a symbol layer at the end of the current symbol layer list.
bool mClipFeaturesToExtent
Definition qgssymbol.h:1024
bool usesMapUnits() const
Returns true if the symbol has any components which use map unit based sizes.
Qgis::SymbolFlags flags() const
Returns flags for the symbol.
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, QVariantMap props) const
Converts the symbol to a SLD representation.
void setColor(const QColor &color) const
Sets the color for the symbol.
bool insertSymbolLayer(int index, QgsSymbolLayer *layer)
Inserts a symbol layer to specified index.
QgsMapUnitScale mapUnitScale() const
Returns the map unit scale for the symbol.
static QString symbolTypeToString(Qgis::SymbolType type)
Returns a translated string version of the specified symbol type.
qreal opacity() const
Returns the opacity for the symbol.
Definition qgssymbol.h:644
bool canCauseArtifactsBetweenAdjacentTiles() const
Returns true if the symbol rendering can cause visible artifacts across a single feature when the fea...
static Qgis::SymbolType symbolTypeForGeometryType(Qgis::GeometryType type)
Returns the default symbol type required for the specified geometry type.
void setMapUnitScale(const QgsMapUnitScale &scale) const
Sets the map unit scale for the symbol.
bool clipFeaturesToExtent() const
Returns whether features drawn by the symbol will be clipped to the render context's extent.
Definition qgssymbol.h:699
QImage bigSymbolPreviewImage(QgsExpressionContext *expressionContext=nullptr, Qgis::SymbolPreviewFlags flags=Qgis::SymbolPreviewFlag::FlagIncludeCrosshairsForMarkerSymbols, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a large (roughly 100x100 pixel) preview image for the symbol.
Qgis::RenderUnit mExtentBufferSizeUnit
Definition qgssymbol.h:1010
QgsSymbolBufferSettings * bufferSettings()
Returns the symbol buffer settings, which control an optional "halo" effect around the symbol.
QImage asImage(QSize size, QgsRenderContext *customContext=nullptr)
Returns an image of the symbol at the specified size.
static void _getPolygon(QPolygonF &pts, QVector< QPolygonF > &holes, QgsRenderContext &context, const QgsPolygon &polygon, bool clipToExtent=true, bool correctRingOrientation=false)
Creates a polygon in screen coordinates from a QgsPolygonXYin map coordinates.
QString dump() const
Returns a string dump of the symbol's properties.
bool hasDataDefinedProperties() const
Returns whether the symbol utilizes any data defined properties.
bool deleteSymbolLayer(int index)
Removes and deletes the symbol layer at the specified index.
virtual ~QgsSymbol()
QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns a list of attributes required to render this feature.
std::unique_ptr< QgsSymbolBufferSettings > mBufferSettings
Definition qgssymbol.h:1027
Qgis::SymbolType mType
Definition qgssymbol.h:1006
bool changeSymbolLayer(int index, QgsSymbolLayer *layer)
Deletes the current layer at the specified index and replaces it with layer.
QgsSymbolLayer * takeSymbolLayer(int index)
Removes a symbol layer from the list and returns a pointer to it.
Qgis::SymbolRenderHints mRenderHints
Definition qgssymbol.h:1015
bool mForceRHR
Definition qgssymbol.h:1025
QgsSymbolLayerList mLayers
Definition qgssymbol.h:1007
void drawPreviewIcon(QPainter *painter, QSize size, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr, const QgsLegendPatchShape *patchShape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Draws an icon of the symbol that occupies an area given by size using the specified painter.
Q_DECL_DEPRECATED const QgsVectorLayer * layer() const
QgsSymbolAnimationSettings mAnimationSettings
Definition qgssymbol.h:1028
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context, int layer=-1)
Called before symbol layers will be rendered for a particular feature.
QColor color() const
Returns the symbol's color.
Qgis::RenderUnit outputUnit() const
Returns the units to use for sizes and widths within the symbol.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:294
QgsSymbol(Qgis::SymbolType type, const QgsSymbolLayerList &layers)
Constructor for a QgsSymbol of the specified type.
void setAnimationSettings(const QgsSymbolAnimationSettings &settings)
Sets a the symbol animation settings.
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
void setBufferSettings(QgsSymbolBufferSettings *settings)
Sets a the symbol buffer settings, which control an optional "halo" effect around the symbol.
Q_DECL_DEPRECATED void setLayer(const QgsVectorLayer *layer)
Sets the vector layer associated with the symbol.
double extentBuffer() const
Returns the symbol's extent buffer.
void exportImage(const QString &path, const QString &format, QSize size)
Export the symbol as an image format, to the specified path and with the given size.
double mExtentBuffer
Definition qgssymbol.h:1009
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context, int layer=-1)
Called after symbol layers have been rendered for a particular feature.
static QgsSymbol * defaultSymbol(Qgis::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
Represents a vector layer which manages a vector based dataset.
QgsVectorLayer * clone() const override
Returns a new instance equivalent to this one.
double tolerance() const
Gets the tolerance of simplification in map units. Represents the maximum distance in map units betwe...
bool forceLocalOptimization() const
Gets where the simplification executes, after fetch the geometries from provider, or when supported,...
Qgis::VectorRenderingSimplificationFlags simplifyHints() const
Gets the simplification hints of the vector layer managed.
Qgis::VectorSimplificationAlgorithm simplifyAlgorithm() const
Gets the local simplification algorithm of the vector layer managed.
static QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
static bool isCurvedType(Qgis::WkbType type)
Returns true if the WKB type is a curved type or can contain curved geometries.
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
Contains geos related utilities and functions.
Definition qgsgeos.h:75
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6855
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6219
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6854
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6302
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
Single variable definition for use within a QgsExpressionContextScope.
Utility class for identifying a unique vertex within a geometry.
Definition qgsvertexid.h:30