QGIS API Documentation 3.99.0-Master (a8f284845db)
Loading...
Searching...
No Matches
qgsfillsymbollayer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsfillsymbollayer.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 "qgsfillsymbollayer.h"
17
18#include <random>
19
20#include "qgsapplication.h"
21#include "qgscolorramp.h"
22#include "qgscolorrampimpl.h"
23#include "qgscolorutils.h"
24#include "qgsdxfexport.h"
26#include "qgsfeedback.h"
27#include "qgsfileutils.h"
28#include "qgsgeometry.h"
29#include "qgsgeometryengine.h"
30#include "qgsimagecache.h"
31#include "qgsimageoperation.h"
32#include "qgslinestring.h"
33#include "qgslinesymbol.h"
34#include "qgslinesymbollayer.h"
35#include "qgsmarkersymbol.h"
36#include "qgsmessagelog.h"
37#include "qgspolygon.h"
38#include "qgsproject.h"
39#include "qgsrendercontext.h"
40#include "qgssldexportcontext.h"
41#include "qgssvgcache.h"
42#include "qgssymbol.h"
43#include "qgssymbollayerutils.h"
44#include "qgsunittypes.h"
45
46#include <QDomDocument>
47#include <QDomElement>
48#include <QFile>
49#include <QPagedPaintDevice>
50#include <QPainter>
51#include <QString>
52#include <QSvgRenderer>
53#include <QtMath>
54
55using namespace Qt::StringLiterals;
56
57QgsSimpleFillSymbolLayer::QgsSimpleFillSymbolLayer( const QColor &color, Qt::BrushStyle style, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth,
58 Qt::PenJoinStyle penJoinStyle )
59 : mBrushStyle( style )
64{
65 mColor = color;
66}
67
69
75
77{
79 if ( mOffsetUnit != unit )
80 {
82 }
83 return unit;
84}
85
91
97
106
107void QgsSimpleFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QBrush &brush, QPen &pen, QPen &selPen )
108{
109 if ( !dataDefinedProperties().hasActiveProperties() )
110 return; // shortcut
111
112 bool ok;
113
115 {
118 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
119 brush.setColor( fillColor );
120 }
122 {
125 if ( !QgsVariantUtils::isNull( exprVal ) )
126 brush.setStyle( QgsSymbolLayerUtils::decodeBrushStyle( exprVal.toString() ) );
127 }
129 {
132 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
133 pen.setColor( penColor );
134 }
136 {
139 if ( !QgsVariantUtils::isNull( exprVal ) )
140 {
141 double width = exprVal.toDouble( &ok );
142 if ( ok )
143 {
145 pen.setWidthF( width );
146 selPen.setWidthF( width );
147 }
148 }
149 }
151 {
153 QString style = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::StrokeStyle, context.renderContext().expressionContext(), QString(), &ok );
154 if ( ok )
155 {
156 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
157 selPen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
158 }
159 }
161 {
163 QString style = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::JoinStyle, context.renderContext().expressionContext(), QString(), &ok );
164 if ( ok )
165 {
166 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
167 selPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
168 }
169 }
170}
171
172
174{
176 Qt::BrushStyle style = DEFAULT_SIMPLEFILL_STYLE;
181 QPointF offset;
182
183 if ( props.contains( u"color"_s ) )
184 color = QgsColorUtils::colorFromString( props[u"color"_s].toString() );
185 if ( props.contains( u"style"_s ) )
186 style = QgsSymbolLayerUtils::decodeBrushStyle( props[u"style"_s].toString() );
187 if ( props.contains( u"color_border"_s ) )
188 {
189 //pre 2.5 projects used "color_border"
190 strokeColor = QgsColorUtils::colorFromString( props[u"color_border"_s].toString() );
191 }
192 else if ( props.contains( u"outline_color"_s ) )
193 {
194 strokeColor = QgsColorUtils::colorFromString( props[u"outline_color"_s].toString() );
195 }
196 else if ( props.contains( u"line_color"_s ) )
197 {
198 strokeColor = QgsColorUtils::colorFromString( props[u"line_color"_s].toString() );
199 }
200
201 if ( props.contains( u"style_border"_s ) )
202 {
203 //pre 2.5 projects used "style_border"
204 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[u"style_border"_s].toString() );
205 }
206 else if ( props.contains( u"outline_style"_s ) )
207 {
208 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[u"outline_style"_s].toString() );
209 }
210 else if ( props.contains( u"line_style"_s ) )
211 {
212 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[u"line_style"_s].toString() );
213 }
214 if ( props.contains( u"width_border"_s ) )
215 {
216 //pre 2.5 projects used "width_border"
217 strokeWidth = props[u"width_border"_s].toDouble();
218 }
219 else if ( props.contains( u"outline_width"_s ) )
220 {
221 strokeWidth = props[u"outline_width"_s].toDouble();
222 }
223 else if ( props.contains( u"line_width"_s ) )
224 {
225 strokeWidth = props[u"line_width"_s].toDouble();
226 }
227 if ( props.contains( u"offset"_s ) )
228 offset = QgsSymbolLayerUtils::decodePoint( props[u"offset"_s].toString() );
229 if ( props.contains( u"joinstyle"_s ) )
230 penJoinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( props[u"joinstyle"_s].toString() );
231
232 auto sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, style, strokeColor, strokeStyle, strokeWidth, penJoinStyle );
233 sl->setOffset( offset );
234 if ( props.contains( u"border_width_unit"_s ) )
235 {
236 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"border_width_unit"_s].toString() ) );
237 }
238 else if ( props.contains( u"outline_width_unit"_s ) )
239 {
240 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"outline_width_unit"_s].toString() ) );
241 }
242 else if ( props.contains( u"line_width_unit"_s ) )
243 {
244 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[u"line_width_unit"_s].toString() ) );
245 }
246 if ( props.contains( u"offset_unit"_s ) )
247 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
248
249 if ( props.contains( u"border_width_map_unit_scale"_s ) )
250 sl->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"border_width_map_unit_scale"_s].toString() ) );
251 if ( props.contains( u"offset_map_unit_scale"_s ) )
252 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_map_unit_scale"_s].toString() ) );
253
254 sl->restoreOldDataDefinedProperties( props );
255
256 return sl.release();
257}
258
259
261{
262 return u"SimpleFill"_s;
263}
264
269
271{
272 QColor fillColor = mColor;
273 fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
274 mBrush = QBrush( fillColor, mBrushStyle );
275
276 QColor selColor = context.renderContext().selectionColor();
277 QColor selPenColor = selColor == mColor ? selColor : mStrokeColor;
278 if ( ! SELECTION_IS_OPAQUE )
279 selColor.setAlphaF( context.opacity() );
280 mSelBrush = QBrush( selColor );
281 // N.B. unless a "selection line color" is implemented in addition to the "selection color" option
282 // this would mean symbols with "no fill" look the same whether or not they are selected
283 if ( SELECT_FILL_STYLE )
284 mSelBrush.setStyle( mBrushStyle );
285
286 QColor strokeColor = mStrokeColor;
287 strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
288 mPen = QPen( strokeColor );
289 mSelPen = QPen( selPenColor );
290 mPen.setStyle( mStrokeStyle );
292 mPen.setJoinStyle( mPenJoinStyle );
293}
294
296{
297 Q_UNUSED( context )
298}
299
300void QgsSimpleFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
301{
302 QPainter *p = context.renderContext().painter();
303 if ( !p )
304 {
305 return;
306 }
307
308 QColor fillColor = mColor;
309 fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
310 mBrush.setColor( fillColor );
311 QColor strokeColor = mStrokeColor;
312 strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
313 mPen.setColor( strokeColor );
314
315 applyDataDefinedSymbology( context, mBrush, mPen, mSelPen );
316
317 QPointF offset = mOffset;
318
320 {
322 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::Property::Offset, context.renderContext().expressionContext(), QString() );
323 bool ok = false;
324 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
325 if ( ok )
326 offset = res;
327 }
328
329 if ( !offset.isNull() )
330 {
333 p->translate( offset );
334 }
335
336 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
337
338 if ( mBrush.style() == Qt::SolidPattern || mBrush.style() == Qt::NoBrush || !dynamic_cast<QPagedPaintDevice *>( p->device() ) )
339 {
340 p->setPen( useSelectedColor ? mSelPen : mPen );
341 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
342 _renderPolygon( p, points, rings, context );
343 }
344 else
345 {
346 // workaround upstream issue https://github.com/qgis/QGIS/issues/36580
347 // when a non-solid brush is set with opacity, the opacity incorrectly applies to the pen
348 // when exporting to PDF/print devices
349 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
350 p->setPen( Qt::NoPen );
351 _renderPolygon( p, points, rings, context );
352
353 p->setPen( useSelectedColor ? mSelPen : mPen );
354 p->setBrush( Qt::NoBrush );
355 _renderPolygon( p, points, rings, context );
356 }
357
358 if ( !offset.isNull() )
359 {
360 p->translate( -offset );
361 }
362}
363
365{
366 QVariantMap map;
367 map[u"color"_s] = QgsColorUtils::colorToString( mColor );
369 map[u"outline_color"_s] = QgsColorUtils::colorToString( mStrokeColor );
370 map[u"outline_style"_s] = QgsSymbolLayerUtils::encodePenStyle( mStrokeStyle );
371 map[u"outline_width"_s] = QString::number( mStrokeWidth );
372 map[u"outline_width_unit"_s] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
373 map[u"border_width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
375 map[u"offset"_s] = QgsSymbolLayerUtils::encodePoint( mOffset );
376 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
377 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
378 return map;
379}
380
382{
383 auto sl = std::make_unique< QgsSimpleFillSymbolLayer >( mColor, mBrushStyle, mStrokeColor, mStrokeStyle, mStrokeWidth, mPenJoinStyle );
384 sl->setOffset( mOffset );
385 sl->setOffsetUnit( mOffsetUnit );
386 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
387 sl->setStrokeWidthUnit( mStrokeWidthUnit );
388 sl->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
389 copyCommonProperties( sl.get() );
390 return sl.release();
391}
392
393void QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
394{
395 QgsSldExportContext context;
396 context.setExtraProperties( props );
397 toSld( doc, element, context );
398}
399
400bool QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
401{
402 if ( mBrushStyle == Qt::NoBrush && mStrokeStyle == Qt::NoPen )
403 return true;
404
405 const QVariantMap props = context.extraProperties();
406 QDomElement symbolizerElem = doc.createElement( u"se:PolygonSymbolizer"_s );
407 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
408 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
409 element.appendChild( symbolizerElem );
410
411 // <Geometry>
412 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
413
414 // Export to PNG
415 bool exportOk { false };
416 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
417 {
418 const QImage image { toTiledPatternImage( ) };
419 if ( ! image.isNull() )
420 {
421 // <Fill>
422 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
423 symbolizerElem.appendChild( fillElem );
424 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
425 fillElem.appendChild( graphicFillElem );
426 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
427 graphicFillElem.appendChild( graphicElem );
428 QgsRenderContext renderContext;
429 const QFileInfo info { context.exportFilePath() };
430 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( u"png"_s ) };
431 pngPath = QgsFileUtils::uniquePath( pngPath );
432 image.save( pngPath );
433 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), u"image/png"_s, QColor(), image.height() );
434 exportOk = true;
435 }
436 }
437
438 if ( ! exportOk )
439 {
440 if ( mBrushStyle != Qt::NoBrush )
441 {
442
443 QColor color { mColor };
444
445 // Apply alpha from symbol
446 bool ok;
447 const double alpha { props.value( u"alpha"_s, QVariant() ).toDouble( &ok ) };
448 if ( ok )
449 {
450 color.setAlphaF( color.alphaF() * alpha );
451 }
452 // <Fill>
453 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
454 symbolizerElem.appendChild( fillElem );
455 QgsSymbolLayerUtils::fillToSld( doc, fillElem, context, mBrushStyle, color );
456 }
457
458 if ( mStrokeStyle != Qt::NoPen )
459 {
460 // <Stroke>
461 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
462 symbolizerElem.appendChild( strokeElem );
464 // Apply alpha from symbol
465 bool ok;
466 const double alpha { props.value( u"alpha"_s, QVariant() ).toDouble( &ok ) };
467 QColor strokeColor { mStrokeColor };
468 if ( ok )
469 {
470 strokeColor.setAlphaF( strokeColor.alphaF() * alpha );
471 }
473 }
474 }
475
476 // <se:Displacement>
479 return true;
480}
481
482QString QgsSimpleFillSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
483{
484 //brush
485 QString symbolStyle;
486 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStyleBrush( mColor ) );
487 symbolStyle.append( ';' );
488 //pen
489 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStylePen( mStrokeWidth, mmScaleFactor, mapUnitScaleFactor, mStrokeColor, mPenJoinStyle ) );
490 return symbolStyle;
491}
492
494{
495 QColor color, strokeColor;
496 Qt::BrushStyle fillStyle;
497 Qt::PenStyle strokeStyle;
498 double strokeWidth;
499
500 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
501 QgsSymbolLayerUtils::fillFromSld( fillElem, fillStyle, color );
502
503 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
505
506 QPointF offset;
508
509 double scaleFactor = 1.0;
510 const QString uom = element.attribute( u"uom"_s );
511 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
512 offset.setX( offset.x() * scaleFactor );
513 offset.setY( offset.y() * scaleFactor );
514 strokeWidth = strokeWidth * scaleFactor;
515
516 auto sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, fillStyle, strokeColor, strokeStyle, strokeWidth );
517 sl->setOutputUnit( sldUnitSize );
518 sl->setOffset( offset );
519 return sl.release();
520}
521
523{
524 double penBleed = context.convertToPainterUnits( mStrokeStyle == Qt::NoPen ? 0 : ( mStrokeWidth / 2.0 ), mStrokeWidthUnit, mStrokeWidthMapUnitScale );
525 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
526 return penBleed + offsetBleed;
527}
528
539
550
561
563{
564 return mStrokeStyle;
565}
566
568{
569 QColor c = mColor;
571 {
573 }
574 return c;
575}
576
578{
579 return mBrushStyle;
580}
581
583{
584 QPixmap pixmap( QSize( 32, 32 ) );
585 pixmap.fill( Qt::transparent );
586 QPainter painter;
587 painter.begin( &pixmap );
588 painter.setRenderHint( QPainter::Antialiasing );
589 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
594 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
595
596 std::unique_ptr< QgsSimpleFillSymbolLayer > layerClone( clone() );
597 layerClone->setStrokeStyle( Qt::PenStyle::NoPen );
598 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
599 painter.end();
600 return pixmap.toImage();
601}
602
603//QgsGradientFillSymbolLayer
604
618
623
625{
626 //default to a two-color, linear gradient with feature mode and pad spreading
631 //default to gradient from the default fill color to white
632 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
633 QPointF referencePoint1 = QPointF( 0.5, 0 );
634 bool refPoint1IsCentroid = false;
635 QPointF referencePoint2 = QPointF( 0.5, 1 );
636 bool refPoint2IsCentroid = false;
637 double angle = 0;
638 QPointF offset;
639
640 //update gradient properties from props
641 if ( props.contains( u"type"_s ) )
642 type = static_cast< Qgis::GradientType >( props[u"type"_s].toInt() );
643 if ( props.contains( u"coordinate_mode"_s ) )
644 coordinateMode = static_cast< Qgis::SymbolCoordinateReference >( props[u"coordinate_mode"_s].toInt() );
645 if ( props.contains( u"spread"_s ) )
646 gradientSpread = static_cast< Qgis::GradientSpread >( props[u"spread"_s].toInt() );
647 if ( props.contains( u"color_type"_s ) )
648 colorType = static_cast< Qgis::GradientColorSource >( props[u"color_type"_s].toInt() );
649 if ( props.contains( u"gradient_color"_s ) )
650 {
651 //pre 2.5 projects used "gradient_color"
652 color = QgsColorUtils::colorFromString( props[u"gradient_color"_s].toString() );
653 }
654 else if ( props.contains( u"color"_s ) )
655 {
656 color = QgsColorUtils::colorFromString( props[u"color"_s].toString() );
657 }
658 if ( props.contains( u"gradient_color2"_s ) )
659 {
660 color2 = QgsColorUtils::colorFromString( props[u"gradient_color2"_s].toString() );
661 }
662
663 if ( props.contains( u"reference_point1"_s ) )
664 referencePoint1 = QgsSymbolLayerUtils::decodePoint( props[u"reference_point1"_s].toString() );
665 if ( props.contains( u"reference_point1_iscentroid"_s ) )
666 refPoint1IsCentroid = props[u"reference_point1_iscentroid"_s].toInt();
667 if ( props.contains( u"reference_point2"_s ) )
668 referencePoint2 = QgsSymbolLayerUtils::decodePoint( props[u"reference_point2"_s].toString() );
669 if ( props.contains( u"reference_point2_iscentroid"_s ) )
670 refPoint2IsCentroid = props[u"reference_point2_iscentroid"_s].toInt();
671 if ( props.contains( u"angle"_s ) )
672 angle = props[u"angle"_s].toDouble();
673
674 if ( props.contains( u"offset"_s ) )
675 offset = QgsSymbolLayerUtils::decodePoint( props[u"offset"_s].toString() );
676
677 //attempt to create color ramp from props
678 QgsColorRamp *gradientRamp = nullptr;
679 if ( props.contains( u"rampType"_s ) && props[u"rampType"_s] == QgsCptCityColorRamp::typeString() )
680 {
681 gradientRamp = QgsCptCityColorRamp::create( props );
682 }
683 else
684 {
685 gradientRamp = QgsGradientColorRamp::create( props );
686 }
687
688 //create a new gradient fill layer with desired properties
689 auto sl = std::make_unique< QgsGradientFillSymbolLayer >( color, color2, colorType, type, coordinateMode, gradientSpread );
690 sl->setOffset( offset );
691 if ( props.contains( u"offset_unit"_s ) )
692 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
693 if ( props.contains( u"offset_map_unit_scale"_s ) )
694 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_map_unit_scale"_s].toString() ) );
695 sl->setReferencePoint1( referencePoint1 );
696 sl->setReferencePoint1IsCentroid( refPoint1IsCentroid );
697 sl->setReferencePoint2( referencePoint2 );
698 sl->setReferencePoint2IsCentroid( refPoint2IsCentroid );
699 sl->setAngle( angle );
700 if ( gradientRamp )
701 sl->setColorRamp( gradientRamp );
702
703 sl->restoreOldDataDefinedProperties( props );
704
705 return sl.release();
706}
707
712
714{
715 mGradientRamp.reset( ramp );
716}
717
719{
720 return u"GradientFill"_s;
721}
722
723void QgsGradientFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, const QPolygonF &points )
724{
726 {
727 //shortcut
730 return;
731 }
732
733 bool ok;
734
735 //first gradient color
736 QColor color = mColor;
738 {
741 color.setAlphaF( context.opacity() * color.alphaF() );
742 }
743
744 //second gradient color
745 QColor color2 = mColor2;
747 {
750 color2.setAlphaF( context.opacity() * color2.alphaF() );
751 }
752
753 //gradient rotation angle
754 double angle = mAngle;
756 {
759 }
760
761 //gradient type
764 {
765 QString currentType = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::GradientType, context.renderContext().expressionContext(), QString(), &ok );
766 if ( ok )
767 {
768 if ( currentType == QObject::tr( "linear" ) )
769 {
771 }
772 else if ( currentType == QObject::tr( "radial" ) )
773 {
775 }
776 else if ( currentType == QObject::tr( "conical" ) )
777 {
779 }
780 }
781 }
782
783 //coordinate mode
786 {
787 QString currentCoordMode = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::CoordinateMode, context.renderContext().expressionContext(), QString(), &ok );
788 if ( ok )
789 {
790 if ( currentCoordMode == QObject::tr( "feature" ) )
791 {
793 }
794 else if ( currentCoordMode == QObject::tr( "viewport" ) )
795 {
797 }
798 }
799 }
800
801 //gradient spread
804 {
805 QString currentSpread = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::GradientSpread, context.renderContext().expressionContext(), QString(), &ok );
806 if ( ok )
807 {
808 if ( currentSpread == QObject::tr( "pad" ) )
809 {
811 }
812 else if ( currentSpread == QObject::tr( "repeat" ) )
813 {
815 }
816 else if ( currentSpread == QObject::tr( "reflect" ) )
817 {
819 }
820 }
821 }
822
823 //reference point 1 x & y
824 double refPoint1X = mReferencePoint1.x();
826 {
827 context.setOriginalValueVariable( refPoint1X );
829 }
830 double refPoint1Y = mReferencePoint1.y();
832 {
833 context.setOriginalValueVariable( refPoint1Y );
835 }
836 bool refPoint1IsCentroid = mReferencePoint1IsCentroid;
838 {
839 context.setOriginalValueVariable( refPoint1IsCentroid );
840 refPoint1IsCentroid = mDataDefinedProperties.valueAsBool( QgsSymbolLayer::Property::GradientReference1IsCentroid, context.renderContext().expressionContext(), refPoint1IsCentroid );
841 }
842
843 //reference point 2 x & y
844 double refPoint2X = mReferencePoint2.x();
846 {
847 context.setOriginalValueVariable( refPoint2X );
849 }
850 double refPoint2Y = mReferencePoint2.y();
852 {
853 context.setOriginalValueVariable( refPoint2Y );
855 }
856 bool refPoint2IsCentroid = mReferencePoint2IsCentroid;
858 {
859 context.setOriginalValueVariable( refPoint2IsCentroid );
860 refPoint2IsCentroid = mDataDefinedProperties.valueAsBool( QgsSymbolLayer::Property::GradientReference2IsCentroid, context.renderContext().expressionContext(), refPoint2IsCentroid );
861 }
862
863 if ( refPoint1IsCentroid || refPoint2IsCentroid )
864 {
865 //either the gradient is starting or ending at a centroid, so calculate it
866 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
867 //centroid coordinates need to be scaled to a range [0, 1] relative to polygon bounds
868 QRectF bbox = points.boundingRect();
869 double centroidX = ( centroid.x() - bbox.left() ) / bbox.width();
870 double centroidY = ( centroid.y() - bbox.top() ) / bbox.height();
871
872 if ( refPoint1IsCentroid )
873 {
874 refPoint1X = centroidX;
875 refPoint1Y = centroidY;
876 }
877 if ( refPoint2IsCentroid )
878 {
879 refPoint2X = centroidX;
880 refPoint2Y = centroidY;
881 }
882 }
883
884 //update gradient with data defined values
886 spread, QPointF( refPoint1X, refPoint1Y ), QPointF( refPoint2X, refPoint2Y ), angle );
887}
888
889QPointF QgsGradientFillSymbolLayer::rotateReferencePoint( QPointF refPoint, double angle )
890{
891 //rotate a reference point by a specified angle around the point (0.5, 0.5)
892
893 //create a line from the centrepoint of a rectangle bounded by (0, 0) and (1, 1) to the reference point
894 QLineF refLine = QLineF( QPointF( 0.5, 0.5 ), refPoint );
895 //rotate this line by the current rotation angle
896 refLine.setAngle( refLine.angle() + angle );
897 //get new end point of line
898 QPointF rotatedReferencePoint = refLine.p2();
899 //make sure coords of new end point is within [0, 1]
900 if ( rotatedReferencePoint.x() > 1 )
901 rotatedReferencePoint.setX( 1 );
902 if ( rotatedReferencePoint.x() < 0 )
903 rotatedReferencePoint.setX( 0 );
904 if ( rotatedReferencePoint.y() > 1 )
905 rotatedReferencePoint.setY( 1 );
906 if ( rotatedReferencePoint.y() < 0 )
907 rotatedReferencePoint.setY( 0 );
908
909 return rotatedReferencePoint;
910}
911
912void QgsGradientFillSymbolLayer::applyGradient( const QgsSymbolRenderContext &context, QBrush &brush,
913 const QColor &color, const QColor &color2, Qgis::GradientColorSource gradientColorType,
914 QgsColorRamp *gradientRamp, Qgis::GradientType gradientType,
915 Qgis::SymbolCoordinateReference coordinateMode, Qgis::GradientSpread gradientSpread,
916 QPointF referencePoint1, QPointF referencePoint2, const double angle )
917{
918 //update alpha of gradient colors
919 QColor fillColor = color;
920 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
921 QColor fillColor2 = color2;
922 fillColor2.setAlphaF( context.opacity() * fillColor2.alphaF() );
923
924 //rotate reference points
925 QPointF rotatedReferencePoint1 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint1, angle ) : referencePoint1;
926 QPointF rotatedReferencePoint2 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint2, angle ) : referencePoint2;
927
928 //create a QGradient with the desired properties
929 QGradient gradient;
930 switch ( gradientType )
931 {
933 gradient = QLinearGradient( rotatedReferencePoint1, rotatedReferencePoint2 );
934 break;
936 gradient = QRadialGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).length() );
937 break;
939 gradient = QConicalGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).angle() );
940 break;
941 }
942 switch ( coordinateMode )
943 {
945 gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
946 break;
948 gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
949 break;
950 }
951 switch ( gradientSpread )
952 {
954 gradient.setSpread( QGradient::PadSpread );
955 break;
957 gradient.setSpread( QGradient::ReflectSpread );
958 break;
960 gradient.setSpread( QGradient::RepeatSpread );
961 break;
962 }
963
964 //add stops to gradient
966 ( gradientRamp->type() == QgsGradientColorRamp::typeString() || gradientRamp->type() == QgsCptCityColorRamp::typeString() ) )
967 {
968 //color ramp gradient
969 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( gradientRamp );
970 gradRamp->addStopsToGradient( &gradient, context.opacity() );
971 }
972 else
973 {
974 //two color gradient
975 gradient.setColorAt( 0.0, fillColor );
976 gradient.setColorAt( 1.0, fillColor2 );
977 }
978
979 //update QBrush use gradient
980 brush = QBrush( gradient );
981}
982
984{
985 QColor selColor = context.renderContext().selectionColor();
986 if ( ! SELECTION_IS_OPAQUE )
987 selColor.setAlphaF( context.opacity() );
988 mSelBrush = QBrush( selColor );
989}
990
992{
993 Q_UNUSED( context )
994}
995
996void QgsGradientFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
997{
998 QPainter *p = context.renderContext().painter();
999 if ( !p )
1000 {
1001 return;
1002 }
1003
1004 applyDataDefinedSymbology( context, points );
1005
1006 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1007 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
1008 p->setPen( Qt::NoPen );
1009
1010 QPointF offset = mOffset;
1012 {
1014 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::Property::Offset, context.renderContext().expressionContext(), QString() );
1015 bool ok = false;
1016 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1017 if ( ok )
1018 offset = res;
1019 }
1020
1021 if ( !offset.isNull() )
1022 {
1025 p->translate( offset );
1026 }
1027
1028 _renderPolygon( p, points, rings, context );
1029
1030 if ( !offset.isNull() )
1031 {
1032 p->translate( -offset );
1033 }
1034}
1035
1037{
1038 QVariantMap map;
1039 map[u"color"_s] = QgsColorUtils::colorToString( mColor );
1040 map[u"gradient_color2"_s] = QgsColorUtils::colorToString( mColor2 );
1041 map[u"color_type"_s] = QString::number( static_cast< int >( mGradientColorType ) );
1042 map[u"type"_s] = QString::number( static_cast<int>( mGradientType ) );
1043 map[u"coordinate_mode"_s] = QString::number( static_cast< int >( mCoordinateMode ) );
1044 map[u"spread"_s] = QString::number( static_cast< int >( mGradientSpread ) );
1045 map[u"reference_point1"_s] = QgsSymbolLayerUtils::encodePoint( mReferencePoint1 );
1046 map[u"reference_point1_iscentroid"_s] = QString::number( mReferencePoint1IsCentroid );
1047 map[u"reference_point2"_s] = QgsSymbolLayerUtils::encodePoint( mReferencePoint2 );
1048 map[u"reference_point2_iscentroid"_s] = QString::number( mReferencePoint2IsCentroid );
1049 map[u"angle"_s] = QString::number( mAngle );
1050 map[u"offset"_s] = QgsSymbolLayerUtils::encodePoint( mOffset );
1051 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1052 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1053 if ( mGradientRamp )
1054 {
1055 map.insert( mGradientRamp->properties() );
1056 }
1057 return map;
1058}
1059
1061{
1062 auto sl = std::make_unique< QgsGradientFillSymbolLayer >( mColor, mColor2, mGradientColorType, mGradientType, mCoordinateMode, mGradientSpread );
1063 if ( mGradientRamp )
1064 sl->setColorRamp( mGradientRamp->clone() );
1065 sl->setReferencePoint1( mReferencePoint1 );
1066 sl->setReferencePoint1IsCentroid( mReferencePoint1IsCentroid );
1067 sl->setReferencePoint2( mReferencePoint2 );
1068 sl->setReferencePoint2IsCentroid( mReferencePoint2IsCentroid );
1069 sl->setAngle( mAngle );
1070 sl->setOffset( mOffset );
1071 sl->setOffsetUnit( mOffsetUnit );
1072 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1073 copyCommonProperties( sl.get() );
1074 return sl.release();
1075}
1076
1078{
1079 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1080 return offsetBleed;
1081}
1082
1087
1092
1097
1102
1107
1112
1113//QgsShapeburstFillSymbolLayer
1114
1116 int blurRadius, bool useWholeShape, double maxDistance )
1117 : mBlurRadius( blurRadius )
1118 , mUseWholeShape( useWholeShape )
1119 , mMaxDistance( maxDistance )
1120 , mColorType( colorType )
1121 , mColor2( color2 )
1122{
1123 mColor = color;
1124}
1125
1127
1129{
1130 //default to a two-color gradient
1132 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
1133 int blurRadius = 0;
1134 bool useWholeShape = true;
1135 double maxDistance = 5;
1136 QPointF offset;
1137
1138 //update fill properties from props
1139 if ( props.contains( u"color_type"_s ) )
1140 {
1141 colorType = static_cast< Qgis::GradientColorSource >( props[u"color_type"_s].toInt() );
1142 }
1143 if ( props.contains( u"shapeburst_color"_s ) )
1144 {
1145 //pre 2.5 projects used "shapeburst_color"
1146 color = QgsColorUtils::colorFromString( props[u"shapeburst_color"_s].toString() );
1147 }
1148 else if ( props.contains( u"color"_s ) )
1149 {
1150 color = QgsColorUtils::colorFromString( props[u"color"_s].toString() );
1151 }
1152
1153 if ( props.contains( u"shapeburst_color2"_s ) )
1154 {
1155 //pre 2.5 projects used "shapeburst_color2"
1156 color2 = QgsColorUtils::colorFromString( props[u"shapeburst_color2"_s].toString() );
1157 }
1158 else if ( props.contains( u"gradient_color2"_s ) )
1159 {
1160 color2 = QgsColorUtils::colorFromString( props[u"gradient_color2"_s].toString() );
1161 }
1162 if ( props.contains( u"blur_radius"_s ) )
1163 {
1164 blurRadius = props[u"blur_radius"_s].toInt();
1165 }
1166 if ( props.contains( u"use_whole_shape"_s ) )
1167 {
1168 useWholeShape = props[u"use_whole_shape"_s].toInt();
1169 }
1170 if ( props.contains( u"max_distance"_s ) )
1171 {
1172 maxDistance = props[u"max_distance"_s].toDouble();
1173 }
1174 if ( props.contains( u"offset"_s ) )
1175 {
1176 offset = QgsSymbolLayerUtils::decodePoint( props[u"offset"_s].toString() );
1177 }
1178
1179 //attempt to create color ramp from props
1180 QgsColorRamp *gradientRamp = nullptr;
1181 if ( props.contains( u"rampType"_s ) && props[u"rampType"_s] == QgsCptCityColorRamp::typeString() )
1182 {
1183 gradientRamp = QgsCptCityColorRamp::create( props );
1184 }
1185 else
1186 {
1187 gradientRamp = QgsGradientColorRamp::create( props );
1188 }
1189
1190 //create a new shapeburst fill layer with desired properties
1191 auto sl = std::make_unique< QgsShapeburstFillSymbolLayer >( color, color2, colorType, blurRadius, useWholeShape, maxDistance );
1192 sl->setOffset( offset );
1193 if ( props.contains( u"offset_unit"_s ) )
1194 {
1195 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
1196 }
1197 if ( props.contains( u"distance_unit"_s ) )
1198 {
1199 sl->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( props[u"distance_unit"_s].toString() ) );
1200 }
1201 if ( props.contains( u"offset_map_unit_scale"_s ) )
1202 {
1203 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_map_unit_scale"_s].toString() ) );
1204 }
1205 if ( props.contains( u"distance_map_unit_scale"_s ) )
1206 {
1207 sl->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"distance_map_unit_scale"_s].toString() ) );
1208 }
1209 if ( props.contains( u"ignore_rings"_s ) )
1210 {
1211 sl->setIgnoreRings( props[u"ignore_rings"_s].toInt() );
1212 }
1213 if ( gradientRamp )
1214 {
1215 sl->setColorRamp( gradientRamp );
1216 }
1217
1218 sl->restoreOldDataDefinedProperties( props );
1219
1220 return sl.release();
1221}
1222
1224{
1225 return u"ShapeburstFill"_s;
1226}
1227
1232
1234{
1235 if ( mGradientRamp.get() == ramp )
1236 return;
1237
1238 mGradientRamp.reset( ramp );
1239}
1240
1241void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
1242 double &maxDistance, bool &ignoreRings )
1243{
1244 //first gradient color
1245 color = mColor;
1247 {
1250 }
1251
1252 //second gradient color
1253 color2 = mColor2;
1255 {
1258 }
1259
1260 //blur radius
1261 blurRadius = mBlurRadius;
1263 {
1264 context.setOriginalValueVariable( mBlurRadius );
1266 }
1267
1268 //use whole shape
1269 useWholeShape = mUseWholeShape;
1271 {
1272 context.setOriginalValueVariable( mUseWholeShape );
1274 }
1275
1276 //max distance
1277 maxDistance = mMaxDistance;
1279 {
1280 context.setOriginalValueVariable( mMaxDistance );
1282 }
1283
1284 //ignore rings
1285 ignoreRings = mIgnoreRings;
1287 {
1288 context.setOriginalValueVariable( mIgnoreRings );
1290 }
1291
1292}
1293
1295{
1296 //TODO - check this
1297 QColor selColor = context.renderContext().selectionColor();
1298 if ( ! SELECTION_IS_OPAQUE )
1299 selColor.setAlphaF( context.opacity() );
1300 mSelBrush = QBrush( selColor );
1301}
1302
1304{
1305 Q_UNUSED( context )
1306}
1307
1308void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1309{
1310 QPainter *p = context.renderContext().painter();
1311 if ( !p )
1312 {
1313 return;
1314 }
1315
1316 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1317 if ( useSelectedColor )
1318 {
1319 //feature is selected, draw using selection style
1320 p->setBrush( mSelBrush );
1321 QPointF offset = mOffset;
1322
1324 {
1326 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::Property::Offset, context.renderContext().expressionContext(), QString() );
1327 bool ok = false;
1328 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1329 if ( ok )
1330 offset = res;
1331 }
1332
1333 if ( !offset.isNull() )
1334 {
1335 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1336 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1337 p->translate( offset );
1338 }
1339 _renderPolygon( p, points, rings, context );
1340 if ( !offset.isNull() )
1341 {
1342 p->translate( -offset );
1343 }
1344 return;
1345 }
1346
1347 QColor color1, color2;
1348 int blurRadius;
1349 bool useWholeShape;
1350 double maxDistance;
1351 bool ignoreRings;
1352 //calculate data defined symbology
1353 applyDataDefinedSymbology( context, color1, color2, blurRadius, useWholeShape, maxDistance, ignoreRings );
1354
1355 //calculate max distance for shapeburst fill to extend from polygon boundary, in pixels
1356 int outputPixelMaxDist = 0;
1357 if ( !useWholeShape && !qgsDoubleNear( maxDistance, 0.0 ) )
1358 {
1359 //convert max distance to pixels
1360 outputPixelMaxDist = static_cast< int >( std::round( context.renderContext().convertToPainterUnits( maxDistance, mDistanceUnit, mDistanceMapUnitScale ) ) );
1361 }
1362
1363 //if we are using the two color mode, create a gradient ramp
1364 std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
1366 {
1367 twoColorGradientRamp = std::make_unique< QgsGradientColorRamp >( color1, color2 );
1368 }
1369
1370 //no stroke for shapeburst fills
1371 p->setPen( QPen( Qt::NoPen ) );
1372
1373 //calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
1374 int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
1375 //create a QImage to draw shapeburst in
1376 int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1377 int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1378 int imWidth = pointsWidth + ( sideBuffer * 2 );
1379 int imHeight = pointsHeight + ( sideBuffer * 2 );
1380
1381 // these are all potentially very expensive operations, so check regularly if the job is canceled and abort responsively
1382 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1383 return;
1384
1385 auto fillImage = std::make_unique< QImage >( imWidth,
1386 imHeight, QImage::Format_ARGB32_Premultiplied );
1387 if ( fillImage->isNull() )
1388 {
1389 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1390 return;
1391 }
1392
1393 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1394 return;
1395
1396 //also create an image to store the alpha channel
1397 auto alphaImage = std::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1398 if ( alphaImage->isNull() )
1399 {
1400 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1401 return;
1402 }
1403
1404 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1405 return;
1406
1407 //Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
1408 //polygon boundary. Since we don't care about pixels which fall outside the polygon, we start with a black image and then draw over it the
1409 //polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
1410 fillImage->fill( Qt::black );
1411
1412 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1413 return;
1414
1415 //initially fill the alpha channel image with a transparent color
1416 alphaImage->fill( Qt::transparent );
1417
1418 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1419 return;
1420
1421 //now, draw the polygon in the alpha channel image
1422 QPainter imgPainter;
1423 imgPainter.begin( alphaImage.get() );
1424 imgPainter.setRenderHint( QPainter::Antialiasing, true );
1425 imgPainter.setBrush( QBrush( Qt::white ) );
1426 imgPainter.setPen( QPen( Qt::black ) );
1427 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1428 _renderPolygon( &imgPainter, points, rings, context );
1429 imgPainter.end();
1430
1431 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1432 return;
1433
1434 //now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
1435 //(this avoids calling _renderPolygon twice, since that can be slow)
1436 imgPainter.begin( fillImage.get() );
1437 if ( !ignoreRings )
1438 {
1439 imgPainter.drawImage( 0, 0, *alphaImage );
1440 }
1441 else
1442 {
1443 //using ignore rings mode, so the alpha image can't be used
1444 //directly as the alpha channel contains polygon rings and we need
1445 //to draw now without any rings
1446 imgPainter.setBrush( QBrush( Qt::white ) );
1447 imgPainter.setPen( QPen( Qt::black ) );
1448 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1449 _renderPolygon( &imgPainter, points, nullptr, context );
1450 }
1451 imgPainter.end();
1452
1453 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1454 return;
1455
1456 //apply distance transform to image, uses the current color ramp to calculate final pixel colors
1457 double *dtArray = distanceTransform( fillImage.get(), context.renderContext() );
1458
1459 //copy distance transform values back to QImage, shading by appropriate color ramp
1460 dtArrayToQImage( dtArray, fillImage.get(), mColorType == Qgis::GradientColorSource::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
1461 context.renderContext(), useWholeShape, outputPixelMaxDist );
1462 if ( context.opacity() < 1 )
1463 {
1464 QgsImageOperation::multiplyOpacity( *fillImage, context.opacity(), context.renderContext().feedback() );
1465 }
1466
1467 //clean up some variables
1468 delete [] dtArray;
1469
1470 //apply blur if desired
1471 if ( blurRadius > 0 )
1472 {
1473 QgsImageOperation::stackBlur( *fillImage, blurRadius, false, context.renderContext().feedback() );
1474 }
1475
1476 //apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1477 imgPainter.begin( fillImage.get() );
1478 imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
1479 imgPainter.drawImage( 0, 0, *alphaImage );
1480 imgPainter.end();
1481 //we're finished with the alpha channel image now
1482 alphaImage.reset();
1483
1484 //draw shapeburst image in correct place in the destination painter
1485
1486 QgsScopedQPainterState painterState( p );
1487 QPointF offset = mOffset;
1489 {
1491 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::Property::Offset, context.renderContext().expressionContext(), QString() );
1492 bool ok = false;
1493 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1494 if ( ok )
1495 offset = res;
1496 }
1497 if ( !offset.isNull() )
1498 {
1499 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1500 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1501 p->translate( offset );
1502 }
1503
1504 p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
1505
1506 if ( !offset.isNull() )
1507 {
1508 p->translate( -offset );
1509 }
1510}
1511
1512//fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
1513
1514/* distance transform of a 1d function using squared distance */
1515void QgsShapeburstFillSymbolLayer::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
1516{
1517 int k = 0;
1518 v[0] = 0;
1519 z[0] = -INF;
1520 z[1] = + INF;
1521 for ( int q = 1; q <= n - 1; q++ )
1522 {
1523 double s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1524 while ( s <= z[k] )
1525 {
1526 k--;
1527 s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1528 }
1529 k++;
1530 v[k] = q;
1531 z[k] = s;
1532 z[k + 1] = + INF;
1533 }
1534
1535 k = 0;
1536 for ( int q = 0; q <= n - 1; q++ )
1537 {
1538 while ( z[k + 1] < q )
1539 k++;
1540 d[q] = static_cast< double >( q - v[k] ) * ( q - v[k] ) + f[v[k]];
1541 }
1542}
1543
1544/* distance transform of 2d function using squared distance */
1545void QgsShapeburstFillSymbolLayer::distanceTransform2d( double *im, int width, int height, QgsRenderContext &context )
1546{
1547 int maxDimension = std::max( width, height );
1548 double *f = new double[ maxDimension ];
1549 int *v = new int[ maxDimension ];
1550 double *z = new double[ maxDimension + 1 ];
1551 double *d = new double[ maxDimension ];
1552
1553 // transform along columns
1554 for ( int x = 0; x < width; x++ )
1555 {
1556 if ( context.renderingStopped() )
1557 break;
1558
1559 for ( int y = 0; y < height; y++ )
1560 {
1561 f[y] = im[ x + static_cast< std::size_t>( y ) * width ];
1562 }
1563 distanceTransform1d( f, height, v, z, d );
1564 for ( int y = 0; y < height; y++ )
1565 {
1566 im[ x + static_cast< std::size_t>( y ) * width ] = d[y];
1567 }
1568 }
1569
1570 // transform along rows
1571 for ( int y = 0; y < height; y++ )
1572 {
1573 if ( context.renderingStopped() )
1574 break;
1575
1576 for ( int x = 0; x < width; x++ )
1577 {
1578 f[x] = im[ x + static_cast< std::size_t>( y ) * width ];
1579 }
1580 distanceTransform1d( f, width, v, z, d );
1581 for ( int x = 0; x < width; x++ )
1582 {
1583 im[ x + static_cast< std::size_t>( y ) * width ] = d[x];
1584 }
1585 }
1586
1587 delete [] d;
1588 delete [] f;
1589 delete [] v;
1590 delete [] z;
1591}
1592
1593/* distance transform of a binary QImage */
1594double *QgsShapeburstFillSymbolLayer::distanceTransform( QImage *im, QgsRenderContext &context )
1595{
1596 int width = im->width();
1597 int height = im->height();
1598
1599 double *dtArray = new double[static_cast< std::size_t>( width ) * height];
1600
1601 //load qImage to array
1602 QRgb tmpRgb;
1603 std::size_t idx = 0;
1604 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1605 {
1606 if ( context.renderingStopped() )
1607 break;
1608
1609 const QRgb *scanLine = reinterpret_cast< const QRgb * >( im->constScanLine( heightIndex ) );
1610 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1611 {
1612 tmpRgb = scanLine[widthIndex];
1613 if ( qRed( tmpRgb ) == 0 )
1614 {
1615 //black pixel, so zero distance
1616 dtArray[ idx ] = 0;
1617 }
1618 else
1619 {
1620 //white pixel, so initially set distance as infinite
1621 dtArray[ idx ] = INF;
1622 }
1623 idx++;
1624 }
1625 }
1626
1627 //calculate squared distance transform
1628 distanceTransform2d( dtArray, width, height, context );
1629
1630 return dtArray;
1631}
1632
1633void QgsShapeburstFillSymbolLayer::dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, QgsRenderContext &context, bool useWholeShape, int maxPixelDistance )
1634{
1635 int width = im->width();
1636 int height = im->height();
1637
1638 //find maximum distance value
1639 double maxDistanceValue;
1640
1641 if ( useWholeShape )
1642 {
1643 //no max distance specified in symbol properties, so calculate from maximum value in distance transform results
1644 double dtMaxValue = array[0];
1645 for ( std::size_t i = 1; i < static_cast< std::size_t >( width ) * height; ++i )
1646 {
1647 if ( array[i] > dtMaxValue )
1648 {
1649 dtMaxValue = array[i];
1650 }
1651 }
1652
1653 //values in distance transform are squared
1654 maxDistanceValue = std::sqrt( dtMaxValue );
1655 }
1656 else
1657 {
1658 //use max distance set in symbol properties
1659 maxDistanceValue = maxPixelDistance;
1660 }
1661
1662 //update the pixels in the provided QImage
1663 std::size_t idx = 0;
1664 double squaredVal = 0;
1665 double pixVal = 0;
1666
1667 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1668 {
1669 if ( context.renderingStopped() )
1670 break;
1671
1672 QRgb *scanLine = reinterpret_cast< QRgb * >( im->scanLine( heightIndex ) );
1673 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1674 {
1675 //result of distance transform
1676 squaredVal = array[idx];
1677
1678 //scale result to fit in the range [0, 1]
1679 if ( maxDistanceValue > 0 )
1680 {
1681 pixVal = squaredVal > 0 ? std::min( ( std::sqrt( squaredVal ) / maxDistanceValue ), 1.0 ) : 0;
1682 }
1683 else
1684 {
1685 pixVal = 1.0;
1686 }
1687
1688 //convert value to color from ramp
1689 //premultiply ramp color since we are storing this in a ARGB32_Premultiplied QImage
1690 scanLine[widthIndex] = qPremultiply( ramp->color( pixVal ).rgba() );
1691 idx++;
1692 }
1693 }
1694}
1695
1697{
1698 QVariantMap map;
1699 map[u"color"_s] = QgsColorUtils::colorToString( mColor );
1700 map[u"gradient_color2"_s] = QgsColorUtils::colorToString( mColor2 );
1701 map[u"color_type"_s] = QString::number( static_cast< int >( mColorType ) );
1702 map[u"blur_radius"_s] = QString::number( mBlurRadius );
1703 map[u"use_whole_shape"_s] = QString::number( mUseWholeShape );
1704 map[u"max_distance"_s] = QString::number( mMaxDistance );
1705 map[u"distance_unit"_s] = QgsUnitTypes::encodeUnit( mDistanceUnit );
1706 map[u"distance_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale );
1707 map[u"ignore_rings"_s] = QString::number( mIgnoreRings );
1708 map[u"offset"_s] = QgsSymbolLayerUtils::encodePoint( mOffset );
1709 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1710 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1711 if ( mGradientRamp )
1712 {
1713 map.insert( mGradientRamp->properties() );
1714 }
1715
1716 return map;
1717}
1718
1720{
1721 auto sl = std::make_unique< QgsShapeburstFillSymbolLayer >( mColor, mColor2, mColorType, mBlurRadius, mUseWholeShape, mMaxDistance );
1722 if ( mGradientRamp )
1723 {
1724 sl->setColorRamp( mGradientRamp->clone() );
1725 }
1726 sl->setDistanceUnit( mDistanceUnit );
1727 sl->setDistanceMapUnitScale( mDistanceMapUnitScale );
1728 sl->setIgnoreRings( mIgnoreRings );
1729 sl->setOffset( mOffset );
1730 sl->setOffsetUnit( mOffsetUnit );
1731 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1732 copyCommonProperties( sl.get() );
1733 return sl.release();
1734}
1735
1737{
1738 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1739 return offsetBleed;
1740}
1741
1746
1748{
1749 mDistanceUnit = unit;
1750 mOffsetUnit = unit;
1751}
1752
1754{
1755 if ( mDistanceUnit == mOffsetUnit )
1756 {
1757 return mDistanceUnit;
1758 }
1760}
1761
1763{
1764 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
1765 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
1766}
1767
1769{
1770 mDistanceMapUnitScale = scale;
1771 mOffsetMapUnitScale = scale;
1772}
1773
1775{
1776 if ( mDistanceMapUnitScale == mOffsetMapUnitScale )
1777 {
1778 return mDistanceMapUnitScale;
1779 }
1780 return QgsMapUnitScale();
1781}
1782
1783
1784//QgsImageFillSymbolLayer
1785
1789
1791
1792void QgsImageFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1793{
1794 QPainter *p = context.renderContext().painter();
1795 if ( !p )
1796 {
1797 return;
1798 }
1799
1801 applyDataDefinedSettings( context );
1802
1803 p->setPen( QPen( Qt::NoPen ) );
1804
1805 QTransform bkTransform = mBrush.transform();
1806 if ( applyBrushTransformFromContext( &context ) && !context.renderContext().textureOrigin().isNull() )
1807 {
1808 QPointF leftCorner = context.renderContext().textureOrigin();
1809 QTransform t = mBrush.transform();
1810 t.translate( leftCorner.x(), leftCorner.y() );
1811 mBrush.setTransform( t );
1812 }
1813 else
1814 {
1815 QTransform t = mBrush.transform();
1816 t.translate( 0, 0 );
1817 mBrush.setTransform( t );
1818 }
1819
1820 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1821 if ( useSelectedColor )
1822 {
1823 QColor selColor = context.renderContext().selectionColor();
1824 p->setBrush( QBrush( selColor ) );
1825 _renderPolygon( p, points, rings, context );
1826 }
1827
1828 if ( !qgsDoubleNear( mNextAngle, 0.0 ) )
1829 {
1830 QTransform t = mBrush.transform();
1831 t.rotate( mNextAngle );
1832 mBrush.setTransform( t );
1833 }
1834 p->setBrush( mBrush );
1835 _renderPolygon( p, points, rings, context );
1836
1837 mBrush.setTransform( bkTransform );
1838}
1839
1844
1849
1854
1859
1870
1872{
1873 return Qt::SolidLine;
1874#if 0
1875 if ( !mStroke )
1876 {
1877 return Qt::SolidLine;
1878 }
1879 else
1880 {
1881 return mStroke->dxfPenStyle();
1882 }
1883#endif //0
1884}
1885
1887{
1888 QVariantMap map;
1889 map.insert( u"coordinate_reference"_s, QgsSymbolLayerUtils::encodeCoordinateReference( mCoordinateReference ) );
1890 return map;
1891}
1892
1894{
1895 //coordinate reference
1898 {
1899 bool ok = false;
1900 QString string = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::CoordinateMode, context->renderContext().expressionContext(), QString(), &ok );
1901 if ( ok )
1902 {
1904 if ( !ok )
1906 }
1907 }
1908
1910}
1911
1912
1913//QgsSVGFillSymbolLayer
1914
1917 , mPatternWidth( width )
1918{
1919 mStrokeWidth = 0.3;
1920 mAngle = angle;
1921 mColor = QColor( 255, 255, 255 );
1923}
1924
1925QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray &svgData, double width, double angle )
1927 , mPatternWidth( width )
1928 , mSvgData( svgData )
1929{
1930 storeViewBox();
1931 mStrokeWidth = 0.3;
1932 mAngle = angle;
1933 mColor = QColor( 255, 255, 255 );
1934 setDefaultSvgParams();
1935}
1936
1938
1940{
1942 mPatternWidthUnit = unit;
1943 mSvgStrokeWidthUnit = unit;
1944 mStrokeWidthUnit = unit;
1945 if ( mStroke )
1946 mStroke->setOutputUnit( unit );
1947}
1948
1950{
1952 if ( mPatternWidthUnit != unit || mSvgStrokeWidthUnit != unit || mStrokeWidthUnit != unit )
1953 {
1955 }
1956 return unit;
1957}
1958
1960{
1962 mPatternWidthMapUnitScale = scale;
1963 mSvgStrokeWidthMapUnitScale = scale;
1964}
1965
1967{
1968 if ( QgsImageFillSymbolLayer::mapUnitScale() == mPatternWidthMapUnitScale &&
1969 mPatternWidthMapUnitScale == mSvgStrokeWidthMapUnitScale &&
1970 mSvgStrokeWidthMapUnitScale == mStrokeWidthMapUnitScale )
1971 {
1972 return mPatternWidthMapUnitScale;
1973 }
1974 return QgsMapUnitScale();
1975}
1976
1977void QgsSVGFillSymbolLayer::setSvgFilePath( const QString &svgPath )
1978{
1979 mSvgData = QgsApplication::svgCache()->getImageData( svgPath );
1980 storeViewBox();
1981
1982 mSvgFilePath = svgPath;
1983 setDefaultSvgParams();
1984}
1985
1987{
1988 QByteArray data;
1989 double width = 20;
1990 QString svgFilePath;
1991 double angle = 0.0;
1992
1993 if ( properties.contains( u"width"_s ) )
1994 {
1995 width = properties[u"width"_s].toDouble();
1996 }
1997 if ( properties.contains( u"svgFile"_s ) )
1998 {
1999 svgFilePath = properties[u"svgFile"_s].toString();
2000 }
2001 if ( properties.contains( u"angle"_s ) )
2002 {
2003 angle = properties[u"angle"_s].toDouble();
2004 }
2005
2006 std::unique_ptr< QgsSVGFillSymbolLayer > symbolLayer;
2007 if ( !svgFilePath.isEmpty() )
2008 {
2009 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( svgFilePath, width, angle );
2010 }
2011 else
2012 {
2013 if ( properties.contains( u"data"_s ) )
2014 {
2015 data = QByteArray::fromHex( properties[u"data"_s].toString().toLocal8Bit() );
2016 }
2017 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( data, width, angle );
2018 }
2019
2020 //svg parameters
2021 if ( properties.contains( u"svgFillColor"_s ) )
2022 {
2023 //pre 2.5 projects used "svgFillColor"
2024 symbolLayer->setSvgFillColor( QgsColorUtils::colorFromString( properties[u"svgFillColor"_s].toString() ) );
2025 }
2026 else if ( properties.contains( u"color"_s ) )
2027 {
2028 symbolLayer->setSvgFillColor( QgsColorUtils::colorFromString( properties[u"color"_s].toString() ) );
2029 }
2030 if ( properties.contains( u"svgOutlineColor"_s ) )
2031 {
2032 //pre 2.5 projects used "svgOutlineColor"
2033 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[u"svgOutlineColor"_s].toString() ) );
2034 }
2035 else if ( properties.contains( u"outline_color"_s ) )
2036 {
2037 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[u"outline_color"_s].toString() ) );
2038 }
2039 else if ( properties.contains( u"line_color"_s ) )
2040 {
2041 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[u"line_color"_s].toString() ) );
2042 }
2043 if ( properties.contains( u"svgOutlineWidth"_s ) )
2044 {
2045 //pre 2.5 projects used "svgOutlineWidth"
2046 symbolLayer->setSvgStrokeWidth( properties[u"svgOutlineWidth"_s].toDouble() );
2047 }
2048 else if ( properties.contains( u"outline_width"_s ) )
2049 {
2050 symbolLayer->setSvgStrokeWidth( properties[u"outline_width"_s].toDouble() );
2051 }
2052 else if ( properties.contains( u"line_width"_s ) )
2053 {
2054 symbolLayer->setSvgStrokeWidth( properties[u"line_width"_s].toDouble() );
2055 }
2056
2057 //units
2058 if ( properties.contains( u"pattern_width_unit"_s ) )
2059 {
2060 symbolLayer->setPatternWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"pattern_width_unit"_s].toString() ) );
2061 }
2062 if ( properties.contains( u"pattern_width_map_unit_scale"_s ) )
2063 {
2064 symbolLayer->setPatternWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"pattern_width_map_unit_scale"_s].toString() ) );
2065 }
2066 if ( properties.contains( u"svg_outline_width_unit"_s ) )
2067 {
2068 symbolLayer->setSvgStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"svg_outline_width_unit"_s].toString() ) );
2069 }
2070 if ( properties.contains( u"svg_outline_width_map_unit_scale"_s ) )
2071 {
2072 symbolLayer->setSvgStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"svg_outline_width_map_unit_scale"_s].toString() ) );
2073 }
2074 if ( properties.contains( u"outline_width_unit"_s ) )
2075 {
2076 symbolLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"outline_width_unit"_s].toString() ) );
2077 }
2078 if ( properties.contains( u"outline_width_map_unit_scale"_s ) )
2079 {
2080 symbolLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"outline_width_map_unit_scale"_s].toString() ) );
2081 }
2082
2083 if ( properties.contains( u"parameters"_s ) )
2084 {
2085 const QVariantMap parameters = properties[u"parameters"_s].toMap();
2086 symbolLayer->setParameters( QgsProperty::variantMapToPropertyMap( parameters ) );
2087 }
2088
2089 symbolLayer->restoreOldDataDefinedProperties( properties );
2090
2091 return symbolLayer.release();
2092}
2093
2094void QgsSVGFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2095{
2096 QVariantMap::iterator it = properties.find( u"svgFile"_s );
2097 if ( it != properties.end() )
2098 {
2099 if ( saving )
2100 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2101 else
2102 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2103 }
2104}
2105
2107{
2108 return u"SVGFill"_s;
2109}
2110
2111void QgsSVGFillSymbolLayer::applyPattern( QBrush &brush, const QString &svgFilePath, double patternWidth, Qgis::RenderUnit patternWidthUnit,
2112 const QColor &svgFillColor, const QColor &svgStrokeColor, double svgStrokeWidth,
2113 Qgis::RenderUnit svgStrokeWidthUnit, const QgsSymbolRenderContext &context,
2114 const QgsMapUnitScale &patternWidthMapUnitScale, const QgsMapUnitScale &svgStrokeWidthMapUnitScale, const QgsStringMap svgParameters )
2115{
2116 if ( mSvgViewBox.isNull() )
2117 {
2118 return;
2119 }
2120
2122
2123 if ( static_cast< int >( size ) < 1.0 || 10000.0 < size )
2124 {
2125 brush.setTextureImage( QImage() );
2126 }
2127 else
2128 {
2129 bool fitsInCache = true;
2131 QImage patternImage = QgsApplication::svgCache()->svgAsImage( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2132 context.renderContext().scaleFactor(), fitsInCache, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), svgParameters );
2133 if ( !fitsInCache )
2134 {
2135 QPicture patternPict = QgsApplication::svgCache()->svgAsPicture( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2136 context.renderContext().scaleFactor(), false, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
2137 double hwRatio = 1.0;
2138 if ( patternPict.width() > 0 )
2139 {
2140 hwRatio = static_cast< double >( patternPict.height() ) / static_cast< double >( patternPict.width() );
2141 }
2142 patternImage = QImage( static_cast< int >( size ), static_cast< int >( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
2143 patternImage.fill( 0 ); // transparent background
2144
2145 QPainter p( &patternImage );
2146 p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
2147 }
2148
2149 QTransform brushTransform;
2150 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2151 {
2152 QImage transparentImage = patternImage.copy();
2153 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2154 brush.setTextureImage( transparentImage );
2155 }
2156 else
2157 {
2158 brush.setTextureImage( patternImage );
2159 }
2160 brush.setTransform( brushTransform );
2161 }
2162}
2163
2165{
2166 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2167
2168 applyPattern( mBrush, mSvgFilePath, mPatternWidth, mPatternWidthUnit, mColor, mSvgStrokeColor, mSvgStrokeWidth, mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2169
2170 if ( mStroke )
2171 {
2172 mStroke->setRenderHints( mStroke->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
2173 mStroke->startRender( context.renderContext(), context.fields() );
2174 }
2175}
2176
2178{
2179 if ( mStroke )
2180 {
2181 mStroke->stopRender( context.renderContext() );
2182 }
2183}
2184
2185void QgsSVGFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
2186{
2187 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
2188
2189 if ( mStroke )
2190 {
2191 const bool useSelectedColor = SELECT_FILL_BORDER && shouldRenderUsingSelectionColor( context );
2192 mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, useSelectedColor );
2193 if ( rings )
2194 {
2195 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
2196 {
2197 mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, useSelectedColor );
2198 }
2199 }
2200 }
2201}
2202
2204{
2205 QVariantMap map;
2206 if ( !mSvgFilePath.isEmpty() )
2207 {
2208 map.insert( u"svgFile"_s, mSvgFilePath );
2209 }
2210 else
2211 {
2212 map.insert( u"data"_s, QString( mSvgData.toHex() ) );
2213 }
2214
2215 map.insert( u"width"_s, QString::number( mPatternWidth ) );
2216 map.insert( u"angle"_s, QString::number( mAngle ) );
2217
2218 //svg parameters
2219 map.insert( u"color"_s, QgsColorUtils::colorToString( mColor ) );
2220 map.insert( u"outline_color"_s, QgsColorUtils::colorToString( mSvgStrokeColor ) );
2221 map.insert( u"outline_width"_s, QString::number( mSvgStrokeWidth ) );
2222
2223 //units
2224 map.insert( u"pattern_width_unit"_s, QgsUnitTypes::encodeUnit( mPatternWidthUnit ) );
2225 map.insert( u"pattern_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mPatternWidthMapUnitScale ) );
2226 map.insert( u"svg_outline_width_unit"_s, QgsUnitTypes::encodeUnit( mSvgStrokeWidthUnit ) );
2227 map.insert( u"svg_outline_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mSvgStrokeWidthMapUnitScale ) );
2228 map.insert( u"outline_width_unit"_s, QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
2229 map.insert( u"outline_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
2230
2231 map[u"parameters"_s] = QgsProperty::propertyMapToVariantMap( mParameters );
2232
2233 return map;
2234}
2235
2237{
2238 std::unique_ptr< QgsSVGFillSymbolLayer > clonedLayer;
2239 if ( !mSvgFilePath.isEmpty() )
2240 {
2241 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgFilePath, mPatternWidth, mAngle );
2242 clonedLayer->setSvgFillColor( mColor );
2243 clonedLayer->setSvgStrokeColor( mSvgStrokeColor );
2244 clonedLayer->setSvgStrokeWidth( mSvgStrokeWidth );
2245 }
2246 else
2247 {
2248 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgData, mPatternWidth, mAngle );
2249 }
2250
2251 clonedLayer->setPatternWidthUnit( mPatternWidthUnit );
2252 clonedLayer->setPatternWidthMapUnitScale( mPatternWidthMapUnitScale );
2253 clonedLayer->setSvgStrokeWidthUnit( mSvgStrokeWidthUnit );
2254 clonedLayer->setSvgStrokeWidthMapUnitScale( mSvgStrokeWidthMapUnitScale );
2255 clonedLayer->setStrokeWidthUnit( mStrokeWidthUnit );
2256 clonedLayer->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
2257
2258 clonedLayer->setParameters( mParameters );
2259
2260 if ( mStroke )
2261 {
2262 clonedLayer->setSubSymbol( mStroke->clone() );
2263 }
2264 copyCommonProperties( clonedLayer.get() );
2265 return clonedLayer.release();
2266}
2267
2268void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2269{
2270 QgsSldExportContext context;
2271 context.setExtraProperties( props );
2272 toSld( doc, element, context );
2273}
2274
2275bool QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
2276{
2277 const QVariantMap props = context.extraProperties();
2278 QDomElement symbolizerElem = doc.createElement( u"se:PolygonSymbolizer"_s );
2279 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
2280 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
2281 element.appendChild( symbolizerElem );
2282
2283 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
2284
2285 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
2286 symbolizerElem.appendChild( fillElem );
2287
2288 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
2289 fillElem.appendChild( graphicFillElem );
2290
2291 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
2292 graphicFillElem.appendChild( graphicElem );
2293
2294 if ( !mSvgFilePath.isEmpty() )
2295 {
2296 // encode a parametric SVG reference
2297 double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
2298 double strokeWidth = QgsSymbolLayerUtils::rescaleUom( mSvgStrokeWidth, mSvgStrokeWidthUnit, props );
2299 QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgStrokeColor, strokeWidth, context );
2300 }
2301 else
2302 {
2303 // TODO: create svg from data
2304 // <se:InlineContent>
2305 // watch out for https://osgeo-org.atlassian.net/browse/GEOT-5206 !
2306 context.pushWarning( QObject::tr( "Exporting embedded SVG content to SLD is not supported" ) );
2307 }
2308
2309 // <Rotation>
2310 QString angleFunc;
2311 bool ok;
2312 double angle = props.value( u"angle"_s, u"0"_s ).toDouble( &ok );
2313 if ( !ok )
2314 {
2315 angleFunc = u"%1 + %2"_s.arg( props.value( u"angle"_s, u"0"_s ).toString() ).arg( mAngle );
2316 }
2317 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2318 {
2319 angleFunc = QString::number( angle + mAngle );
2320 }
2321 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc, context );
2322
2323 if ( mStroke )
2324 {
2325 // the stroke sub symbol should be stored within the Stroke element,
2326 // but it will be stored in a separated LineSymbolizer because it could
2327 // have more than one layer
2328 mStroke->toSld( doc, element, context );
2329 }
2330 return true;
2331}
2332
2334{
2335 return mPatternWidthUnit == Qgis::RenderUnit::MapUnits || mPatternWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2336 || mSvgStrokeWidthUnit == Qgis::RenderUnit::MapUnits || mSvgStrokeWidthUnit == Qgis::RenderUnit::MetersInMapUnits;
2337}
2338
2340{
2341 return mStroke.get();
2342}
2343
2345{
2346 if ( !symbol ) //unset current stroke
2347 {
2348 mStroke.reset( nullptr );
2349 return true;
2350 }
2351
2352 if ( symbol->type() != Qgis::SymbolType::Line )
2353 {
2354 delete symbol;
2355 return false;
2356 }
2357
2358 QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
2359 if ( lineSymbol )
2360 {
2361 mStroke.reset( lineSymbol );
2362 return true;
2363 }
2364
2365 delete symbol;
2366 return false;
2367}
2368
2370{
2371 if ( mStroke && mStroke->symbolLayer( 0 ) )
2372 {
2373 double subLayerBleed = mStroke->symbolLayer( 0 )->estimateMaxBleed( context );
2374 return subLayerBleed;
2375 }
2376 return 0;
2377}
2378
2380{
2381 Q_UNUSED( context )
2382 if ( !mStroke )
2383 {
2384 return QColor( Qt::black );
2385 }
2386 return mStroke->color();
2387}
2388
2389QSet<QString> QgsSVGFillSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2390{
2391 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2392 if ( mStroke )
2393 attr.unite( mStroke->usedAttributes( context ) );
2394 return attr;
2395}
2396
2398{
2400 return true;
2401 if ( mStroke && mStroke->hasDataDefinedProperties() )
2402 return true;
2403 return false;
2404}
2405
2407{
2408 QString path, mimeType;
2409 QColor fillColor, strokeColor;
2410 Qt::PenStyle penStyle;
2411 double size, strokeWidth;
2412
2413 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2414 if ( fillElem.isNull() )
2415 return nullptr;
2416
2417 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2418 if ( graphicFillElem.isNull() )
2419 return nullptr;
2420
2421 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
2422 if ( graphicElem.isNull() )
2423 return nullptr;
2424
2425 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
2426 return nullptr;
2427
2428 if ( mimeType != "image/svg+xml"_L1 )
2429 return nullptr;
2430
2431 QgsSymbolLayerUtils::lineFromSld( graphicElem, penStyle, strokeColor, strokeWidth );
2432
2433 double scaleFactor = 1.0;
2434 const QString uom = element.attribute( u"uom"_s );
2435 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2436 size = size * scaleFactor;
2437 strokeWidth = strokeWidth * scaleFactor;
2438
2439 double angle = 0.0;
2440 QString angleFunc;
2441 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2442 {
2443 bool ok;
2444 double d = angleFunc.toDouble( &ok );
2445 if ( ok )
2446 angle = d;
2447 }
2448
2449 auto sl = std::make_unique< QgsSVGFillSymbolLayer >( path, size, angle );
2450 sl->setOutputUnit( sldUnitSize );
2451 sl->setSvgFillColor( fillColor );
2452 sl->setSvgStrokeColor( strokeColor );
2453 sl->setSvgStrokeWidth( strokeWidth );
2454
2455 // try to get the stroke
2456 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
2457 if ( !strokeElem.isNull() )
2458 {
2459 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createLineLayerFromSld( strokeElem );
2460 if ( l )
2461 {
2462 QgsSymbolLayerList layers;
2463 layers.append( l.release() );
2464 sl->setSubSymbol( new QgsLineSymbol( layers ) );
2465 }
2466 }
2467
2468 return sl.release();
2469}
2470
2472{
2476 {
2477 return; //no data defined settings
2478 }
2479
2481 {
2484 }
2485
2486 double width = mPatternWidth;
2488 {
2489 context.setOriginalValueVariable( mPatternWidth );
2490 width = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::Width, context.renderContext().expressionContext(), mPatternWidth );
2491 }
2492 QString svgFile = mSvgFilePath;
2494 {
2495 context.setOriginalValueVariable( mSvgFilePath );
2497 context.renderContext().pathResolver() );
2498 }
2499 QColor svgFillColor = mColor;
2501 {
2504 }
2505 QColor svgStrokeColor = mSvgStrokeColor;
2507 {
2508 context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2510 }
2511 double strokeWidth = mSvgStrokeWidth;
2513 {
2514 context.setOriginalValueVariable( mSvgStrokeWidth );
2515 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), mSvgStrokeWidth );
2516 }
2517 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2518
2519 applyPattern( mBrush, svgFile, width, mPatternWidthUnit, svgFillColor, svgStrokeColor, strokeWidth,
2520 mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2521
2522}
2523
2524void QgsSVGFillSymbolLayer::storeViewBox()
2525{
2526 if ( !mSvgData.isEmpty() )
2527 {
2528 QSvgRenderer r( mSvgData );
2529 if ( r.isValid() )
2530 {
2531 mSvgViewBox = r.viewBoxF();
2532 return;
2533 }
2534 }
2535
2536 mSvgViewBox = QRectF();
2537}
2538
2539void QgsSVGFillSymbolLayer::setDefaultSvgParams()
2540{
2541 if ( mSvgFilePath.isEmpty() )
2542 {
2543 return;
2544 }
2545
2546 bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
2547 bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
2548 QColor defaultFillColor, defaultStrokeColor;
2549 double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
2550 QgsApplication::svgCache()->containsParams( mSvgFilePath, hasFillParam, hasDefaultFillColor, defaultFillColor,
2551 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
2552 hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2553 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
2554 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
2555
2556 double newFillOpacity = hasFillOpacityParam ? mColor.alphaF() : 1.0;
2557 double newStrokeOpacity = hasStrokeOpacityParam ? mSvgStrokeColor.alphaF() : 1.0;
2558
2559 if ( hasDefaultFillColor )
2560 {
2561 mColor = defaultFillColor;
2562 mColor.setAlphaF( newFillOpacity );
2563 }
2564 if ( hasDefaultFillOpacity )
2565 {
2566 mColor.setAlphaF( defaultFillOpacity );
2567 }
2568 if ( hasDefaultStrokeColor )
2569 {
2570 mSvgStrokeColor = defaultStrokeColor;
2571 mSvgStrokeColor.setAlphaF( newStrokeOpacity );
2572 }
2573 if ( hasDefaultStrokeOpacity )
2574 {
2575 mSvgStrokeColor.setAlphaF( defaultStrokeOpacity );
2576 }
2577 if ( hasDefaultStrokeWidth )
2578 {
2579 mSvgStrokeWidth = defaultStrokeWidth;
2580 }
2581}
2582
2583void QgsSVGFillSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2584{
2585 mParameters = parameters;
2586}
2587
2588
2591{
2592 mFillLineSymbol = std::make_unique<QgsLineSymbol>( );
2593 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
2594}
2595
2597
2599{
2600 mFillLineSymbol->setWidth( w );
2601 mLineWidth = w;
2602}
2603
2605{
2606 mFillLineSymbol->setColor( c );
2607 mColor = c;
2608}
2609
2611{
2612 return mFillLineSymbol ? mFillLineSymbol->color() : mColor;
2613}
2614
2616{
2617 if ( !symbol )
2618 {
2619 return false;
2620 }
2621
2622 if ( symbol->type() == Qgis::SymbolType::Line )
2623 {
2624 mFillLineSymbol.reset( qgis::down_cast<QgsLineSymbol *>( symbol ) );
2625 return true;
2626 }
2627 delete symbol;
2628 return false;
2629}
2630
2632{
2633 return mFillLineSymbol.get();
2634}
2635
2637{
2638 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2639 if ( mFillLineSymbol )
2640 attr.unite( mFillLineSymbol->usedAttributes( context ) );
2641 return attr;
2642}
2643
2645{
2647 return true;
2648 if ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
2649 return true;
2650 return false;
2651}
2652
2654{
2655 installMasks( context, true );
2656
2657 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2658}
2659
2661{
2662 removeMasks( context, true );
2663
2664 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2665}
2666
2668{
2669
2670 double lineAngleRad { qDegreesToRadians( mLineAngle ) };
2671
2672 const int quadrant { static_cast<int>( lineAngleRad / M_PI_2 ) };
2673 Q_ASSERT( quadrant >= 0 && quadrant <= 3 );
2674
2675 switch ( quadrant )
2676 {
2677 case 0:
2678 {
2679 break;
2680 }
2681 case 1:
2682 {
2683 lineAngleRad -= M_PI / 2;
2684 break;
2685 }
2686 case 2:
2687 {
2688 lineAngleRad -= M_PI;
2689 break;
2690 }
2691 case 3:
2692 {
2693 lineAngleRad -= M_PI + M_PI_2;
2694 break;
2695 }
2696 }
2697
2698
2699 double distancePx { QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, {} ) };
2700
2701 QSize size { static_cast<int>( distancePx ), static_cast<int>( distancePx ) };
2702
2703 if ( static_cast<int>( mLineAngle ) % 90 != 0 )
2704 {
2705 size = QSize( static_cast<int>( distancePx / std::sin( lineAngleRad ) ), static_cast<int>( distancePx / std::cos( lineAngleRad ) ) );
2706 }
2707
2708 QPixmap pixmap( size );
2709 pixmap.fill( Qt::transparent );
2710 QPainter painter;
2711 painter.begin( &pixmap );
2712 painter.setRenderHint( QPainter::Antialiasing );
2713 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
2718 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
2719
2720 std::unique_ptr< QgsLinePatternFillSymbolLayer > layerClone( clone() );
2721 layerClone->setOffset( 0 );
2722 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
2723 painter.end();
2724 return pixmap.toImage();
2725}
2726
2728{
2729 return 0;
2730}
2731
2733{
2735 mDistanceUnit = unit;
2736 mLineWidthUnit = unit;
2737 mOffsetUnit = unit;
2738
2739 if ( mFillLineSymbol )
2740 mFillLineSymbol->setOutputUnit( unit );
2741}
2742
2744{
2746 if ( mDistanceUnit != unit || mLineWidthUnit != unit || ( mOffsetUnit != unit && mOffsetUnit != Qgis::RenderUnit::Percentage ) )
2747 {
2749 }
2750 return unit;
2751}
2752
2754{
2755 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
2756 || mLineWidthUnit == Qgis::RenderUnit::MapUnits || mLineWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2757 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
2758}
2759
2761{
2763 mDistanceMapUnitScale = scale;
2764 mLineWidthMapUnitScale = scale;
2765 mOffsetMapUnitScale = scale;
2766}
2767
2769{
2770 if ( QgsImageFillSymbolLayer::mapUnitScale() == mDistanceMapUnitScale &&
2771 mDistanceMapUnitScale == mLineWidthMapUnitScale &&
2772 mLineWidthMapUnitScale == mOffsetMapUnitScale )
2773 {
2774 return mDistanceMapUnitScale;
2775 }
2776 return QgsMapUnitScale();
2777}
2778
2780{
2781 auto patternLayer = std::make_unique< QgsLinePatternFillSymbolLayer >();
2782
2783 //default values
2784 double lineAngle = 45;
2785 double distance = 5;
2786 double lineWidth = 0.5;
2787 QColor color( Qt::black );
2788 double offset = 0.0;
2789
2790 if ( properties.contains( u"lineangle"_s ) )
2791 {
2792 //pre 2.5 projects used "lineangle"
2793 lineAngle = properties[u"lineangle"_s].toDouble();
2794 }
2795 else if ( properties.contains( u"angle"_s ) )
2796 {
2797 lineAngle = properties[u"angle"_s].toDouble();
2798 }
2799 patternLayer->setLineAngle( lineAngle );
2800
2801 if ( properties.contains( u"distance"_s ) )
2802 {
2803 distance = properties[u"distance"_s].toDouble();
2804 }
2805 patternLayer->setDistance( distance );
2806
2807 if ( properties.contains( u"linewidth"_s ) )
2808 {
2809 //pre 2.5 projects used "linewidth"
2810 lineWidth = properties[u"linewidth"_s].toDouble();
2811 }
2812 else if ( properties.contains( u"outline_width"_s ) )
2813 {
2814 lineWidth = properties[u"outline_width"_s].toDouble();
2815 }
2816 else if ( properties.contains( u"line_width"_s ) )
2817 {
2818 lineWidth = properties[u"line_width"_s].toDouble();
2819 }
2820 patternLayer->setLineWidth( lineWidth );
2821
2822 if ( properties.contains( u"color"_s ) )
2823 {
2824 color = QgsColorUtils::colorFromString( properties[u"color"_s].toString() );
2825 }
2826 else if ( properties.contains( u"outline_color"_s ) )
2827 {
2828 color = QgsColorUtils::colorFromString( properties[u"outline_color"_s].toString() );
2829 }
2830 else if ( properties.contains( u"line_color"_s ) )
2831 {
2832 color = QgsColorUtils::colorFromString( properties[u"line_color"_s].toString() );
2833 }
2834 patternLayer->setColor( color );
2835
2836 if ( properties.contains( u"offset"_s ) )
2837 {
2838 offset = properties[u"offset"_s].toDouble();
2839 }
2840 patternLayer->setOffset( offset );
2841
2842
2843 if ( properties.contains( u"distance_unit"_s ) )
2844 {
2845 patternLayer->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( properties[u"distance_unit"_s].toString() ) );
2846 }
2847 if ( properties.contains( u"distance_map_unit_scale"_s ) )
2848 {
2849 patternLayer->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"distance_map_unit_scale"_s].toString() ) );
2850 }
2851 if ( properties.contains( u"line_width_unit"_s ) )
2852 {
2853 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"line_width_unit"_s].toString() ) );
2854 }
2855 else if ( properties.contains( u"outline_width_unit"_s ) )
2856 {
2857 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"outline_width_unit"_s].toString() ) );
2858 }
2859 if ( properties.contains( u"line_width_map_unit_scale"_s ) )
2860 {
2861 patternLayer->setLineWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"line_width_map_unit_scale"_s].toString() ) );
2862 }
2863 if ( properties.contains( u"offset_unit"_s ) )
2864 {
2865 patternLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
2866 }
2867 if ( properties.contains( u"offset_map_unit_scale"_s ) )
2868 {
2869 patternLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
2870 }
2871 if ( properties.contains( u"outline_width_unit"_s ) )
2872 {
2873 patternLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"outline_width_unit"_s].toString() ) );
2874 }
2875 if ( properties.contains( u"outline_width_map_unit_scale"_s ) )
2876 {
2877 patternLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"outline_width_map_unit_scale"_s].toString() ) );
2878 }
2879 if ( properties.contains( u"coordinate_reference"_s ) )
2880 {
2881 patternLayer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[u"coordinate_reference"_s].toString() ) );
2882 }
2883 if ( properties.contains( u"clip_mode"_s ) )
2884 {
2885 patternLayer->setClipMode( QgsSymbolLayerUtils::decodeLineClipMode( properties.value( u"clip_mode"_s ).toString() ) );
2886 }
2887
2888 patternLayer->restoreOldDataDefinedProperties( properties );
2889
2890 return patternLayer.release();
2891}
2892
2894{
2895 return u"LinePatternFill"_s;
2896}
2897
2898bool QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double lineAngle, double distance )
2899{
2900 mBrush.setTextureImage( QImage() ); // set empty in case we have to return
2901
2902 if ( !mFillLineSymbol )
2903 {
2904 return true;
2905 }
2906 // We have to make a copy because marker intervals will have to be adjusted
2907 std::unique_ptr< QgsLineSymbol > fillLineSymbol( mFillLineSymbol->clone() );
2908 if ( !fillLineSymbol )
2909 {
2910 return true;
2911 }
2912
2913 const QgsRenderContext &ctx = context.renderContext();
2914 //double strokePixelWidth = lineWidth * QgsSymbolLayerUtils::pixelSizeScaleFactor( ctx, mLineWidthUnit, mLineWidthMapUnitScale );
2915 double outputPixelDist = ctx.convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
2916 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDist * mOffset / 100
2917 : ctx.convertToPainterUnits( mOffset, mOffsetUnit, mOffsetMapUnitScale );
2918
2919 // NOTE: this may need to be modified if we ever change from a forced rasterized/brush approach,
2920 // because potentially we may want to allow vector based line pattern fills where the first line
2921 // is offset by a large distance
2922
2923 // fix truncated pattern with larger offsets
2924 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDist );
2925 if ( outputPixelOffset > outputPixelDist / 2.0 )
2926 outputPixelOffset -= outputPixelDist;
2927
2928 // To get all patterns into image, we have to consider symbols size (estimateMaxBleed()).
2929 // For marker lines we have to get markers interval.
2930 double outputPixelBleed = 0;
2931 double outputPixelInterval = 0; // maximum interval
2932 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2933 {
2934 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2935 double outputPixelLayerBleed = layer->estimateMaxBleed( context.renderContext() );
2936 outputPixelBleed = std::max( outputPixelBleed, outputPixelLayerBleed );
2937
2938 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2939 if ( markerLineLayer )
2940 {
2941 double outputPixelLayerInterval = ctx.convertToPainterUnits( markerLineLayer->interval(), markerLineLayer->intervalUnit(), markerLineLayer->intervalMapUnitScale() );
2942
2943 // There may be multiple marker lines with different intervals.
2944 // In theory we should find the least common multiple, but that could be too
2945 // big (multiplication of intervals in the worst case).
2946 // Because patterns without small common interval would look strange, we
2947 // believe that the longest interval should usually be sufficient.
2948 outputPixelInterval = std::max( outputPixelInterval, outputPixelLayerInterval );
2949 }
2950 }
2951
2952 if ( outputPixelInterval > 0 )
2953 {
2954 // We have to adjust marker intervals to integer pixel size to get
2955 // repeatable pattern.
2956 double intervalScale = std::round( outputPixelInterval ) / outputPixelInterval;
2957 outputPixelInterval = std::round( outputPixelInterval );
2958
2959 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2960 {
2961 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2962
2963 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2964 if ( markerLineLayer )
2965 {
2966 markerLineLayer->setInterval( intervalScale * markerLineLayer->interval() );
2967 }
2968 }
2969 }
2970
2971 //create image
2972 int height, width;
2973 lineAngle = std::fmod( lineAngle, 360 );
2974 if ( lineAngle < 0 )
2975 lineAngle += 360;
2976 if ( qgsDoubleNear( lineAngle, 0 ) || qgsDoubleNear( lineAngle, 360 ) || qgsDoubleNear( lineAngle, 180 ) )
2977 {
2978 height = outputPixelDist;
2979 width = outputPixelInterval > 0 ? outputPixelInterval : height;
2980 }
2981 else if ( qgsDoubleNear( lineAngle, 90 ) || qgsDoubleNear( lineAngle, 270 ) )
2982 {
2983 width = outputPixelDist;
2984 height = outputPixelInterval > 0 ? outputPixelInterval : width;
2985 }
2986 else
2987 {
2988 height = outputPixelDist / std::cos( lineAngle * M_PI / 180 ); //keep perpendicular distance between lines constant
2989 width = outputPixelDist / std::sin( lineAngle * M_PI / 180 );
2990
2991 // recalculate real angle and distance after rounding to pixels
2992 lineAngle = 180 * std::atan2( static_cast< double >( height ), static_cast< double >( width ) ) / M_PI;
2993 if ( lineAngle < 0 )
2994 {
2995 lineAngle += 360.;
2996 }
2997
2998 height = std::abs( height );
2999 width = std::abs( width );
3000
3001 outputPixelDist = std::abs( height * std::cos( lineAngle * M_PI / 180 ) );
3002
3003 // Round offset to correspond to one pixel height, otherwise lines may
3004 // be shifted on tile border if offset falls close to pixel center
3005 int offsetHeight = static_cast< int >( std::round( outputPixelOffset / std::cos( lineAngle * M_PI / 180 ) ) );
3006 outputPixelOffset = offsetHeight * std::cos( lineAngle * M_PI / 180 );
3007 }
3008
3009 //depending on the angle, we might need to render into a larger image and use a subset of it
3010 double dx = 0;
3011 double dy = 0;
3012
3013 // Add buffer based on bleed but keep precisely the height/width ratio (angle)
3014 // thus we add integer multiplications of width and height covering the bleed
3015 int bufferMulti = static_cast< int >( std::max( std::ceil( outputPixelBleed / width ), std::ceil( outputPixelBleed / width ) ) );
3016
3017 // Always buffer at least once so that center of line marker in upper right corner
3018 // does not fall outside due to representation error
3019 bufferMulti = std::max( bufferMulti, 1 );
3020
3021 int xBuffer = width * bufferMulti;
3022 int yBuffer = height * bufferMulti;
3023 int innerWidth = width;
3024 int innerHeight = height;
3025 width += 2 * xBuffer;
3026 height += 2 * yBuffer;
3027
3028 //protect from zero width/height image and symbol layer from eating too much memory
3029 if ( width > 2000 || height > 2000 || width == 0 || height == 0 )
3030 {
3031 return false;
3032 }
3033
3034 QImage patternImage( width, height, QImage::Format_ARGB32 );
3035 patternImage.fill( 0 );
3036
3037 QPointF p1, p2, p3, p4, p5, p6;
3038 if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
3039 {
3040 p1 = QPointF( 0, yBuffer );
3041 p2 = QPointF( width, yBuffer );
3042 p3 = QPointF( 0, yBuffer + innerHeight );
3043 p4 = QPointF( width, yBuffer + innerHeight );
3044 }
3045 else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
3046 {
3047 p1 = QPointF( xBuffer, height );
3048 p2 = QPointF( xBuffer, 0 );
3049 p3 = QPointF( xBuffer + innerWidth, height );
3050 p4 = QPointF( xBuffer + innerWidth, 0 );
3051 }
3052 else if ( lineAngle > 0 && lineAngle < 90 )
3053 {
3054 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3055 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3056 p1 = QPointF( 0, height );
3057 p2 = QPointF( width, 0 );
3058 p3 = QPointF( -dx, height - dy );
3059 p4 = QPointF( width - dx, -dy );
3060 p5 = QPointF( dx, height + dy );
3061 p6 = QPointF( width + dx, dy );
3062 }
3063 else if ( lineAngle > 180 && lineAngle < 270 )
3064 {
3065 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3066 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3067 p1 = QPointF( width, 0 );
3068 p2 = QPointF( 0, height );
3069 p3 = QPointF( width - dx, -dy );
3070 p4 = QPointF( -dx, height - dy );
3071 p5 = QPointF( width + dx, dy );
3072 p6 = QPointF( dx, height + dy );
3073 }
3074 else if ( lineAngle > 90 && lineAngle < 180 )
3075 {
3076 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3077 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3078 p1 = QPointF( 0, 0 );
3079 p2 = QPointF( width, height );
3080 p5 = QPointF( dx, -dy );
3081 p6 = QPointF( width + dx, height - dy );
3082 p3 = QPointF( -dx, dy );
3083 p4 = QPointF( width - dx, height + dy );
3084 }
3085 else if ( lineAngle > 270 && lineAngle < 360 )
3086 {
3087 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3088 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3089 p1 = QPointF( width, height );
3090 p2 = QPointF( 0, 0 );
3091 p5 = QPointF( width + dx, height - dy );
3092 p6 = QPointF( dx, -dy );
3093 p3 = QPointF( width - dx, height + dy );
3094 p4 = QPointF( -dx, dy );
3095 }
3096
3097 if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
3098 {
3099 QPointF tempPt;
3100 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
3101 p3 = QPointF( tempPt.x(), tempPt.y() );
3102 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
3103 p4 = QPointF( tempPt.x(), tempPt.y() );
3104 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
3105 p5 = QPointF( tempPt.x(), tempPt.y() );
3106 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
3107 p6 = QPointF( tempPt.x(), tempPt.y() );
3108
3109 //update p1, p2 last
3110 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
3111 p1 = QPointF( tempPt.x(), tempPt.y() );
3112 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
3113 p2 = QPointF( tempPt.x(), tempPt.y() );
3114 }
3115
3116 QPainter p( &patternImage );
3117
3118#if 0
3119 // DEBUG: Draw rectangle
3120 p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
3121 QPen pen( QColor( Qt::black ) );
3122 pen.setWidthF( 0.1 );
3123 pen.setCapStyle( Qt::FlatCap );
3124 p.setPen( pen );
3125
3126 // To see this rectangle, comment buffer cut below.
3127 // Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
3128 QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 );
3129 p.drawPolygon( polygon );
3130
3131 polygon = QPolygon() << QPoint( xBuffer, yBuffer ) << QPoint( width - xBuffer - 1, yBuffer ) << QPoint( width - xBuffer - 1, height - yBuffer - 1 ) << QPoint( xBuffer, height - yBuffer - 1 ) << QPoint( xBuffer, yBuffer );
3132 p.drawPolygon( polygon );
3133#endif
3134
3135 // Use antialiasing because without antialiasing lines are rendered to the
3136 // right and below the mathematically defined points (not symmetrical)
3137 // and such tiles become useless for are filling
3138 p.setRenderHint( QPainter::Antialiasing, true );
3139
3140 // line rendering needs context for drawing on patternImage
3141 QgsRenderContext lineRenderContext;
3142 lineRenderContext.setPainter( &p );
3143 lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3144 QgsMapToPixel mtp( context.renderContext().mapToPixel().mapUnitsPerPixel() );
3145 lineRenderContext.setMapToPixel( mtp );
3146 lineRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3148 lineRenderContext.setDisabledSymbolLayersV2( context.renderContext().disabledSymbolLayersV2() );
3149
3150 fillLineSymbol->setRenderHints( fillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3151 fillLineSymbol->startRender( lineRenderContext, context.fields() );
3152
3153 QVector<QPolygonF> polygons;
3154 polygons.append( QPolygonF() << p1 << p2 );
3155 polygons.append( QPolygonF() << p3 << p4 );
3156 if ( !qgsDoubleNear( lineAngle, 0 ) && !qgsDoubleNear( lineAngle, 360 ) && !qgsDoubleNear( lineAngle, 90 ) && !qgsDoubleNear( lineAngle, 180 ) && !qgsDoubleNear( lineAngle, 270 ) )
3157 {
3158 polygons.append( QPolygonF() << p5 << p6 );
3159 }
3160
3161 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3162 for ( const QPolygonF &polygon : std::as_const( polygons ) )
3163 {
3164 fillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, useSelectedColor );
3165 }
3166
3167 fillLineSymbol->stopRender( lineRenderContext );
3168 p.end();
3169
3170 // Cut off the buffer
3171 patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
3172
3173 //set image to mBrush
3174 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3175 {
3176 QImage transparentImage = patternImage.copy();
3177 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3178 brush.setTextureImage( transparentImage );
3179 }
3180 else
3181 {
3182 brush.setTextureImage( patternImage );
3183 }
3184
3185 QTransform brushTransform;
3186 brush.setTransform( brushTransform );
3187
3188 return true;
3189}
3190
3192{
3193 // if we are using a vector based output, we need to render points as vectors
3194 // (OR if the line has data defined symbology, in which case we need to evaluate this line-by-line)
3195 mRenderUsingLines = context.forceVectorRendering()
3196 || ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
3199
3200 if ( !mRenderUsingLines )
3201 {
3202 // optimised render for screen only, use image based brush
3203 // (fallback to line rendering when pattern image will result in too large a memory footprint)
3204 mRenderUsingLines = !applyPattern( context, mBrush, mLineAngle, mDistance );
3205 }
3206
3207 if ( mRenderUsingLines && mFillLineSymbol )
3208 {
3209 mFillLineSymbol->setRenderHints( mFillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3210 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3211 mFillLineSymbolRenderStarted = true;
3212 }
3213}
3214
3216{
3217 if ( mFillLineSymbolRenderStarted )
3218 {
3219 mFillLineSymbol->stopRender( context.renderContext() );
3220 mFillLineSymbolRenderStarted = false;
3221 }
3222}
3223
3224void QgsLinePatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3225{
3226 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3227 if ( !useSelectedColor && !mRenderUsingLines )
3228 {
3229 // use image based brush for speed
3230 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3231 return;
3232 }
3233
3234 if ( !mFillLineSymbol )
3235 return;
3236
3237 if ( !mFillLineSymbolRenderStarted )
3238 {
3239 mFillLineSymbol->setRenderHints( mFillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3240 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3241 mFillLineSymbolRenderStarted = true;
3242 }
3243
3244 // vector based output - so draw line by line!
3245 QPainter *p = context.renderContext().painter();
3246 if ( !p )
3247 {
3248 return;
3249 }
3250
3251 double lineAngle = mLineAngle;
3253 {
3254 context.setOriginalValueVariable( mLineAngle );
3256 }
3257
3258 double distance = mDistance;
3260 {
3261 context.setOriginalValueVariable( mDistance );
3263 }
3264 // Clip distance to a reasonable distance to avoid app freezes
3265 double outputPixelDistance = context.renderContext().convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
3266
3267 double offset = mOffset;
3268 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDistance * offset / 100
3269 : context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3270
3271 // fix truncated pattern with larger offsets
3272 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDistance );
3273 if ( outputPixelOffset > outputPixelDistance / 2.0 )
3274 outputPixelOffset -= outputPixelDistance;
3275
3276 p->setPen( QPen( Qt::NoPen ) );
3277
3278 // if invalid parameters, skip out
3279 if ( qgsDoubleNear( distance, 0 ) )
3280 return;
3281
3282 // Clip distance to a reasonable distance to avoid app freezes
3283 outputPixelDistance = std::max(
3285 ? 0.1
3286 : 0.025,
3287 outputPixelDistance );
3288
3289 p->save();
3290
3291 Qgis::LineClipMode clipMode = mClipMode;
3293 {
3295 bool ok = false;
3296 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::LineClipping, context.renderContext().expressionContext(), QString(), &ok );
3297 if ( ok )
3298 {
3299 Qgis::LineClipMode decodedMode = QgsSymbolLayerUtils::decodeLineClipMode( valueString, &ok );
3300 if ( ok )
3301 clipMode = decodedMode;
3302 }
3303 }
3304
3305 std::unique_ptr< QgsPolygon > shapePolygon;
3306 std::unique_ptr< QgsGeometryEngine > shapeEngine;
3307 switch ( clipMode )
3308 {
3310 break;
3311
3313 {
3314 shapePolygon = std::make_unique< QgsPolygon >();
3315 std::unique_ptr< QgsLineString > fromPolygon = QgsLineString::fromQPolygonF( points );
3316 shapePolygon->setExteriorRing( fromPolygon.release() );
3317 if ( rings )
3318 {
3319 for ( const QPolygonF &ring : *rings )
3320 {
3321 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
3322 shapePolygon->addInteriorRing( fromRing.release() );
3323 }
3324 }
3325 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3326 shapeEngine->prepareGeometry();
3327 break;
3328 }
3329
3331 {
3332 QPainterPath path;
3333 path.addPolygon( points );
3334 if ( rings )
3335 {
3336 for ( const QPolygonF &ring : *rings )
3337 {
3338 path.addPolygon( ring );
3339 }
3340 }
3341 p->setClipPath( path, Qt::IntersectClip );
3342 break;
3343 }
3344 }
3345
3346 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3347 const QRectF boundingRect = points.boundingRect();
3348
3349 QTransform invertedRotateTransform;
3350 double left;
3351 double top;
3352 double right;
3353 double bottom;
3354
3355 QTransform transform;
3356 if ( applyBrushTransform )
3357 {
3358 // rotation applies around center of feature
3359 transform.translate( -boundingRect.center().x(),
3360 -boundingRect.center().y() );
3361 transform.rotate( lineAngle );
3362 transform.translate( boundingRect.center().x(),
3363 boundingRect.center().y() );
3364 }
3365 else
3366 {
3367 // rotation applies around top of viewport
3368 transform.rotate( lineAngle );
3369 }
3370
3371 const QRectF transformedBounds = transform.map( points ).boundingRect();
3372
3373 // bounds are expanded out a bit to account for maximum line width
3374 const double buffer = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFillLineSymbol.get(), context.renderContext() );
3375 left = transformedBounds.left() - buffer * 2;
3376 top = transformedBounds.top() - buffer * 2;
3377 right = transformedBounds.right() + buffer * 2;
3378 bottom = transformedBounds.bottom() + buffer * 2;
3379 invertedRotateTransform = transform.inverted();
3380
3381 if ( !applyBrushTransform )
3382 {
3383 top -= transformedBounds.top() - ( outputPixelDistance * std::floor( transformedBounds.top() / outputPixelDistance ) );
3384 }
3385
3387 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
3388 const bool needsExpressionContext = mFillLineSymbol->hasDataDefinedProperties();
3389
3390 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3392
3393 int currentLine = 0;
3394 for ( double currentY = top; currentY <= bottom; currentY += outputPixelDistance )
3395 {
3396 if ( context.renderContext().renderingStopped() )
3397 break;
3398
3399 if ( needsExpressionContext )
3400 scope->addVariable( QgsExpressionContextScope::StaticVariable( u"symbol_line_number"_s, ++currentLine, true ) );
3401
3402 double x1 = left;
3403 double y1 = currentY;
3404 double x2 = left;
3405 double y2 = currentY;
3406 invertedRotateTransform.map( left, currentY - outputPixelOffset, &x1, &y1 );
3407 invertedRotateTransform.map( right, currentY - outputPixelOffset, &x2, &y2 );
3408
3409 if ( shapeEngine )
3410 {
3411 QgsLineString ls( QgsPoint( x1, y1 ), QgsPoint( x2, y2 ) );
3412 std::unique_ptr< QgsAbstractGeometry > intersection( shapeEngine->intersection( &ls ) );
3413 for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
3414 {
3416 {
3417 mFillLineSymbol->renderPolyline( ls->asQPolygonF(), context.feature(), context.renderContext(), -1, useSelectedColor );
3418 }
3419 }
3420 }
3421 else
3422 {
3423 mFillLineSymbol->renderPolyline( QPolygonF() << QPointF( x1, y1 ) << QPointF( x2, y2 ), context.feature(), context.renderContext(), -1, useSelectedColor );
3424 }
3425 }
3426
3427 p->restore();
3428
3430}
3431
3433{
3434 QVariantMap map = QgsImageFillSymbolLayer::properties();
3435 map.insert( u"angle"_s, QString::number( mLineAngle ) );
3436 map.insert( u"distance"_s, QString::number( mDistance ) );
3437 map.insert( u"line_width"_s, QString::number( mLineWidth ) );
3438 map.insert( u"color"_s, QgsColorUtils::colorToString( mColor ) );
3439 map.insert( u"offset"_s, QString::number( mOffset ) );
3440 map.insert( u"distance_unit"_s, QgsUnitTypes::encodeUnit( mDistanceUnit ) );
3441 map.insert( u"line_width_unit"_s, QgsUnitTypes::encodeUnit( mLineWidthUnit ) );
3442 map.insert( u"offset_unit"_s, QgsUnitTypes::encodeUnit( mOffsetUnit ) );
3443 map.insert( u"distance_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale ) );
3444 map.insert( u"line_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mLineWidthMapUnitScale ) );
3445 map.insert( u"offset_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ) );
3446 map.insert( u"outline_width_unit"_s, QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3447 map.insert( u"outline_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3448 map.insert( u"clip_mode"_s, QgsSymbolLayerUtils::encodeLineClipMode( mClipMode ) );
3449 return map;
3450}
3451
3453{
3455 if ( mFillLineSymbol )
3456 {
3457 clonedLayer->setSubSymbol( mFillLineSymbol->clone() );
3458 }
3459 copyCommonProperties( clonedLayer );
3460 return clonedLayer;
3461}
3462
3463void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3464{
3465 QgsSldExportContext context;
3466 context.setExtraProperties( props );
3467 toSld( doc, element, context );
3468}
3469
3470bool QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
3471{
3472 const QVariantMap props = context.extraProperties();
3473 QDomElement symbolizerElem = doc.createElement( u"se:PolygonSymbolizer"_s );
3474 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
3475 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
3476 element.appendChild( symbolizerElem );
3477
3478 // <Geometry>
3479 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
3480
3481 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
3482 symbolizerElem.appendChild( fillElem );
3483
3484 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
3485 fillElem.appendChild( graphicFillElem );
3486
3487 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
3488 graphicFillElem.appendChild( graphicElem );
3489
3490 // Export to PNG (TODO: SVG)
3491 bool exportOk { false };
3492 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
3493 {
3494 const QImage image { toTiledPatternImage() };
3495 if ( ! image.isNull() )
3496 {
3497 const QFileInfo info { context.exportFilePath() };
3498 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( u"png"_s ) };
3499 pngPath = QgsFileUtils::uniquePath( pngPath );
3500 image.save( pngPath );
3501 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), u"image/png"_s, QColor(), image.height() );
3502 exportOk = true;
3503 }
3504 }
3505
3506 if ( ! exportOk )
3507 {
3508 //line properties must be inside the graphic definition
3509 QColor lineColor = mFillLineSymbol ? mFillLineSymbol->color() : QColor();
3510 double lineWidth = mFillLineSymbol ? mFillLineSymbol->width() : 0.0;
3511 lineWidth = QgsSymbolLayerUtils::rescaleUom( lineWidth, mLineWidthUnit, props );
3512 double distance = QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, props );
3513 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, u"horline"_s, QColor(), lineColor, Qt::SolidLine, context, lineWidth, distance );
3514
3515 // <Rotation>
3516 QString angleFunc;
3517 bool ok;
3518 double angle = props.value( u"angle"_s, u"0"_s ).toDouble( &ok );
3519 if ( !ok )
3520 {
3521 angleFunc = u"%1 + %2"_s.arg( props.value( u"angle"_s, u"0"_s ).toString() ).arg( mLineAngle );
3522 }
3523 else if ( !qgsDoubleNear( angle + mLineAngle, 0.0 ) )
3524 {
3525 angleFunc = QString::number( angle + mLineAngle );
3526 }
3527 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc, context );
3528
3529 // <se:Displacement>
3530 QPointF lineOffset( std::sin( mLineAngle ) * mOffset, std::cos( mLineAngle ) * mOffset );
3531 lineOffset = QgsSymbolLayerUtils::rescaleUom( lineOffset, mOffsetUnit, props );
3532 QgsSymbolLayerUtils::createDisplacementElement( doc, graphicElem, lineOffset );
3533 }
3534 return true;
3535}
3536
3537QString QgsLinePatternFillSymbolLayer::ogrFeatureStyleWidth( double widthScaleFactor ) const
3538{
3539 QString featureStyle;
3540 featureStyle.append( "Brush(" );
3541 featureStyle.append( u"fc:%1"_s.arg( mColor.name() ) );
3542 featureStyle.append( u",bc:%1"_s.arg( "#00000000"_L1 ) ); //transparent background
3543 featureStyle.append( ",id:\"ogr-brush-2\"" );
3544 featureStyle.append( u",a:%1"_s.arg( mLineAngle ) );
3545 featureStyle.append( u",s:%1"_s.arg( mLineWidth * widthScaleFactor ) );
3546 featureStyle.append( ",dx:0mm" );
3547 featureStyle.append( u",dy:%1mm"_s.arg( mDistance * widthScaleFactor ) );
3548 featureStyle.append( ')' );
3549 return featureStyle;
3550}
3551
3553{
3555 && ( !mFillLineSymbol || !mFillLineSymbol->hasDataDefinedProperties() ) )
3556 {
3557 return; //no data defined settings
3558 }
3559
3560 double lineAngle = mLineAngle;
3562 {
3563 context.setOriginalValueVariable( mLineAngle );
3565 }
3566 double distance = mDistance;
3568 {
3569 context.setOriginalValueVariable( mDistance );
3571 }
3572 applyPattern( context, mBrush, lineAngle, distance );
3573}
3574
3576{
3577 QString name;
3578 QColor fillColor, lineColor;
3579 double size, lineWidth;
3580 Qt::PenStyle lineStyle;
3581
3582 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
3583 if ( fillElem.isNull() )
3584 return nullptr;
3585
3586 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
3587 if ( graphicFillElem.isNull() )
3588 return nullptr;
3589
3590 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
3591 if ( graphicElem.isNull() )
3592 return nullptr;
3593
3594 if ( !QgsSymbolLayerUtils::wellKnownMarkerFromSld( graphicElem, name, fillColor, lineColor, lineStyle, lineWidth, size ) )
3595 return nullptr;
3596
3597 if ( name != "horline"_L1 )
3598 return nullptr;
3599
3600 double angle = 0.0;
3601 QString angleFunc;
3602 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3603 {
3604 bool ok;
3605 double d = angleFunc.toDouble( &ok );
3606 if ( ok )
3607 angle = d;
3608 }
3609
3610 double offset = 0.0;
3611 QPointF vectOffset;
3612 if ( QgsSymbolLayerUtils::displacementFromSldElement( graphicElem, vectOffset ) )
3613 {
3614 offset = std::sqrt( std::pow( vectOffset.x(), 2 ) + std::pow( vectOffset.y(), 2 ) );
3615 }
3616
3617 double scaleFactor = 1.0;
3618 const QString uom = element.attribute( u"uom"_s );
3619 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
3620 size = size * scaleFactor;
3621 lineWidth = lineWidth * scaleFactor;
3622
3623 auto sl = std::make_unique< QgsLinePatternFillSymbolLayer >();
3624 sl->setOutputUnit( sldUnitSize );
3625 sl->setColor( lineColor );
3626 sl->setLineWidth( lineWidth );
3627 sl->setLineAngle( angle );
3628 sl->setOffset( offset );
3629 sl->setDistance( size );
3630
3631 // try to get the stroke
3632 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
3633 if ( !strokeElem.isNull() )
3634 {
3635 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createLineLayerFromSld( strokeElem );
3636 if ( l )
3637 {
3638 QgsSymbolLayerList layers;
3639 layers.append( l.release() );
3640 sl->setSubSymbol( new QgsLineSymbol( layers ) );
3641 }
3642 }
3643
3644 return sl.release();
3645}
3646
3647
3649
3652{
3653 mMarkerSymbol = std::make_unique<QgsMarkerSymbol>();
3654 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
3655}
3656
3658
3660{
3662 mDistanceXUnit = unit;
3663 mDistanceYUnit = unit;
3664 // don't change "percentage" units -- since they adapt directly to whatever other unit is set
3666 mDisplacementXUnit = unit;
3668 mDisplacementYUnit = unit;
3670 mOffsetXUnit = unit;
3672 mOffsetYUnit = unit;
3674 mRandomDeviationXUnit = unit;
3676 mRandomDeviationYUnit = unit;
3677
3678 if ( mMarkerSymbol )
3679 {
3680 mMarkerSymbol->setOutputUnit( unit );
3681 }
3682}
3683
3700
3712
3725
3741
3743{
3744 auto layer = std::make_unique< QgsPointPatternFillSymbolLayer >();
3745 if ( properties.contains( u"distance_x"_s ) )
3746 {
3747 layer->setDistanceX( properties[u"distance_x"_s].toDouble() );
3748 }
3749 if ( properties.contains( u"distance_y"_s ) )
3750 {
3751 layer->setDistanceY( properties[u"distance_y"_s].toDouble() );
3752 }
3753 if ( properties.contains( u"displacement_x"_s ) )
3754 {
3755 layer->setDisplacementX( properties[u"displacement_x"_s].toDouble() );
3756 }
3757 if ( properties.contains( u"displacement_y"_s ) )
3758 {
3759 layer->setDisplacementY( properties[u"displacement_y"_s].toDouble() );
3760 }
3761 if ( properties.contains( u"offset_x"_s ) )
3762 {
3763 layer->setOffsetX( properties[u"offset_x"_s].toDouble() );
3764 }
3765 if ( properties.contains( u"offset_y"_s ) )
3766 {
3767 layer->setOffsetY( properties[u"offset_y"_s].toDouble() );
3768 }
3769
3770 if ( properties.contains( u"distance_x_unit"_s ) )
3771 {
3772 layer->setDistanceXUnit( QgsUnitTypes::decodeRenderUnit( properties[u"distance_x_unit"_s].toString() ) );
3773 }
3774 if ( properties.contains( u"distance_x_map_unit_scale"_s ) )
3775 {
3776 layer->setDistanceXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"distance_x_map_unit_scale"_s].toString() ) );
3777 }
3778 if ( properties.contains( u"distance_y_unit"_s ) )
3779 {
3780 layer->setDistanceYUnit( QgsUnitTypes::decodeRenderUnit( properties[u"distance_y_unit"_s].toString() ) );
3781 }
3782 if ( properties.contains( u"distance_y_map_unit_scale"_s ) )
3783 {
3784 layer->setDistanceYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"distance_y_map_unit_scale"_s].toString() ) );
3785 }
3786 if ( properties.contains( u"displacement_x_unit"_s ) )
3787 {
3788 layer->setDisplacementXUnit( QgsUnitTypes::decodeRenderUnit( properties[u"displacement_x_unit"_s].toString() ) );
3789 }
3790 if ( properties.contains( u"displacement_x_map_unit_scale"_s ) )
3791 {
3792 layer->setDisplacementXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"displacement_x_map_unit_scale"_s].toString() ) );
3793 }
3794 if ( properties.contains( u"displacement_y_unit"_s ) )
3795 {
3796 layer->setDisplacementYUnit( QgsUnitTypes::decodeRenderUnit( properties[u"displacement_y_unit"_s].toString() ) );
3797 }
3798 if ( properties.contains( u"displacement_y_map_unit_scale"_s ) )
3799 {
3800 layer->setDisplacementYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"displacement_y_map_unit_scale"_s].toString() ) );
3801 }
3802 if ( properties.contains( u"offset_x_unit"_s ) )
3803 {
3804 layer->setOffsetXUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_x_unit"_s].toString() ) );
3805 }
3806 if ( properties.contains( u"offset_x_map_unit_scale"_s ) )
3807 {
3808 layer->setOffsetXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_x_map_unit_scale"_s].toString() ) );
3809 }
3810 if ( properties.contains( u"offset_y_unit"_s ) )
3811 {
3812 layer->setOffsetYUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_y_unit"_s].toString() ) );
3813 }
3814 if ( properties.contains( u"offset_y_map_unit_scale"_s ) )
3815 {
3816 layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_y_map_unit_scale"_s].toString() ) );
3817 }
3818
3819 if ( properties.contains( u"random_deviation_x"_s ) )
3820 {
3821 layer->setMaximumRandomDeviationX( properties[u"random_deviation_x"_s].toDouble() );
3822 }
3823 if ( properties.contains( u"random_deviation_y"_s ) )
3824 {
3825 layer->setMaximumRandomDeviationY( properties[u"random_deviation_y"_s].toDouble() );
3826 }
3827 if ( properties.contains( u"random_deviation_x_unit"_s ) )
3828 {
3829 layer->setRandomDeviationXUnit( QgsUnitTypes::decodeRenderUnit( properties[u"random_deviation_x_unit"_s].toString() ) );
3830 }
3831 if ( properties.contains( u"random_deviation_x_map_unit_scale"_s ) )
3832 {
3833 layer->setRandomDeviationXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"random_deviation_x_map_unit_scale"_s].toString() ) );
3834 }
3835 if ( properties.contains( u"random_deviation_y_unit"_s ) )
3836 {
3837 layer->setRandomDeviationYUnit( QgsUnitTypes::decodeRenderUnit( properties[u"random_deviation_y_unit"_s].toString() ) );
3838 }
3839 if ( properties.contains( u"random_deviation_y_map_unit_scale"_s ) )
3840 {
3841 layer->setRandomDeviationYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"random_deviation_y_map_unit_scale"_s].toString() ) );
3842 }
3843 unsigned long seed = 0;
3844 if ( properties.contains( u"seed"_s ) )
3845 seed = properties.value( u"seed"_s ).toUInt();
3846 else
3847 {
3848 // if we a creating a new point pattern fill from scratch, we default to a random seed
3849 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
3850 std::random_device rd;
3851 std::mt19937 mt( static_cast< int >( rd() ) );
3852 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
3853 seed = uniformDist( mt );
3854 }
3855 layer->setSeed( seed );
3856
3857 if ( properties.contains( u"outline_width_unit"_s ) )
3858 {
3859 layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"outline_width_unit"_s].toString() ) );
3860 }
3861 if ( properties.contains( u"outline_width_map_unit_scale"_s ) )
3862 {
3863 layer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"outline_width_map_unit_scale"_s].toString() ) );
3864 }
3865 if ( properties.contains( u"clip_mode"_s ) )
3866 {
3867 layer->setClipMode( QgsSymbolLayerUtils::decodeMarkerClipMode( properties.value( u"clip_mode"_s ).toString() ) );
3868 }
3869 if ( properties.contains( u"coordinate_reference"_s ) )
3870 {
3871 layer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[u"coordinate_reference"_s].toString() ) );
3872 }
3873
3874 if ( properties.contains( u"angle"_s ) )
3875 {
3876 layer->setAngle( properties[u"angle"_s].toDouble() );
3877 }
3878
3880
3881 return layer.release();
3882}
3883
3885{
3886 return u"PointPatternFill"_s;
3887}
3888
3889bool QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double distanceX, double distanceY,
3890 double displacementX, double displacementY, double offsetX, double offsetY )
3891{
3892 //render 3 rows and columns in one go to easily incorporate displacement
3893 const QgsRenderContext &ctx = context.renderContext();
3896
3897 double widthOffset = std::fmod(
3899 width );
3900 double heightOffset = std::fmod(
3902 height );
3903
3904 if ( width > 2000 || height > 2000 ) //protect symbol layer from eating too much memory
3905 {
3906 brush.setTextureImage( QImage() );
3907 return false;
3908 }
3909
3910 QImage patternImage( width, height, QImage::Format_ARGB32 );
3911 patternImage.fill( 0 );
3912 if ( patternImage.isNull() )
3913 {
3914 brush.setTextureImage( QImage() );
3915 return false;
3916 }
3917 if ( mMarkerSymbol )
3918 {
3919 QPainter p( &patternImage );
3920
3921 //marker rendering needs context for drawing on patternImage
3922 QgsRenderContext pointRenderContext;
3923 pointRenderContext.setRendererScale( context.renderContext().rendererScale() );
3924 pointRenderContext.setPainter( &p );
3925 pointRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3926
3928 QgsMapToPixel mtp( context.renderContext().mapToPixel().mapUnitsPerPixel() );
3929 pointRenderContext.setMapToPixel( mtp );
3930 pointRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3932
3934 mMarkerSymbol->startRender( pointRenderContext, context.fields() );
3935
3936 //render points on distance grid
3937 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3938 {
3939 for ( double currentY = -height; currentY <= height * 2.0; currentY += height )
3940 {
3941 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset, currentY + heightOffset ), context.feature(), pointRenderContext );
3942 }
3943 }
3944
3945 //render displaced points
3946 double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
3947 ? ( width * displacementX / 200 )
3948 : ctx.convertToPainterUnits( displacementX, mDisplacementXUnit, mDisplacementXMapUnitScale );
3949 double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
3950 ? ( height * displacementY / 200 )
3952 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3953 {
3954 for ( double currentY = -height / 2.0; currentY <= height * 2.0; currentY += height )
3955 {
3956 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + displacementPixelX, currentY + heightOffset ), context.feature(), pointRenderContext );
3957 }
3958 }
3959
3960 for ( double currentX = -width / 2.0; currentX <= width * 2.0; currentX += width )
3961 {
3962 for ( double currentY = -height; currentY <= height * 2.0; currentY += height / 2.0 )
3963 {
3964 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + ( std::fmod( currentY, height ) != 0 ? displacementPixelX : 0 ), currentY + heightOffset - displacementPixelY ), context.feature(), pointRenderContext );
3965 }
3966 }
3967
3968 mMarkerSymbol->stopRender( pointRenderContext );
3969 }
3970
3971 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3972 {
3973 QImage transparentImage = patternImage.copy();
3974 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3975 brush.setTextureImage( transparentImage );
3976 }
3977 else
3978 {
3979 brush.setTextureImage( patternImage );
3980 }
3981 QTransform brushTransform;
3982 brush.setTransform( brushTransform );
3983
3984 return true;
3985}
3986
3988{
3989 // if we are using a vector based output, we need to render points as vectors
3990 // (OR if the marker has data defined symbology, in which case we need to evaluate this point-by-point)
3991 mRenderUsingMarkers = context.forceVectorRendering()
3992 || ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
3996 || mClipMode != Qgis::MarkerClipMode::Shape
3999 || !qgsDoubleNear( mAngle, 0 )
4001
4002 if ( !mRenderUsingMarkers )
4003 {
4004 // optimised render for screen only, use image based brush
4005 // (fallback to line rendering when pattern image will result in too large a memory footprint)
4006 mRenderUsingMarkers = !applyPattern( context, mBrush, mDistanceX, mDistanceY, mDisplacementX, mDisplacementY, mOffsetX, mOffsetY );
4007 }
4008
4009 if ( mRenderUsingMarkers && mMarkerSymbol )
4010 {
4012 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
4014 }
4015}
4016
4018{
4020 {
4021 mMarkerSymbol->stopRender( context.renderContext() );
4023 }
4024}
4025
4027{
4028 installMasks( context, true );
4029
4030 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4031 // Otherwise generators used in the subsymbol will only render a single point per feature (they
4032 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
4033}
4034
4036{
4037 removeMasks( context, true );
4038
4039 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4040 // Otherwise generators used in the subsymbol will only render a single point per feature (they
4041 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
4042}
4043
4044void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4045{
4046 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4047 if ( !useSelectedColor && !mRenderUsingMarkers )
4048 {
4049 // use image based brush for speed
4050 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
4051 return;
4052 }
4053
4055 {
4057 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
4059 }
4060
4061 // vector based output - so draw dot by dot!
4062 QPainter *p = context.renderContext().painter();
4063 if ( !p )
4064 {
4065 return;
4066 }
4067
4068 double angle = mAngle;
4070 {
4073 }
4074
4075 double distanceX = mDistanceX;
4077 {
4080 }
4082
4083 double distanceY = mDistanceY;
4085 {
4088 }
4090
4091 double offsetX = mOffsetX;
4093 {
4096 }
4097 const double widthOffset = std::fmod(
4099 ? ( offsetX * width / 100 )
4101 width );
4102
4103 double offsetY = mOffsetY;
4105 {
4108 }
4109 const double heightOffset = std::fmod(
4111 ? ( offsetY * height / 100 )
4113 height );
4114
4117 {
4120 }
4121 const double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
4122 ? ( displacementX * width / 100 )
4124
4127 {
4130 }
4131 const double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
4132 ? ( displacementY * height / 100 )
4134
4135 p->setPen( QPen( Qt::NoPen ) );
4136
4137 // if invalid parameters, skip out
4138 if ( qgsDoubleNear( width, 0 ) || qgsDoubleNear( height, 0 ) || width < 0 || height < 0 )
4139 return;
4140
4141 // Clip width and heights steps to a reasonable distance to avoid app freezes
4142 width = std::max( ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderLayerTree )
4143 ? 0.1
4144 : 0.025,
4145 width );
4146 height = std::max( ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderLayerTree )
4147 ? 0.1
4148 : 0.025,
4149 height );
4150
4151 p->save();
4152
4153 Qgis::MarkerClipMode clipMode = mClipMode;
4155 {
4157 bool ok = false;
4158 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::MarkerClipping, context.renderContext().expressionContext(), QString(), &ok );
4159 if ( ok )
4160 {
4161 Qgis::MarkerClipMode decodedMode = QgsSymbolLayerUtils::decodeMarkerClipMode( valueString, &ok );
4162 if ( ok )
4163 clipMode = decodedMode;
4164 }
4165 }
4166
4167 std::unique_ptr< QgsPolygon > shapePolygon;
4168 std::unique_ptr< QgsGeometryEngine > shapeEngine;
4169 switch ( clipMode )
4170 {
4174 {
4175 shapePolygon = std::make_unique< QgsPolygon >();
4176 std::unique_ptr< QgsLineString > fromPolygon = QgsLineString::fromQPolygonF( points );
4177 shapePolygon->setExteriorRing( fromPolygon.release() );
4178 if ( rings )
4179 {
4180 for ( const QPolygonF &ring : *rings )
4181 {
4182 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
4183 shapePolygon->addInteriorRing( fromRing.release() );
4184 }
4185 }
4186 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
4187 shapeEngine->prepareGeometry();
4188 break;
4189 }
4190
4192 {
4193 QPainterPath path;
4194 path.addPolygon( points );
4195 if ( rings )
4196 {
4197 for ( const QPolygonF &ring : *rings )
4198 {
4199 path.addPolygon( ring );
4200 }
4201 }
4202 p->setClipPath( path, Qt::IntersectClip );
4203 break;
4204 }
4205 }
4206
4207 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
4208 const QRectF boundingRect = points.boundingRect();
4209
4210 QTransform invertedRotateTransform;
4211 double left;
4212 double top;
4213 double right;
4214 double bottom;
4215
4216 if ( !qgsDoubleNear( angle, 0 ) )
4217 {
4218 QTransform transform;
4219 if ( applyBrushTransform )
4220 {
4221 // rotation applies around center of feature
4222 transform.translate( -boundingRect.center().x(),
4223 -boundingRect.center().y() );
4224 transform.rotate( -angle );
4225 transform.translate( boundingRect.center().x(),
4226 boundingRect.center().y() );
4227 }
4228 else
4229 {
4230 // rotation applies around top of viewport
4231 transform.rotate( -angle );
4232 }
4233
4234 const QRectF transformedBounds = transform.map( points ).boundingRect();
4235 left = transformedBounds.left() - 2 * width;
4236 top = transformedBounds.top() - 2 * height;
4237 right = transformedBounds.right() + 2 * width;
4238 bottom = transformedBounds.bottom() + 2 * height;
4239 invertedRotateTransform = transform.inverted();
4240
4241 if ( !applyBrushTransform )
4242 {
4243 left -= transformedBounds.left() - ( width * std::floor( transformedBounds.left() / width ) );
4244 top -= transformedBounds.top() - ( height * std::floor( transformedBounds.top() / height ) );
4245 }
4246 }
4247 else
4248 {
4249 left = boundingRect.left() - 2 * width;
4250 top = boundingRect.top() - 2 * height;
4251 right = boundingRect.right() + 2 * width;
4252 bottom = boundingRect.bottom() + 2 * height;
4253
4254 if ( !applyBrushTransform )
4255 {
4256 left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
4257 top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
4258 }
4259 }
4260
4261 unsigned long seed = mSeed;
4263 {
4264 context.renderContext().expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
4266 }
4267
4268 double maxRandomDeviationX = mRandomDeviationX;
4270 {
4271 context.setOriginalValueVariable( maxRandomDeviationX );
4272 maxRandomDeviationX = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::RandomOffsetX, context.renderContext().expressionContext(), maxRandomDeviationX );
4273 }
4274 const double maxRandomDeviationPixelX = mRandomDeviationXUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationX * width / 100 )
4276
4277 double maxRandomDeviationY = mRandomDeviationY;
4279 {
4280 context.setOriginalValueVariable( maxRandomDeviationY );
4281 maxRandomDeviationY = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::RandomOffsetY, context.renderContext().expressionContext(), maxRandomDeviationY );
4282 }
4283 const double maxRandomDeviationPixelY = mRandomDeviationYUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationY * height / 100 )
4285
4286 std::random_device rd;
4287 std::mt19937 mt( seed == 0 ? rd() : seed );
4288 std::uniform_real_distribution<> uniformDist( 0, 1 );
4289 const bool useRandomShift = !qgsDoubleNear( maxRandomDeviationPixelX, 0 ) || !qgsDoubleNear( maxRandomDeviationPixelY, 0 );
4290
4292 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
4293 int pointNum = 0;
4294 const bool needsExpressionContext = mMarkerSymbol->hasDataDefinedProperties();
4295
4296 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4298
4299 const double prevOpacity = mMarkerSymbol->opacity();
4300 mMarkerSymbol->setOpacity( mMarkerSymbol->opacity() * context.opacity() );
4301
4302 bool alternateColumn = false;
4303 int currentCol = -3; // because we actually render a few rows/cols outside the bounds, try to align the col/row numbers to start at 1 for the first visible row/col
4304 for ( double currentX = left; currentX <= right; currentX += width, alternateColumn = !alternateColumn )
4305 {
4306 if ( context.renderContext().renderingStopped() )
4307 break;
4308
4309 if ( needsExpressionContext )
4310 scope->addVariable( QgsExpressionContextScope::StaticVariable( u"symbol_marker_column"_s, ++currentCol, true ) );
4311
4312 bool alternateRow = false;
4313 const double columnX = currentX + widthOffset;
4314 int currentRow = -3;
4315 for ( double currentY = top; currentY <= bottom; currentY += height, alternateRow = !alternateRow )
4316 {
4317 if ( context.renderContext().renderingStopped() )
4318 break;
4319
4320 double y = currentY + heightOffset;
4321 double x = columnX;
4322 if ( alternateRow )
4323 x += displacementPixelX;
4324
4325 if ( !alternateColumn )
4326 y -= displacementPixelY;
4327
4328 if ( !qgsDoubleNear( angle, 0 ) )
4329 {
4330 double xx = x;
4331 double yy = y;
4332 invertedRotateTransform.map( xx, yy, &x, &y );
4333 }
4334
4335 if ( useRandomShift )
4336 {
4337 x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
4338 y += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelY;
4339 }
4340
4341 if ( needsExpressionContext )
4342 {
4344 scope->addVariable( QgsExpressionContextScope::StaticVariable( u"symbol_marker_row"_s, ++currentRow, true ) );
4345 }
4346
4347 if ( shapeEngine )
4348 {
4349 bool renderPoint = true;
4350 switch ( clipMode )
4351 {
4353 {
4354 // we test using the marker bounds here and NOT just the x,y point, as the marker symbol may have offsets or other data defined properties which affect its visual placement
4355 const QgsRectangle markerRect = QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) );
4356 QgsPoint p( markerRect.center() );
4357 renderPoint = shapeEngine->intersects( &p );
4358 break;
4359 }
4360
4363 {
4364 const QgsGeometry markerBounds = QgsGeometry::fromRect( QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) ) );
4365
4367 renderPoint = shapeEngine->contains( markerBounds.constGet() );
4368 else
4369 renderPoint = shapeEngine->intersects( markerBounds.constGet() );
4370 break;
4371 }
4372
4374 break;
4375 }
4376
4377 if ( !renderPoint )
4378 continue;
4379 }
4380
4381 mMarkerSymbol->renderPoint( QPointF( x, y ), context.feature(), context.renderContext(), -1, useSelectedColor );
4382 }
4383 }
4384
4385 mMarkerSymbol->setOpacity( prevOpacity );
4386
4387 p->restore();
4388
4390}
4391
4393{
4394 QVariantMap map = QgsImageFillSymbolLayer::properties();
4395 map.insert( u"distance_x"_s, QString::number( mDistanceX ) );
4396 map.insert( u"distance_y"_s, QString::number( mDistanceY ) );
4397 map.insert( u"displacement_x"_s, QString::number( mDisplacementX ) );
4398 map.insert( u"displacement_y"_s, QString::number( mDisplacementY ) );
4399 map.insert( u"offset_x"_s, QString::number( mOffsetX ) );
4400 map.insert( u"offset_y"_s, QString::number( mOffsetY ) );
4401 map.insert( u"distance_x_unit"_s, QgsUnitTypes::encodeUnit( mDistanceXUnit ) );
4402 map.insert( u"distance_y_unit"_s, QgsUnitTypes::encodeUnit( mDistanceYUnit ) );
4403 map.insert( u"displacement_x_unit"_s, QgsUnitTypes::encodeUnit( mDisplacementXUnit ) );
4404 map.insert( u"displacement_y_unit"_s, QgsUnitTypes::encodeUnit( mDisplacementYUnit ) );
4405 map.insert( u"offset_x_unit"_s, QgsUnitTypes::encodeUnit( mOffsetXUnit ) );
4406 map.insert( u"offset_y_unit"_s, QgsUnitTypes::encodeUnit( mOffsetYUnit ) );
4407 map.insert( u"distance_x_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceXMapUnitScale ) );
4408 map.insert( u"distance_y_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceYMapUnitScale ) );
4409 map.insert( u"displacement_x_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementXMapUnitScale ) );
4410 map.insert( u"displacement_y_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementYMapUnitScale ) );
4411 map.insert( u"offset_x_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetXMapUnitScale ) );
4412 map.insert( u"offset_y_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetYMapUnitScale ) );
4413 map.insert( u"outline_width_unit"_s, QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
4414 map.insert( u"outline_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
4415 map.insert( u"clip_mode"_s, QgsSymbolLayerUtils::encodeMarkerClipMode( mClipMode ) );
4416 map.insert( u"random_deviation_x"_s, QString::number( mRandomDeviationX ) );
4417 map.insert( u"random_deviation_y"_s, QString::number( mRandomDeviationY ) );
4418 map.insert( u"random_deviation_x_unit"_s, QgsUnitTypes::encodeUnit( mRandomDeviationXUnit ) );
4419 map.insert( u"random_deviation_y_unit"_s, QgsUnitTypes::encodeUnit( mRandomDeviationYUnit ) );
4420 map.insert( u"random_deviation_x_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationXMapUnitScale ) );
4421 map.insert( u"random_deviation_y_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationYMapUnitScale ) );
4422 map.insert( u"seed"_s, QString::number( mSeed ) );
4423 map.insert( u"angle"_s, mAngle );
4424 return map;
4425}
4426
4428{
4430 if ( mMarkerSymbol )
4431 {
4432 clonedLayer->setSubSymbol( mMarkerSymbol->clone() );
4433 }
4434 clonedLayer->setClipMode( mClipMode );
4435 copyCommonProperties( clonedLayer );
4436 return clonedLayer;
4437}
4438
4439void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4440{
4441 QgsSldExportContext context;
4442 context.setExtraProperties( props );
4443 toSld( doc, element, context );
4444}
4445
4446bool QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
4447{
4448 const QVariantMap props = context.extraProperties();
4449 for ( int symbolLayerIdx = 0; symbolLayerIdx < mMarkerSymbol->symbolLayerCount(); symbolLayerIdx++ )
4450 {
4451 QDomElement symbolizerElem = doc.createElement( u"se:PolygonSymbolizer"_s );
4452 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
4453 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
4454 element.appendChild( symbolizerElem );
4455
4456 // <Geometry>
4457 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
4458
4459 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
4460 symbolizerElem.appendChild( fillElem );
4461
4462 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
4463 fillElem.appendChild( graphicFillElem );
4464
4465 QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( symbolLayerIdx );
4466
4467 // Export to PNG (TODO: SVG)
4468 bool exportOk { false };
4469 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
4470 {
4471 const QImage image { toTiledPatternImage( ) };
4472 if ( ! image.isNull() )
4473 {
4474 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
4475 graphicFillElem.appendChild( graphicElem );
4476 const QFileInfo info { context.exportFilePath() };
4477 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( u"png"_s ) };
4478 pngPath = QgsFileUtils::uniquePath( pngPath );
4479 image.save( pngPath );
4480 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), u"image/png"_s, QColor(), image.height() );
4481 exportOk = true;
4482 }
4483 }
4484
4485 if ( ! exportOk )
4486 {
4487 // Converts to GeoServer "graphic-margin": symbol size must be subtracted from distance and then divided by 2
4488 const double markerSize { mMarkerSymbol->size() };
4489
4490 // store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
4493 // From: https://docs.geoserver.org/stable/en/user/styling/sld/extensions/margins.html
4494 // top-bottom,right-left (two values, top and bottom sharing the same value)
4495 const QString marginSpec = QString( "%1 %2" ).arg( qgsDoubleToString( ( dy - markerSize ) / 2, 2 ), qgsDoubleToString( ( dx - markerSize ) / 2, 2 ) );
4496
4497 QDomElement graphicMarginElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, u"graphic-margin"_s, marginSpec );
4498 symbolizerElem.appendChild( graphicMarginElem );
4499
4500 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
4501 {
4502 markerLayer->writeSldMarker( doc, graphicFillElem, context );
4503 }
4504 else if ( layer )
4505 {
4506 QgsDebugError( u"QgsMarkerSymbolLayer expected, %1 found. Skip it."_s.arg( layer->layerType() ) );
4507 }
4508 else
4509 {
4510 QgsDebugError( u"Missing point pattern symbol layer. Skip it."_s );
4511 }
4512 }
4513 }
4514 return true;
4515}
4516
4518{
4519
4520 double angleRads { qDegreesToRadians( mAngle ) };
4521
4522 int distanceXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, {} ) ) };
4523 int distanceYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, {} ) ) };
4524
4525 const int displacementXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementX, mDisplacementXUnit, {} ) ) };
4526 const int displacementYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementY, mDisplacementYUnit, {} ) ) };
4527
4528 // Consider displacement, double the distance.
4529 if ( displacementXPx != 0 )
4530 {
4531 distanceXPx *= 2;
4532 }
4533
4534 if ( displacementYPx != 0 )
4535 {
4536 distanceYPx *= 2;
4537 }
4538
4539 const QSize size { QgsSymbolLayerUtils::tileSize( distanceXPx, distanceYPx, angleRads ) };
4540
4541 QPixmap pixmap( size );
4542 pixmap.fill( Qt::transparent );
4543 QPainter painter;
4544 painter.begin( &pixmap );
4545 painter.setRenderHint( QPainter::Antialiasing );
4546 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
4551 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
4552
4553 std::unique_ptr< QgsPointPatternFillSymbolLayer > layerClone( clone() );
4554
4555 layerClone->setAngle( qRadiansToDegrees( angleRads ) );
4556
4557 // No way we can export a random pattern, disable it.
4558 layerClone->setMaximumRandomDeviationX( 0 );
4559 layerClone->setMaximumRandomDeviationY( 0 );
4560
4561 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
4562 painter.end();
4563 return pixmap.toImage();
4564}
4565
4567{
4568
4569 // input element is PolygonSymbolizer
4570
4571 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
4572 if ( fillElem.isNull() )
4573 return nullptr;
4574
4575 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
4576 if ( graphicFillElem.isNull() )
4577 return nullptr;
4578
4579 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
4580 if ( graphicElem.isNull() )
4581 return nullptr;
4582
4583 std::unique_ptr< QgsSymbolLayer > simpleMarkerSl = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicFillElem );
4584 if ( !simpleMarkerSl )
4585 return nullptr;
4586
4587 QgsSymbolLayerList layers;
4588 layers.append( simpleMarkerSl.release() );
4589
4590 auto marker = std::make_unique< QgsMarkerSymbol >( layers );
4591
4592 // Converts from GeoServer "graphic-margin": symbol size must be added and margin doubled
4593 const double markerSize { marker->size() };
4594
4595 auto pointPatternFillSl = std::make_unique< QgsPointPatternFillSymbolLayer >();
4596 pointPatternFillSl->setSubSymbol( marker.release() );
4597 // This may not be correct in all cases, TODO: check "uom"
4598 pointPatternFillSl->setDistanceXUnit( Qgis::RenderUnit::Pixels );
4599 pointPatternFillSl->setDistanceYUnit( Qgis::RenderUnit::Pixels );
4600
4601 auto distanceParser = [ & ]( const QStringList & values )
4602 {
4603 switch ( values.count( ) )
4604 {
4605 case 1: // top-right-bottom-left (single value for all four margins)
4606 {
4607 bool ok;
4608 const double v { values.at( 0 ).toDouble( &ok ) };
4609 if ( ok )
4610 {
4611 pointPatternFillSl->setDistanceX( v * 2 + markerSize );
4612 pointPatternFillSl->setDistanceY( v * 2 + markerSize );
4613 }
4614 break;
4615 }
4616 case 2: // top-bottom,right-left (two values, top and bottom sharing the same value)
4617 {
4618 bool ok;
4619 const double vX { values.at( 1 ).toDouble( &ok ) };
4620 if ( ok )
4621 {
4622 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4623 }
4624 const double vY { values.at( 0 ).toDouble( &ok ) };
4625 if ( ok )
4626 {
4627 pointPatternFillSl->setDistanceY( vY * 2 + markerSize );
4628 }
4629 break;
4630 }
4631 case 3: // top,right-left,bottom (three values, with right and left sharing the same value)
4632 {
4633 bool ok;
4634 const double vX { values.at( 1 ).toDouble( &ok ) };
4635 if ( ok )
4636 {
4637 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4638 }
4639 const double vYt { values.at( 0 ).toDouble( &ok ) };
4640 if ( ok )
4641 {
4642 const double vYb { values.at( 2 ).toDouble( &ok ) };
4643 if ( ok )
4644 {
4645 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4646 }
4647 }
4648 break;
4649 }
4650 case 4: // top,right,bottom,left (one explicit value per margin)
4651 {
4652 bool ok;
4653 const double vYt { values.at( 0 ).toDouble( &ok ) };
4654 if ( ok )
4655 {
4656 const double vYb { values.at( 2 ).toDouble( &ok ) };
4657 if ( ok )
4658 {
4659 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4660 }
4661 }
4662 const double vXr { values.at( 1 ).toDouble( &ok ) };
4663 if ( ok )
4664 {
4665 const double vXl { values.at( 3 ).toDouble( &ok ) };
4666 if ( ok )
4667 {
4668 pointPatternFillSl->setDistanceX( ( vXr + vXl ) + markerSize );
4669 }
4670 }
4671 break;
4672 }
4673 default:
4674 break;
4675 }
4676 };
4677
4678 // Set distance X and Y from vendor options, or from Size if no vendor options are set
4679 bool distanceFromVendorOption { false };
4680 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
4681 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
4682 {
4683 // Legacy
4684 if ( it.key() == "distance"_L1 )
4685 {
4686 distanceParser( it.value().split( ',' ) );
4687 distanceFromVendorOption = true;
4688 }
4689 // GeoServer
4690 else if ( it.key() == "graphic-margin"_L1 )
4691 {
4692 distanceParser( it.value().split( ' ' ) );
4693 distanceFromVendorOption = true;
4694 }
4695 }
4696
4697 // Get distances from size
4698 if ( ! distanceFromVendorOption && ! graphicFillElem.elementsByTagName( u"Size"_s ).isEmpty() )
4699 {
4700 const QDomElement sizeElement { graphicFillElem.elementsByTagName( u"Size"_s ).at( 0 ).toElement() };
4701 bool ok;
4702 const double size { sizeElement.text().toDouble( &ok ) };
4703 if ( ok )
4704 {
4705 pointPatternFillSl->setDistanceX( size );
4706 pointPatternFillSl->setDistanceY( size );
4707 }
4708 }
4709
4710 return pointPatternFillSl.release();
4711}
4712
4714{
4715 if ( !symbol )
4716 {
4717 return false;
4718 }
4719
4720 if ( symbol->type() == Qgis::SymbolType::Marker )
4721 {
4722 QgsMarkerSymbol *markerSymbol = static_cast<QgsMarkerSymbol *>( symbol );
4723 mMarkerSymbol.reset( markerSymbol );
4724 }
4725 return true;
4726}
4727
4732
4734{
4738 && ( !mMarkerSymbol || !mMarkerSymbol->hasDataDefinedProperties() ) )
4739 {
4740 return;
4741 }
4742
4743 double distanceX = mDistanceX;
4745 {
4748 }
4749 double distanceY = mDistanceY;
4751 {
4754 }
4757 {
4760 }
4763 {
4766 }
4767 double offsetX = mOffsetX;
4769 {
4772 }
4773 double offsetY = mOffsetY;
4775 {
4778 }
4779 applyPattern( context, mBrush, distanceX, distanceY, displacementX, displacementY, offsetX, offsetY );
4780}
4781
4783{
4784 return 0;
4785}
4786
4788{
4789 QSet<QString> attributes = QgsImageFillSymbolLayer::usedAttributes( context );
4790
4791 if ( mMarkerSymbol )
4792 attributes.unite( mMarkerSymbol->usedAttributes( context ) );
4793
4794 return attributes;
4795}
4796
4798{
4800 return true;
4801 if ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
4802 return true;
4803 return false;
4804}
4805
4807{
4808 mColor = c;
4809 if ( mMarkerSymbol )
4810 mMarkerSymbol->setColor( c );
4811}
4812
4814{
4815 return mMarkerSymbol ? mMarkerSymbol->color() : mColor;
4816}
4817
4819
4820
4825
4827
4829{
4830 auto sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4831
4832 if ( properties.contains( u"point_on_surface"_s ) )
4833 sl->setPointOnSurface( properties[u"point_on_surface"_s].toInt() != 0 );
4834 if ( properties.contains( u"point_on_all_parts"_s ) )
4835 sl->setPointOnAllParts( properties[u"point_on_all_parts"_s].toInt() != 0 );
4836 if ( properties.contains( u"clip_points"_s ) )
4837 sl->setClipPoints( properties[u"clip_points"_s].toInt() != 0 );
4838 if ( properties.contains( u"clip_on_current_part_only"_s ) )
4839 sl->setClipOnCurrentPartOnly( properties[u"clip_on_current_part_only"_s].toInt() != 0 );
4840
4841 sl->restoreOldDataDefinedProperties( properties );
4842
4843 return sl.release();
4844}
4845
4847{
4848 return u"CentroidFill"_s;
4849}
4850
4852{
4853 mMarker->setColor( color );
4854 mColor = color;
4855}
4856
4858{
4859 return mMarker ? mMarker->color() : mColor;
4860}
4861
4863{
4864 mMarker->setRenderHints( mMarker->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
4865 mMarker->startRender( context.renderContext(), context.fields() );
4866}
4867
4869{
4870 mMarker->stopRender( context.renderContext() );
4871}
4872
4873void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4874{
4875 Part part;
4876 part.exterior = points;
4877 if ( rings )
4878 part.rings = *rings;
4879
4880 if ( mRenderingFeature )
4881 {
4882 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
4883 // until after we've received the final part
4884 mFeatureSymbolOpacity = context.opacity();
4886 mCurrentParts << part;
4887 }
4888 else
4889 {
4890 // not rendering a feature, so we can just render the polygon immediately
4891 const double prevOpacity = mMarker->opacity();
4892 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
4893 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4894 render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
4895 mMarker->setOpacity( prevOpacity );
4896 }
4897}
4898
4900{
4901 installMasks( context, true );
4902
4903 mRenderingFeature = true;
4904 mCurrentParts.clear();
4905}
4906
4908{
4909 mRenderingFeature = false;
4910
4911 const double prevOpacity = mMarker->opacity();
4912 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
4913
4914 render( context, mCurrentParts, feature, mUseSelectedColor );
4916 mUseSelectedColor = false;
4917 mMarker->setOpacity( prevOpacity );
4918
4919 removeMasks( context, true );
4920}
4921
4922void QgsCentroidFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsCentroidFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
4923{
4926 bool clipPoints = mClipPoints;
4928
4929 // TODO add expressions support
4930
4931 QVector< QgsGeometry > geometryParts;
4932 geometryParts.reserve( parts.size() );
4933 QPainterPath globalPath;
4934
4935 int maxArea = 0;
4936 int maxAreaPartIdx = 0;
4937
4938 for ( int i = 0; i < parts.size(); i++ )
4939 {
4940 const Part part = parts[i];
4941 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
4942
4943 if ( !geom.isNull() && !part.rings.empty() )
4944 {
4946
4947 if ( !pointOnAllParts )
4948 {
4949 int area = poly->area();
4950
4951 if ( area > maxArea )
4952 {
4953 maxArea = area;
4954 maxAreaPartIdx = i;
4955 }
4956 }
4957 }
4958
4960 {
4961 globalPath.addPolygon( part.exterior );
4962 for ( const QPolygonF &ring : part.rings )
4963 {
4964 globalPath.addPolygon( ring );
4965 }
4966 }
4967 }
4968
4969 for ( int i = 0; i < parts.size(); i++ )
4970 {
4971 if ( !pointOnAllParts && i != maxAreaPartIdx )
4972 continue;
4973
4974 const Part part = parts[i];
4975
4976 if ( clipPoints )
4977 {
4978 QPainterPath path;
4979
4981 {
4982 path.addPolygon( part.exterior );
4983 for ( const QPolygonF &ring : part.rings )
4984 {
4985 path.addPolygon( ring );
4986 }
4987 }
4988 else
4989 {
4990 path = globalPath;
4991 }
4992
4993 context.painter()->save();
4994 context.painter()->setClipPath( path );
4995 }
4996
4997 QPointF centroid = pointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( part.exterior, &part.rings ) : QgsSymbolLayerUtils::polygonCentroid( part.exterior );
4998
4999 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
5001 mMarker->renderPoint( centroid, feature.isValid() ? &feature : nullptr, context, -1, selected );
5002 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
5003
5004 if ( clipPoints )
5005 {
5006 context.painter()->restore();
5007 }
5008 }
5009}
5010
5012{
5013 QVariantMap map;
5014 map[u"point_on_surface"_s] = QString::number( mPointOnSurface );
5015 map[u"point_on_all_parts"_s] = QString::number( mPointOnAllParts );
5016 map[u"clip_points"_s] = QString::number( mClipPoints );
5017 map[u"clip_on_current_part_only"_s] = QString::number( mClipOnCurrentPartOnly );
5018 return map;
5019}
5020
5022{
5023 auto x = std::make_unique< QgsCentroidFillSymbolLayer >();
5024 x->mAngle = mAngle;
5025 x->mColor = mColor;
5026 x->setSubSymbol( mMarker->clone() );
5027 x->setPointOnSurface( mPointOnSurface );
5028 x->setPointOnAllParts( mPointOnAllParts );
5029 x->setClipPoints( mClipPoints );
5030 x->setClipOnCurrentPartOnly( mClipOnCurrentPartOnly );
5031 copyCommonProperties( x.get() );
5032 return x.release();
5033}
5034
5035void QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
5036{
5037 QgsSldExportContext context;
5038 context.setExtraProperties( props );
5039 toSld( doc, element, context );
5040}
5041
5042bool QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
5043{
5044 // SLD 1.0 specs says: "if a line, polygon, or raster geometry is
5045 // used with PointSymbolizer, then the semantic is to use the centroid
5046 // of the geometry, or any similar representative point.
5047 return mMarker->toSld( doc, element, context );
5048}
5049
5051{
5052 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createMarkerLayerFromSld( element );
5053 if ( !l )
5054 return nullptr;
5055
5056 QgsSymbolLayerList layers;
5057 layers.append( l.release() );
5058 auto marker = std::make_unique<QgsMarkerSymbol>( layers );
5059
5060 auto sl = std::make_unique< QgsCentroidFillSymbolLayer >();
5061 sl->setSubSymbol( marker.release() );
5062 sl->setPointOnAllParts( false );
5063 return sl.release();
5064}
5065
5066
5071
5073{
5074 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5075 {
5076 delete symbol;
5077 return false;
5078 }
5079
5080 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5081 mColor = mMarker->color();
5082 return true;
5083}
5084
5086{
5087 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5088
5089 if ( mMarker )
5090 attributes.unite( mMarker->usedAttributes( context ) );
5091
5092 return attributes;
5093}
5094
5096{
5098 return true;
5099 if ( mMarker && mMarker->hasDataDefinedProperties() )
5100 return true;
5101 return false;
5102}
5103
5108
5110{
5111 if ( mMarker )
5112 {
5113 mMarker->setOutputUnit( unit );
5114 }
5115}
5116
5118{
5119 if ( mMarker )
5120 {
5121 return mMarker->outputUnit();
5122 }
5123 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5124}
5125
5127{
5128 if ( mMarker )
5129 {
5130 return mMarker->usesMapUnits();
5131 }
5132 return false;
5133}
5134
5136{
5137 if ( mMarker )
5138 {
5139 mMarker->setMapUnitScale( scale );
5140 }
5141}
5142
5144{
5145 if ( mMarker )
5146 {
5147 return mMarker->mapUnitScale();
5148 }
5149 return QgsMapUnitScale();
5150}
5151
5152
5153
5154
5162
5164
5166{
5168 double alpha = 1.0;
5169 QPointF offset;
5170 double angle = 0.0;
5171 double width = 0.0;
5172
5173 QString imagePath;
5174 if ( properties.contains( u"imageFile"_s ) )
5175 {
5176 imagePath = properties[u"imageFile"_s].toString();
5177 }
5178 if ( properties.contains( u"coordinate_mode"_s ) )
5179 {
5180 mode = static_cast< Qgis::SymbolCoordinateReference >( properties[u"coordinate_mode"_s].toInt() );
5181 }
5182 if ( properties.contains( u"alpha"_s ) )
5183 {
5184 alpha = properties[u"alpha"_s].toDouble();
5185 }
5186 if ( properties.contains( u"offset"_s ) )
5187 {
5188 offset = QgsSymbolLayerUtils::decodePoint( properties[u"offset"_s].toString() );
5189 }
5190 if ( properties.contains( u"angle"_s ) )
5191 {
5192 angle = properties[u"angle"_s].toDouble();
5193 }
5194 if ( properties.contains( u"width"_s ) )
5195 {
5196 width = properties[u"width"_s].toDouble();
5197 }
5198 auto symbolLayer = std::make_unique< QgsRasterFillSymbolLayer >( imagePath );
5199 symbolLayer->setCoordinateMode( mode );
5200 symbolLayer->setOpacity( alpha );
5201 symbolLayer->setOffset( offset );
5202 symbolLayer->setAngle( angle );
5203 symbolLayer->setWidth( width );
5204 if ( properties.contains( u"offset_unit"_s ) )
5205 {
5206 symbolLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
5207 }
5208 if ( properties.contains( u"offset_map_unit_scale"_s ) )
5209 {
5210 symbolLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
5211 }
5212 if ( properties.contains( u"width_unit"_s ) )
5213 {
5214 symbolLayer->setSizeUnit( QgsUnitTypes::decodeRenderUnit( properties[u"width_unit"_s].toString() ) );
5215 }
5216 if ( properties.contains( u"width_map_unit_scale"_s ) )
5217 {
5218 symbolLayer->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"width_map_unit_scale"_s].toString() ) );
5219 }
5220
5221 if ( properties.contains( u"height"_s ) )
5222 {
5223 symbolLayer->setHeight( properties[u"height"_s].toDouble() );
5224 }
5225
5226 symbolLayer->restoreOldDataDefinedProperties( properties );
5227
5228 return symbolLayer.release();
5229}
5230
5232{
5233 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
5234 if ( fillElem.isNull() )
5235 return nullptr;
5236
5237 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
5238 if ( graphicFillElem.isNull() )
5239 return nullptr;
5240
5241 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
5242 if ( graphicElem.isNull() )
5243 return nullptr;
5244
5245 QString path, mimeType;
5246 double size;
5247 QColor fillColor;
5248
5249 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
5250 return nullptr;
5251
5252 // Try to correct the path, this is a wild guess but we have not access to the SLD path here.
5253 if ( ! QFile::exists( path ) )
5254 {
5255 path = QgsProject::instance()->pathResolver().readPath( path ); // skip-keyword-check
5256 }
5257
5258 auto sl = std::make_unique< QgsRasterFillSymbolLayer>( path );
5259
5260 return sl.release();
5261}
5262
5263void QgsRasterFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
5264{
5265 QVariantMap::iterator it = properties.find( u"imageFile"_s );
5266 if ( it != properties.end() )
5267 {
5268 if ( saving )
5269 it.value() = pathResolver.writePath( it.value().toString() );
5270 else
5271 it.value() = pathResolver.readPath( it.value().toString() );
5272 }
5273}
5274
5276{
5277 Q_UNUSED( symbol )
5278 return true;
5279}
5280
5282{
5283 return u"RasterFill"_s;
5284}
5285
5290
5291void QgsRasterFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5292{
5293 QPainter *p = context.renderContext().painter();
5294 if ( !p )
5295 {
5296 return;
5297 }
5298
5299 QPointF offset = mOffset;
5301 {
5303 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::Property::Offset, context.renderContext().expressionContext(), QString() );
5304 bool ok = false;
5305 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
5306 if ( ok )
5307 offset = res;
5308 }
5309 if ( !offset.isNull() )
5310 {
5311 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
5312 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
5313 p->translate( offset );
5314 }
5315 if ( mCoordinateMode == Qgis::SymbolCoordinateReference::Feature )
5316 {
5317 QRectF boundingRect = points.boundingRect();
5318 mBrush.setTransform( mBrush.transform().translate( boundingRect.left() - mBrush.transform().dx(),
5319 boundingRect.top() - mBrush.transform().dy() ) );
5320 }
5321
5322 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
5323 if ( !offset.isNull() )
5324 {
5325 p->translate( -offset );
5326 }
5327}
5328
5330{
5331 applyPattern( mBrush, mImageFilePath, mWidth, mHeight, mOpacity * context.opacity(), context );
5332}
5333
5335{
5336 Q_UNUSED( context )
5337}
5338
5340{
5341 QVariantMap map;
5342 map[u"imageFile"_s] = mImageFilePath;
5343 map[u"coordinate_mode"_s] = QString::number( static_cast< int >( mCoordinateMode ) );
5344 map[u"alpha"_s] = QString::number( mOpacity );
5345 map[u"offset"_s] = QgsSymbolLayerUtils::encodePoint( mOffset );
5346 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
5347 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
5348 map[u"angle"_s] = QString::number( mAngle );
5349
5350 map[u"width"_s] = QString::number( mWidth );
5351 map[u"height"_s] = QString::number( mHeight );
5352 map[u"width_unit"_s] = QgsUnitTypes::encodeUnit( mSizeUnit );
5353 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
5354
5355 return map;
5356}
5357
5359{
5360 auto sl = std::make_unique< QgsRasterFillSymbolLayer >( mImageFilePath );
5361 sl->setCoordinateMode( mCoordinateMode );
5362 sl->setOpacity( mOpacity );
5363 sl->setOffset( mOffset );
5364 sl->setOffsetUnit( mOffsetUnit );
5365 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
5366 sl->setAngle( mAngle );
5367 sl->setWidth( mWidth );
5368 sl->setHeight( mHeight );
5369 sl->setSizeUnit( mSizeUnit );
5370 sl->setSizeMapUnitScale( mSizeMapUnitScale );
5371
5372 copyCommonProperties( sl.get() );
5373 return sl.release();
5374}
5375
5377{
5378 return context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
5379}
5380
5382{
5383 return mSizeUnit == Qgis::RenderUnit::MapUnits || mSizeUnit == Qgis::RenderUnit::MetersInMapUnits
5384 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
5385}
5386
5388{
5389 return QColor();
5390}
5391
5393{
5395 mOffsetUnit = unit;
5396 mSizeUnit = unit;
5397}
5398
5399void QgsRasterFillSymbolLayer::setImageFilePath( const QString &imagePath )
5400{
5401 mImageFilePath = imagePath;
5402}
5403
5405{
5406 mCoordinateMode = mode;
5407}
5408
5410{
5411 mOpacity = opacity;
5412}
5413
5415{
5416 if ( !dataDefinedProperties().hasActiveProperties() )
5417 return; // shortcut
5418
5419 const bool hasWidthExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Width );
5420 const bool hasHeightExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Height );
5421 const bool hasFileExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::File );
5422 const bool hasOpacityExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Opacity );
5423 const bool hasAngleExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Angle );
5424
5425 if ( !hasWidthExpression && !hasHeightExpression && !hasAngleExpression && !hasOpacityExpression && !hasFileExpression )
5426 {
5427 return; //no data defined settings
5428 }
5429
5430 bool ok;
5431 if ( hasAngleExpression )
5432 {
5434 double nextAngle = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::Angle, context.renderContext().expressionContext(), 0, &ok );
5435 if ( ok )
5436 mNextAngle = nextAngle;
5437 }
5438
5439 if ( !hasWidthExpression && !hasHeightExpression && !hasOpacityExpression && !hasFileExpression )
5440 {
5441 return; //nothing further to do
5442 }
5443
5444 double width = mWidth;
5445 if ( hasWidthExpression )
5446 {
5447 context.setOriginalValueVariable( mWidth );
5449 }
5450 double height = mHeight;
5451 if ( hasHeightExpression )
5452 {
5453 context.setOriginalValueVariable( mHeight );
5455 }
5456 double opacity = mOpacity;
5457 if ( hasOpacityExpression )
5458 {
5459 context.setOriginalValueVariable( mOpacity );
5461 }
5462 QString file = mImageFilePath;
5463 if ( hasFileExpression )
5464 {
5465 context.setOriginalValueVariable( mImageFilePath );
5467 }
5468 applyPattern( mBrush, file, width, height, opacity, context );
5469}
5470
5475
5476void QgsRasterFillSymbolLayer::applyPattern( QBrush &brush, const QString &imageFilePath, const double width, const double height, const double alpha, const QgsSymbolRenderContext &context )
5477{
5478 double imageWidth = 0;
5479 double imageHeight = 0;
5480
5481 // defer retrieval of original size till we actually NEED it
5482 QSize originalSize;
5483
5484 if ( width > 0 )
5485 {
5486 if ( mSizeUnit != Qgis::RenderUnit::Percentage )
5487 {
5488 imageWidth = context.renderContext().convertToPainterUnits( width, mSizeUnit, mSizeMapUnitScale );
5489 }
5490 else
5491 {
5492 // RenderPercentage Unit Type takes original image size
5494 if ( originalSize.isEmpty() )
5495 return;
5496
5497 imageWidth = ( width * originalSize.width() ) / 100.0;
5498
5499 // don't render symbols with size below one or above 10,000 pixels
5500 if ( static_cast< int >( imageWidth ) < 1 || 10000.0 < imageWidth )
5501 return;
5502 }
5503 }
5504 if ( height > 0 )
5505 {
5506 if ( mSizeUnit != Qgis::RenderUnit::Percentage )
5507 {
5508 imageHeight = context.renderContext().convertToPainterUnits( height, mSizeUnit, mSizeMapUnitScale );
5509 }
5510 else
5511 {
5512 // RenderPercentage Unit Type takes original image size
5513 if ( !originalSize.isValid() )
5515
5516 if ( originalSize.isEmpty() )
5517 return;
5518
5519 imageHeight = ( height * originalSize.height() ) / 100.0;
5520
5521 // don't render symbols with size below one or above 10,000 pixels
5522 if ( static_cast< int >( imageHeight ) < 1 || 10000.0 < imageHeight )
5523 return;
5524 }
5525 }
5526
5527 if ( width == 0 && imageHeight > 0 )
5528 {
5529 if ( !originalSize.isValid() )
5531
5532 imageWidth = imageHeight * originalSize.width() / originalSize.height();
5533 }
5534 else if ( height == 0 && imageWidth > 0 )
5535 {
5536 if ( !originalSize.isValid() )
5538
5539 imageHeight = imageWidth * originalSize.height() / originalSize.width();
5540 }
5541 if ( imageWidth == 0 || imageHeight == 0 )
5542 {
5543 if ( !originalSize.isValid() )
5545
5546 imageWidth = originalSize.width();
5547 imageHeight = originalSize.height();
5548 }
5549
5550 bool cached;
5551 QImage img = QgsApplication::imageCache()->pathAsImage( imageFilePath, QSize( std::round< int >( imageWidth ), std::round< int >( imageHeight ) ), false, alpha, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
5552 if ( img.isNull() )
5553 return;
5554
5555 brush.setTextureImage( img );
5556}
5557
5558
5559//
5560// QgsRandomMarkerFillSymbolLayer
5561//
5562
5564 : mCountMethod( method )
5565 , mPointCount( pointCount )
5566 , mDensityArea( densityArea )
5567 , mSeed( seed )
5568{
5570}
5571
5573
5575{
5576 const Qgis::PointCountMethod countMethod = static_cast< Qgis::PointCountMethod >( properties.value( u"count_method"_s, u"0"_s ).toInt() );
5577 const int pointCount = properties.value( u"point_count"_s, u"10"_s ).toInt();
5578 const double densityArea = properties.value( u"density_area"_s, u"250.0"_s ).toDouble();
5579
5580 unsigned long seed = 0;
5581 if ( properties.contains( u"seed"_s ) )
5582 seed = properties.value( u"seed"_s ).toUInt();
5583 else
5584 {
5585 // if we a creating a new random marker fill from scratch, we default to a random seed
5586 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
5587 std::random_device rd;
5588 std::mt19937 mt( static_cast< int >( rd() ) );
5589 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
5590 seed = uniformDist( mt );
5591 }
5592
5593 auto sl = std::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, countMethod, densityArea, seed );
5594
5595 if ( properties.contains( u"density_area_unit"_s ) )
5596 sl->setDensityAreaUnit( QgsUnitTypes::decodeRenderUnit( properties[u"density_area_unit"_s].toString() ) );
5597 if ( properties.contains( u"density_area_unit_scale"_s ) )
5598 sl->setDensityAreaUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"density_area_unit_scale"_s].toString() ) );
5599
5600 if ( properties.contains( u"clip_points"_s ) )
5601 {
5602 sl->setClipPoints( properties[u"clip_points"_s].toInt() );
5603 }
5604
5605 return sl.release();
5606}
5607
5609{
5610 return u"RandomMarkerFill"_s;
5611}
5612
5614{
5615 mMarker->setColor( color );
5616 mColor = color;
5617}
5618
5620{
5621 return mMarker ? mMarker->color() : mColor;
5622}
5623
5625{
5626 mMarker->setRenderHints( mMarker->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
5627 mMarker->startRender( context.renderContext(), context.fields() );
5628}
5629
5631{
5632 mMarker->stopRender( context.renderContext() );
5633}
5634
5635void QgsRandomMarkerFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5636{
5637 Part part;
5638 part.exterior = points;
5639 if ( rings )
5640 part.rings = *rings;
5641
5642 if ( mRenderingFeature )
5643 {
5644 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
5645 // until after we've received the final part
5646 mFeatureSymbolOpacity = context.opacity();
5647 mCurrentParts << part;
5648 }
5649 else
5650 {
5651 // not rendering a feature, so we can just render the polygon immediately
5652 const double prevOpacity = mMarker->opacity();
5653 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
5654 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
5655 render( context.renderContext(), QVector< Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
5656 mMarker->setOpacity( prevOpacity );
5657 }
5658}
5659
5660void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsRandomMarkerFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
5661{
5662 bool clipPoints = mClipPoints;
5664 {
5667 }
5668
5669 QVector< QgsGeometry > geometryParts;
5670 geometryParts.reserve( parts.size() );
5671 QPainterPath path;
5672
5673 for ( const Part &part : parts )
5674 {
5675 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
5676 if ( !geom.isNull() && !part.rings.empty() )
5677 {
5678 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
5679 for ( const QPolygonF &ring : part.rings )
5680 {
5681 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
5682 poly->addInteriorRing( fromRing.release() );
5683 }
5684 }
5685 if ( !geom.isGeosValid() )
5686 {
5687 geom = geom.buffer( 0, 0 );
5688 }
5689 geometryParts << geom;
5690
5691 if ( clipPoints )
5692 {
5693 path.addPolygon( part.exterior );
5694 for ( const QPolygonF &ring : part.rings )
5695 {
5696 path.addPolygon( ring );
5697 }
5698 }
5699 }
5700
5701 const QgsGeometry geom = geometryParts.count() != 1 ? QgsGeometry::unaryUnion( geometryParts ) : geometryParts.at( 0 );
5702
5703 if ( clipPoints )
5704 {
5705 context.painter()->save();
5706 context.painter()->setClipPath( path );
5707 }
5708
5709
5710 int count = mPointCount;
5712 {
5713 context.expressionContext().setOriginalValueVariable( count );
5714 count = mDataDefinedProperties.valueAsInt( QgsSymbolLayer::Property::PointCount, context.expressionContext(), count );
5715 }
5716
5717 switch ( mCountMethod )
5718 {
5720 {
5721 double densityArea = mDensityArea;
5723 {
5726 }
5727 densityArea = context.convertToPainterUnits( std::sqrt( densityArea ), mDensityAreaUnit, mDensityAreaUnitScale );
5728 densityArea = std::pow( densityArea, 2 );
5729 count = std::max( 0.0, std::ceil( count * ( geom.area() / densityArea ) ) );
5730 break;
5731 }
5733 break;
5734 }
5735
5736 unsigned long seed = mSeed;
5738 {
5739 context.expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
5740 seed = mDataDefinedProperties.valueAsInt( QgsSymbolLayer::Property::RandomSeed, context.expressionContext(), seed );
5741 }
5742
5743 QVector< QgsPointXY > randomPoints = geom.randomPointsInPolygon( count, seed );
5744#if 0
5745 // in some cases rendering from top to bottom is nice (e.g. randomised tree markers), but in other cases it's not wanted..
5746 // TODO consider exposing this as an option
5747 std::sort( randomPoints.begin(), randomPoints.end(), []( const QgsPointXY & a, const QgsPointXY & b )->bool
5748 {
5749 return a.y() < b.y();
5750 } );
5751#endif
5752 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
5753 QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scope );
5754 int pointNum = 0;
5755 const bool needsExpressionContext = mMarker->hasDataDefinedProperties();
5756
5757 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
5759
5760 for ( const QgsPointXY &p : std::as_const( randomPoints ) )
5761 {
5762 if ( needsExpressionContext )
5763 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
5764 mMarker->renderPoint( QPointF( p.x(), p.y() ), feature.isValid() ? &feature : nullptr, context, -1, selected );
5765 }
5766
5767 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
5768
5769 if ( clipPoints )
5770 {
5771 context.painter()->restore();
5772 }
5773}
5774
5776{
5777 QVariantMap map;
5778 map.insert( u"count_method"_s, QString::number( static_cast< int >( mCountMethod ) ) );
5779 map.insert( u"point_count"_s, QString::number( mPointCount ) );
5780 map.insert( u"density_area"_s, QString::number( mDensityArea ) );
5781 map.insert( u"density_area_unit"_s, QgsUnitTypes::encodeUnit( mDensityAreaUnit ) );
5782 map.insert( u"density_area_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDensityAreaUnitScale ) );
5783 map.insert( u"seed"_s, QString::number( mSeed ) );
5784 map.insert( u"clip_points"_s, QString::number( mClipPoints ) );
5785 return map;
5786}
5787
5789{
5790 auto res = std::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mCountMethod, mDensityArea, mSeed );
5791 res->mAngle = mAngle;
5792 res->mColor = mColor;
5793 res->setDensityAreaUnit( mDensityAreaUnit );
5794 res->setDensityAreaUnitScale( mDensityAreaUnitScale );
5795 res->mClipPoints = mClipPoints;
5796 res->setSubSymbol( mMarker->clone() );
5797 copyCommonProperties( res.get() );
5798 return res.release();
5799}
5800
5805
5807{
5809 return false;
5810
5811 // special case -- two QgsRandomMarkerFillSymbolLayer will render differently if they have
5812 // no seed value set.
5813 if ( mSeed == 0 )
5814 return false;
5815
5816 return true;
5817}
5818
5820{
5821 return mMarker.get();
5822}
5823
5825{
5826 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5827 {
5828 delete symbol;
5829 return false;
5830 }
5831
5832 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5833 mColor = mMarker->color();
5834 return true;
5835}
5836
5838{
5839 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5840
5841 if ( mMarker )
5842 attributes.unite( mMarker->usedAttributes( context ) );
5843
5844 return attributes;
5845}
5846
5848{
5850 return true;
5851 if ( mMarker && mMarker->hasDataDefinedProperties() )
5852 return true;
5853 return false;
5854}
5855
5857{
5858 return mPointCount;
5859}
5860
5862{
5863 mPointCount = pointCount;
5864}
5865
5867{
5868 return mSeed;
5869}
5870
5872{
5873 mSeed = seed;
5874}
5875
5877{
5878 return mClipPoints;
5879}
5880
5882{
5883 mClipPoints = clipPoints;
5884}
5885
5887{
5888 return mCountMethod;
5889}
5890
5892{
5893 mCountMethod = method;
5894}
5895
5897{
5898 return mDensityArea;
5899}
5900
5902{
5903 mDensityArea = area;
5904}
5905
5907{
5908 installMasks( context, true );
5909
5910 mRenderingFeature = true;
5911 mCurrentParts.clear();
5912}
5913
5915{
5916 mRenderingFeature = false;
5917
5918 const double prevOpacity = mMarker->opacity();
5919 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
5920
5921 render( context, mCurrentParts, feature, false );
5922
5923 mFeatureSymbolOpacity = 1;
5924 mMarker->setOpacity( prevOpacity );
5925
5926 removeMasks( context, true );
5927}
5928
5929
5931{
5932 mDensityAreaUnit = unit;
5933 if ( mMarker )
5934 {
5935 mMarker->setOutputUnit( unit );
5936 }
5937}
5938
5940{
5941 if ( mMarker )
5942 {
5943 return mMarker->outputUnit();
5944 }
5945 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5946}
5947
5949{
5950 if ( mMarker )
5951 {
5952 return mMarker->usesMapUnits();
5953 }
5954 return false;
5955}
5956
5958{
5959 if ( mMarker )
5960 {
5961 mMarker->setMapUnitScale( scale );
5962 }
5963}
5964
5966{
5967 if ( mMarker )
5968 {
5969 return mMarker->mapUnitScale();
5970 }
5971 return QgsMapUnitScale();
5972}
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2778
MarkerClipMode
Marker clipping modes.
Definition qgis.h:3335
@ CompletelyWithin
Render complete markers wherever the completely fall within the polygon shape.
Definition qgis.h:3339
@ NoClipping
No clipping, render complete markers.
Definition qgis.h:3336
@ Shape
Clip to polygon shape.
Definition qgis.h:3337
@ CentroidWithin
Render complete markers wherever their centroid falls within the polygon shape.
Definition qgis.h:3338
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
Definition qgis.h:792
LineClipMode
Line clipping modes.
Definition qgis.h:3349
@ NoClipping
Lines are not clipped, will extend to shape's bounding box.
Definition qgis.h:3352
@ ClipPainterOnly
Applying clipping on the painter only (i.e. line endpoints will coincide with polygon bounding box,...
Definition qgis.h:3350
@ ClipToIntersection
Clip lines to intersection with polygon shape (slower) (i.e. line endpoints will coincide with polygo...
Definition qgis.h:3351
GradientColorSource
Gradient color sources.
Definition qgis.h:3264
@ ColorRamp
Gradient color ramp.
Definition qgis.h:3266
@ SimpleTwoColor
Simple two color gradient.
Definition qgis.h:3265
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
Definition qgis.h:903
GradientSpread
Gradient spread options, which control how gradients are rendered outside of their start and end poin...
Definition qgis.h:3308
@ Repeat
Repeat gradient.
Definition qgis.h:3311
@ Reflect
Reflect gradient.
Definition qgis.h:3310
@ Pad
Pad out gradient using colors at endpoint of gradient.
Definition qgis.h:3309
@ Png
Export complex styles to separate PNG files for better compatibility with OGC servers.
Definition qgis.h:720
PointCountMethod
Methods which define the number of points randomly filling a polygon.
Definition qgis.h:3323
@ Absolute
The point count is used as an absolute count of markers.
Definition qgis.h:3324
@ DensityBased
The point count is part of a marker density count.
Definition qgis.h:3325
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:908
RenderUnit
Rendering size units.
Definition qgis.h:5305
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5309
@ Unknown
Mixed or unknown units.
Definition qgis.h:5312
@ MapUnits
Map units.
Definition qgis.h:5307
@ Pixels
Pixels.
Definition qgis.h:5308
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5313
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
Definition qgis.h:2838
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
Definition qgis.h:2833
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
Definition qgis.h:2828
@ RenderLayerTree
The render is for a layer tree display where map based properties are not available and where avoidan...
Definition qgis.h:2844
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
Definition qgis.h:2839
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
Definition qgis.h:2832
GradientType
Gradient types.
Definition qgis.h:3278
@ Linear
Linear gradient.
Definition qgis.h:3279
@ Conical
Conical (polar) gradient.
Definition qgis.h:3281
@ Radial
Radial (circular) gradient.
Definition qgis.h:3280
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:798
@ Marker
Marker symbol.
Definition qgis.h:632
@ Line
Line symbol.
Definition qgis.h:633
SymbolCoordinateReference
Symbol coordinate reference modes.
Definition qgis.h:3293
@ Feature
Relative to feature/shape being rendered.
Definition qgis.h:3294
@ Viewport
Relative to the whole viewport/output device.
Definition qgis.h:3295
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color.
bool valueAsBool(int key, const QgsExpressionContext &context, bool defaultValue=false, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an boolean.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QgsSymbolLayer * createFromSld(QDomElement &element)
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
bool setSubSymbol(QgsSymbol *symbol) final
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsMapUnitScale mapUnitScale() const override
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
std::unique_ptr< QgsMarkerSymbol > mMarker
QString layerType() const override
Returns a string that represents this layer type.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
bool pointOnAllParts() const
Returns whether a point is drawn for all parts or only on the biggest part of multi-part features.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
bool clipPoints() const
Returns true if point markers should be clipped to the polygon boundary.
QgsCentroidFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsCentroidFillSymbolLayer using the specified properties map containing symbol propert...
~QgsCentroidFillSymbolLayer() override
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QColor color() const override
Returns the "representative" color of the symbol layer.
bool clipOnCurrentPartOnly() const
Returns true if point markers should be clipped to the current part boundary only.
Abstract base class for color ramps.
virtual QColor color(double value) const =0
Returns the color corresponding to a specified value.
virtual QString type() const =0
Returns a string representing the color ramp type.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
double area() const override
Returns the planar, 2-dimensional area of the geometry.
Exports QGIS layers to the DXF format.
static double mapUnitScaleFactor(double scale, Qgis::RenderUnit symbolUnits, Qgis::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
Qgis::DistanceUnit mapUnits() const
Retrieve map units.
double symbologyScale() const
Returns the reference scale for output.
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
static const QString EXPR_GEOMETRY_POINT_NUM
Inbuilt variable name for point number variable.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
bool isValid() const
Returns the validity of this feature.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:55
void _renderPolygon(QPainter *p, const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context)
Default method to render polygon.
double angle() const
Returns the rotation angle of the fill symbol, in degrees clockwise.
A geometry is the spatial representation of a feature.
QVector< QgsPointXY > randomPointsInPolygon(int count, const std::function< bool(const QgsPointXY &) > &acceptPoint, unsigned long seed=0, QgsFeedback *feedback=nullptr, int maxTriesPerPoint=0) const
Returns a list of count random points generated inside a (multi)polygon geometry (if acceptPoint is s...
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool isGeosValid(Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Checks validity of the geometry using GEOS.
double area() const
Returns the planar, 2-dimensional area of the geometry.
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...
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsColorRamp from a map of properties.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
void addStopsToGradient(QGradient *gradient, double opacity=1) const
Copy color ramp stops to a QGradient.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used for the gradient fill.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QColor color2() const
Returns the color for endpoint of gradient, only used if the gradient color type is set to SimpleTwoC...
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
QgsGradientFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
QgsMapUnitScale mapUnitScale() const override
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
Qgis::SymbolCoordinateReference coordinateMode() const
Returns the coordinate mode for gradient, which controls how the gradient stops are positioned.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
Qgis::SymbolCoordinateReference mCoordinateMode
QgsGradientFillSymbolLayer(const QColor &color=DEFAULT_SIMPLEFILL_COLOR, const QColor &color2=Qt::white, Qgis::GradientColorSource gradientColorType=Qgis::GradientColorSource::SimpleTwoColor, Qgis::GradientType gradientType=Qgis::GradientType::Linear, Qgis::SymbolCoordinateReference coordinateMode=Qgis::SymbolCoordinateReference::Feature, Qgis::GradientSpread gradientSpread=Qgis::GradientSpread::Pad)
Constructor for QgsGradientFillSymbolLayer.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsGradientFillSymbolLayer using the specified properties map containing symbol propert...
void setMapUnitScale(const QgsMapUnitScale &scale) override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
Qgis::GradientSpread mGradientSpread
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QPointF referencePoint1() const
Returns the starting point of gradient fill, in the range [0,0] - [1,1].
Qgis::GradientSpread gradientSpread() const
Returns the gradient spread mode, which controls how the gradient behaves outside of the predefined s...
Qgis::GradientColorSource gradientColorType() const
Returns the gradient color mode, which controls how gradient color stops are created.
QPointF offset() const
Returns the offset by which polygons will be translated during rendering.
Qgis::GradientColorSource mGradientColorType
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
QString layerType() const override
Returns a string that represents this layer type.
Qgis::GradientType gradientType() const
Returns the type of gradient, e.g., linear or radial.
QPointF referencePoint2() const
Returns the end point of gradient fill, in the range [0,0] - [1,1].
std::unique_ptr< QgsColorRamp > mGradientRamp
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
QgsMapUnitScale mStrokeWidthMapUnitScale
Qgis::SymbolCoordinateReference coordinateReference() const
Returns the coordinate reference mode for fill which controls how the top left corner of the image fi...
double mStrokeWidth
Stroke width.
Qgis::SymbolCoordinateReference mCoordinateReference
double dxfWidth(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets line width.
QgsMapUnitScale mapUnitScale() const override
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
Qgis::RenderUnit mStrokeWidthUnit
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
virtual void applyDataDefinedSettings(QgsSymbolRenderContext &context)
Applies data defined settings prior to generating the fill symbol brush.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
~QgsImageFillSymbolLayer() override
virtual bool applyBrushTransformFromContext(QgsSymbolRenderContext *context=nullptr) const
Returns true if the image brush should be transformed using the render context's texture origin.
static void multiplyOpacity(QImage &image, double factor, QgsFeedback *feedback=nullptr)
Multiplies opacity of image pixel values by a factor.
static void stackBlur(QImage &image, int radius, bool alphaOnly=false, QgsFeedback *feedback=nullptr)
Performs a stack blur on an image.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsMapUnitScale mapUnitScale() const override
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QString layerType() const override
Returns a string that represents this layer type.
QgsLinePatternFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
QColor color() const override
Returns the "representative" color of the symbol layer.
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
double lineWidth() const
Returns the width of the line subsymbol used to render the parallel lines in the fill.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setMapUnitScale(const QgsMapUnitScale &scale) override
Qgis::LineClipMode clipMode() const
Returns the line clipping mode, which defines how lines are clipped at the edges of shapes.
double lineAngle() const
Returns the angle for the parallel lines used to fill the symbol.
void setLineWidth(double w)
Sets the width of the line subsymbol used to render the parallel lines in the fill.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
Applies data defined settings prior to generating the fill symbol brush.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
QImage toTiledPatternImage() const override
Renders the symbol layer as an image that can be used as a seamless pattern fill for polygons,...
double offset() const
Returns the offset distance for lines within the fill, which is the distance to offset the parallel l...
double distance() const
Returns the distance between lines in the fill pattern.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QString ogrFeatureStyleWidth(double widthScaleFactor) const
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
~QgsLinePatternFillSymbolLayer() override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsLinePatternFillSymbolLayer from a SLD element.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsLinePatternFillSymbolLayer from a properties map.
Line string geometry type, with support for z-dimension and m-values.
static std::unique_ptr< QgsLineString > fromQPolygonF(const QPolygonF &polygon)
Returns a new linestring from a QPolygonF polygon input.
QPolygonF asQPolygonF() const override
Returns a QPolygonF representing the points.
A line symbol type, for rendering LineString and MultiLineString geometries.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
Struct for storing maximum and minimum scales for measurements in map units.
Abstract base class for marker symbol layers.
A marker symbol type, for rendering Point and MultiPoint geometries.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
QgsMapUnitScale mapUnitScale() const override
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
double distanceX() const
Returns the horizontal distance between rendered markers in the fill.
QImage toTiledPatternImage() const override
Renders the symbol layer as an image that can be used as a seamless pattern fill for polygons,...
double displacementY() const
Returns the vertical displacement for odd numbered columns in the pattern.
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsPointPatternFillSymbolLayer using the specified properties map containing symbol pro...
unsigned long seed() const
Returns the random number seed to use when randomly shifting points, or 0 if a truly random sequence ...
Qgis::MarkerClipMode clipMode() const
Returns the marker clipping mode, which defines how markers are clipped at the edges of shapes.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
double offsetY() const
Returns the vertical offset values for points in the pattern.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
void setMapUnitScale(const QgsMapUnitScale &scale) override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QString layerType() const override
Returns a string that represents this layer type.
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
Applies data defined settings prior to generating the fill symbol brush.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
QColor color() const override
Returns the "representative" color of the symbol layer.
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
double offsetX() const
Returns the horizontal offset values for points in the pattern.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
std::unique_ptr< QgsMarkerSymbol > mMarkerSymbol
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QgsPointPatternFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
double displacementX() const
Returns the horizontal displacement for odd numbered rows in the pattern.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
~QgsPointPatternFillSymbolLayer() override
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
double distanceY() const
Returns the vertical distance between rendered markers in the fill.
void setClipMode(Qgis::MarkerClipMode mode)
Sets the marker clipping mode, which defines how markers are clipped at the edges of shapes.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:37
void addInteriorRing(QgsCurve *ring) override
Adds an interior ring to the geometry (takes ownership).
static QgsProject * instance()
Returns the QgsProject singleton instance.
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
static QVariantMap propertyMapToVariantMap(const QMap< QString, QgsProperty > &propertyMap)
Convert a map of QgsProperty to a map of QVariant This is useful to save a map of properties.
static QMap< QString, QgsProperty > variantMapToPropertyMap(const QVariantMap &variantMap)
Convert a map of QVariant to a map of QgsProperty This is useful to restore a map of properties.
~QgsRandomMarkerFillSymbolLayer() override
bool setSubSymbol(QgsSymbol *symbol) final
Sets layer's subsymbol. takes ownership of the passed symbol.
int pointCount() const
Returns the count of random points to render in the fill.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
QgsRandomMarkerFillSymbolLayer(int pointCount=10, Qgis::PointCountMethod method=Qgis::PointCountMethod::Absolute, double densityArea=250.0, unsigned long seed=0)
Constructor for QgsRandomMarkerFillSymbolLayer, with the specified pointCount.
unsigned long seed() const
Returns the random number seed to use when generating points, or 0 if a truly random sequence will be...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsRandomMarkerFillSymbolLayer using the specified properties map containing symbol pro...
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
QString layerType() const override
Returns a string that represents this layer type.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsRandomMarkerFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setCountMethod(Qgis::PointCountMethod method)
Sets the count method used to randomly fill the polygon.
bool clipPoints() const
Returns true if point markers should be clipped to the polygon boundary.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
void setClipPoints(bool clipped)
Sets whether point markers should be clipped to the polygon boundary.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QColor color() const override
Returns the "representative" color of the symbol layer.
void setSeed(unsigned long seed)
Sets the random number seed to use when generating points, or 0 if a truly random sequence will be us...
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
void setPointCount(int count)
Sets the count of random points to render in the fill.
Qgis::PointCountMethod countMethod() const
Returns the count method used to randomly fill the polygon.
double densityArea() const
Returns the density area used to count the number of points to randomly fill the polygon.
bool rendersIdenticallyTo(const QgsSymbolLayer *other) const override
Returns true if this symbol layer will always render identically to an other symbol layer.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
void setDensityArea(double area)
Sets the density area used to count the number of points to randomly fill the polygon.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsMapUnitScale mapUnitScale() const override
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
Applies data defined settings prior to generating the fill symbol brush.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
double width() const
Returns the width used for scaling the image used in the fill.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsRasterFillSymbolLayer from a SLD element.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsRasterFillSymbolLayer from a properties map.
QString layerType() const override
Returns a string that represents this layer type.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsRasterFillSymbolLayer(const QString &imageFilePath=QString())
Constructor for QgsRasterFillSymbolLayer, using a raster fill from the specified imageFilePath.
double opacity() const
Returns the opacity for the raster image used in the fill.
~QgsRasterFillSymbolLayer() override
void setOpacity(double opacity)
Sets the opacity for the raster image used in the fill.
QColor color() const override
Returns the "representative" color of the symbol layer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QString imageFilePath() const
The path to the raster image used for the fill.
void setImageFilePath(const QString &imagePath)
Sets the path to the raster image used for the fill.
double height() const
Returns the height used for scaling the image used in the fill.
static void resolvePaths(QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving)
Turns relative paths in properties map to absolute when reading and vice versa when writing.
QPointF offset() const
Returns the offset for the fill.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsRasterFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool applyBrushTransformFromContext(QgsSymbolRenderContext *context=nullptr) const override
Returns true if the image brush should be transformed using the render context's texture origin.
void setCoordinateMode(Qgis::SymbolCoordinateReference mode)
Set the coordinate mode for fill.
A rectangle specified with double values.
QgsPointXY center
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
QSet< QString > disabledSymbolLayersV2() const
When rendering a map layer in a second pass (for selective masking), some symbol layers may be disabl...
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.
double rendererScale() const
Returns the renderer map scale.
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 setDisabledSymbolLayersV2(const QSet< QString > &symbolLayers)
When rendering a map layer in a second pass (for selective masking), some symbol layers may be disabl...
void setRasterizedRenderingPolicy(Qgis::RasterizedRenderingPolicy policy)
Sets the policy controlling when rasterisation of content during renders is permitted.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QColor selectionColor() const
Returns the color to use when rendering selected features.
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
QPointF textureOrigin() const
Returns the texture origin, which should be used as a brush transform when rendering using QBrush obj...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
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.
void setRendererScale(double scale)
Sets the renderer map scale.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
const QgsPathResolver & pathResolver() const
Returns the path resolver for conversion between relative and absolute paths during rendering operati...
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
void setParameters(const QMap< QString, QgsProperty > &parameters)
Sets the dynamic SVG parameters.
QString svgFilePath() const
Returns the path to the SVG file used to render the fill.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QColor dxfColor(QgsSymbolRenderContext &context) const override
Gets color.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsSVGFillSymbolLayer from a SLD element.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QColor svgStrokeColor() const
Returns the stroke color used for rendering the SVG content.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
Qgis::RenderUnit patternWidthUnit() const
Returns the units for the width of the SVG images in the pattern.
const QgsMapUnitScale & svgStrokeWidthMapUnitScale() const
Returns the map unit scale for the pattern's stroke.
double svgStrokeWidth() const
Returns the stroke width used for rendering the SVG content.
QString layerType() const override
Returns a string that represents this layer type.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QMap< QString, QgsProperty > parameters() const
Returns the dynamic SVG parameters.
QgsSVGFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QgsSVGFillSymbolLayer(const QString &svgFilePath, double width=20, double rotation=0.0)
Constructor for QgsSVGFillSymbolLayer, using the SVG picture at the specified absolute file path.
void setSvgFilePath(const QString &svgPath)
Sets the path to the SVG file to render in the fill.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSVGFillSymbolLayer from a properties map.
QColor svgFillColor() const
Returns the fill color used for rendering the SVG content.
static void resolvePaths(QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving)
Turns relative paths in properties map to absolute when reading and vice versa when writing.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
const QgsMapUnitScale & patternWidthMapUnitScale() const
Returns the map unit scale for the pattern's width.
Qgis::RenderUnit svgStrokeWidthUnit() const
Returns the units for the stroke width.
double patternWidth() const
Returns the width of the rendered SVG content within the fill (i.e.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
~QgsSVGFillSymbolLayer() override
void setMapUnitScale(const QgsMapUnitScale &scale) override
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
Applies data defined settings prior to generating the fill symbol brush.
QgsMapUnitScale mapUnitScale() const override
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
Scoped object for saving and restoring a QPainter object's state.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QgsShapeburstFillSymbolLayer(const QColor &color=DEFAULT_SIMPLEFILL_COLOR, const QColor &color2=Qt::white, Qgis::GradientColorSource colorType=Qgis::GradientColorSource::SimpleTwoColor, int blurRadius=0, bool useWholeShape=true, double maxDistance=5)
Constructor for QgsShapeburstFillSymbolLayer.
QgsShapeburstFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
~QgsShapeburstFillSymbolLayer() override
int blurRadius() const
Returns the blur radius, which controls the amount of blurring applied to the fill.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QColor color2() const
Returns the color used for the endpoint of the shapeburst fill.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QgsMapUnitScale mapUnitScale() const override
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsShapeburstFillSymbolLayer using the specified properties map containing symbol prope...
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QPointF offset() const
Returns the offset for the shapeburst fill.
bool useWholeShape() const
Returns whether the shapeburst fill is set to cover the entire shape.
bool ignoreRings() const
Returns whether the shapeburst fill is set to ignore polygon interior rings.
double maxDistance() const
Returns the maximum distance from the shape's boundary which is shaded.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
QString layerType() const override
Returns a string that represents this layer type.
Qgis::GradientColorSource colorType() const
Returns the color mode used for the shapeburst fill.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used to draw the shapeburst fill.
QgsSimpleFillSymbolLayer(const QColor &color=DEFAULT_SIMPLEFILL_COLOR, Qt::BrushStyle style=DEFAULT_SIMPLEFILL_STYLE, const QColor &strokeColor=DEFAULT_SIMPLEFILL_BORDERCOLOR, Qt::PenStyle strokeStyle=DEFAULT_SIMPLEFILL_BORDERSTYLE, double strokeWidth=DEFAULT_SIMPLEFILL_BORDERWIDTH, Qt::PenJoinStyle penJoinStyle=DEFAULT_SIMPLEFILL_JOINSTYLE)
double dxfWidth(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets line width.
Qt::PenJoinStyle penJoinStyle() const
QColor strokeColor() const override
Returns the stroke color for the symbol layer.
QColor dxfBrushColor(QgsSymbolRenderContext &context) const override
Gets brush/fill color.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
Qt::BrushStyle dxfBrushStyle() const override
Gets brush/fill style.
QString ogrFeatureStyle(double mmScaleFactor, double mapUnitScaleFactor) const override
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
~QgsSimpleFillSymbolLayer() override
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QColor dxfColor(QgsSymbolRenderContext &context) const override
Gets color.
QColor fillColor() const override
Returns the fill color for the symbol layer.
Qt::PenStyle strokeStyle() const
QString layerType() const override
Returns a string that represents this layer type.
double dxfAngle(QgsSymbolRenderContext &context) const override
Gets angle.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsMapUnitScale mOffsetMapUnitScale
Qgis::RenderUnit mStrokeWidthUnit
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QPointF offset() const
Returns the offset by which polygons will be translated during rendering.
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
QgsMapUnitScale mStrokeWidthMapUnitScale
QImage toTiledPatternImage() const override
Renders the symbol layer as an image that can be used as a seamless pattern fill for polygons,...
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
QgsSimpleFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
QgsMapUnitScale mapUnitScale() const override
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSimpleFillSymbolLayer using the specified properties map containing symbol propertie...
static QgsSymbolLayer * createFromSld(QDomElement &element)
Holds SLD export options and other information related to SLD export of a QGIS layer style.
QString exportFilePath() const
Returns the export file path for the SLD.
void setExtraProperties(const QVariantMap &properties)
Sets the open ended set of properties that can drive/inform the SLD encoding.
void pushWarning(const QString &warning)
Pushes a warning message generated during the conversion.
Qgis::SldExportOptions exportOptions() const
Returns the export options.
QVariantMap extraProperties() const
Returns the open ended set of properties that can drive/inform the SLD encoding.
QByteArray getImageData(const QString &path, bool blocking=false) const
Gets the SVG content corresponding to the given path.
QPicture svgAsPicture(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool forceVectorOutput=false, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >())
Returns an SVG drawing as a QPicture.
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor, bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking=false) const
Tests if an SVG file contains parameters for fill, stroke color, stroke width.
QImage svgAsImage(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >())
Returns an SVG drawing as a QImage.
static bool rotationFromSldElement(QDomElement &element, QString &rotationFunc)
static Qgis::MarkerClipMode decodeMarkerClipMode(const QString &string, bool *ok=nullptr)
Decodes a string representing a marker clip mode.
static QString encodePenStyle(Qt::PenStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QgsStringMap evaluatePropertiesMap(const QMap< QString, QgsProperty > &propertiesMap, const QgsExpressionContext &context)
Evaluates a map of properties using the given context and returns a variant map with evaluated expres...
static bool displacementFromSldElement(QDomElement &element, QPointF &offset)
static QPointF polygonCentroid(const QPolygonF &points)
Calculate the centroid point of a QPolygonF.
static QString encodeBrushStyle(Qt::BrushStyle style)
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static void externalGraphicToSld(QDomDocument &doc, QDomElement &element, const QString &path, const QString &mime, const QColor &color, double size=-1)
static QPointF polygonPointOnSurface(const QPolygonF &points, const QVector< QPolygonF > *rings=nullptr)
Calculate a point on the surface of a QPolygonF.
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
static void multiplyImageOpacity(QImage *image, qreal opacity)
Multiplies opacity of image pixel values with a (global) transparency value.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static double rescaleUom(double size, Qgis::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
static std::unique_ptr< QgsSymbolLayer > createMarkerLayerFromSld(QDomElement &element)
Creates a new marker layer from a SLD DOM element.
static QString ogrFeatureStylePen(double width, double mmScaleFactor, double mapUnitsScaleFactor, const QColor &c, Qt::PenJoinStyle joinStyle=Qt::MiterJoin, Qt::PenCapStyle capStyle=Qt::FlatCap, double offset=0.0, const QVector< qreal > *dashPattern=nullptr)
Create ogr feature style string for pen.
static bool externalGraphicFromSld(QDomElement &element, QString &path, QString &mime, QColor &color, double &size)
static Q_DECL_DEPRECATED void parametricSvgToSld(QDomDocument &doc, QDomElement &graphicElem, const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth)
Encodes a reference to a parametric SVG into SLD, as a succession of parametric SVG using URL paramet...
static bool lineFromSld(QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle=nullptr, Qt::PenCapStyle *penCapStyle=nullptr, QVector< qreal > *customDashPattern=nullptr, double *dashOffset=nullptr)
static std::unique_ptr< QgsSymbolLayer > createLineLayerFromSld(QDomElement &element)
Creates a new line layer from a SLD DOM element.
static QString ogrFeatureStyleBrush(const QColor &fillColr)
Create ogr feature style string for brush.
static QString encodeLineClipMode(Qgis::LineClipMode mode)
Encodes a line clip mode to a string.
static Qgis::LineClipMode decodeLineClipMode(const QString &string, bool *ok=nullptr)
Decodes a string representing a line clip mode.
static QPointF pointOnLineWithDistance(QPointF startPoint, QPointF directionPoint, double distance)
Returns a point on the line from startPoint to directionPoint that is a certain distance away from th...
static QSize tileSize(int width, int height, double &angleRad)
Calculate the minimum size in pixels of a symbol tile given the symbol width and height and the symbo...
static Qt::BrushStyle decodeBrushStyle(const QString &str)
static QDomElement createVendorOptionElement(QDomDocument &doc, const QString &name, const QString &value)
static bool wellKnownMarkerFromSld(QDomElement &element, QString &name, QColor &color, QColor &strokeColor, Qt::PenStyle &strokeStyle, double &strokeWidth, double &size)
Extracts properties from an SLD marker definition.
static void createDisplacementElement(QDomDocument &doc, QDomElement &element, QPointF offset)
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodeColor(const QColor &color)
static bool fillFromSld(QDomElement &element, Qt::BrushStyle &brushStyle, QColor &color)
static Q_DECL_DEPRECATED void fillToSld(QDomDocument &doc, QDomElement &element, Qt::BrushStyle brushStyle, const QColor &color=QColor())
Exports fill details to an SLD element.
static Qgis::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
static Q_DECL_DEPRECATED void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
Creates an SLD geometry element.
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
static Q_DECL_DEPRECATED void wellKnownMarkerToSld(QDomDocument &doc, QDomElement &element, const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth=-1, double size=-1)
Exports a marker to SLD.
static Qt::PenStyle decodePenStyle(const QString &str)
static Q_DECL_DEPRECATED void createRotationElement(QDomDocument &doc, QDomElement &element, const QString &rotationFunc)
Creates SLD rotation element.
static Qgis::SymbolCoordinateReference decodeCoordinateReference(const QString &string, bool *ok=nullptr)
Decodes a string representing a symbol coordinate reference mode.
static QString encodePoint(QPointF point)
Encodes a QPointF to a string.
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static QPointF decodePoint(const QString &string)
Decodes a QSizeF from a string.
static QString encodeCoordinateReference(Qgis::SymbolCoordinateReference coordinateReference)
Encodes a symbol coordinate reference mode to a string.
static QString encodeMarkerClipMode(Qgis::MarkerClipMode mode)
Encodes a marker clip mode to a string.
static void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, QgsSldExportContext &context, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
void copyCommonProperties(QgsSymbolLayer *destLayer) const
Copies all common base class properties from this layer to another symbol layer.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
bool shouldRenderUsingSelectionColor(const QgsSymbolRenderContext &context) const
Returns true if the symbol layer should be rendered using the selection color from the render context...
Qgis::SymbolType type() const
virtual QColor fillColor() const
Returns the fill color for the symbol layer.
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
bool installMasks(QgsRenderContext &context, bool recursive, const QRectF &rect=QRectF())
When rendering, install masks on context painter.
void removeMasks(QgsRenderContext &context, bool recursive)
When rendering, remove previously installed masks from context painter if recursive is true masks are...
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
@ GradientType
Gradient fill type.
@ SecondaryColor
Secondary color (eg for gradient fills).
@ File
Filename, eg for svg files.
@ GradientReference2Y
Gradient reference point 2 y.
@ GradientReference1X
Gradient reference point 1 x.
@ OffsetY
Vertical offset.
@ OffsetX
Horizontal offset.
@ GradientReference1Y
Gradient reference point 1 y.
@ GradientSpread
Gradient spread mode.
@ ShapeburstMaxDistance
Shapeburst fill from edge distance.
@ StrokeStyle
Stroke style (eg solid, dashed).
@ DistanceY
Vertical distance between points.
@ ClipPoints
Whether markers should be clipped to polygon boundaries.
@ LineClipping
Line clipping mode.
@ ShapeburstIgnoreRings
Shapeburst ignore rings.
@ ShapeburstUseWholeShape
Shapeburst use whole shape.
@ DisplacementX
Horizontal displacement.
@ CoordinateMode
Gradient coordinate mode.
@ FillStyle
Fill style (eg solid, dots).
@ GradientReference2X
Gradient reference point 2 x.
@ BlurRadius
Shapeburst blur radius.
@ MarkerClipping
Marker clipping mode.
@ RandomSeed
Random number seed.
@ LineAngle
Line angle, or angle of hash lines for hash line symbols.
@ JoinStyle
Line join style.
@ RandomOffsetY
Random offset Y.
@ DisplacementY
Vertical displacement.
@ DistanceX
Horizontal distance between points.
@ GradientReference1IsCentroid
Gradient reference point 1 is centroid.
@ GradientReference2IsCentroid
Gradient reference point 2 is centroid.
@ LineDistance
Distance between lines, or length of lines for hash line symbols.
@ RandomOffsetX
Random offset X.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
virtual double estimateMaxBleed(const QgsRenderContext &context) const
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
static const bool SELECT_FILL_BORDER
Whether fill styles for selected features also highlight symbol stroke.
virtual bool rendersIdenticallyTo(const QgsSymbolLayer *other) const
Returns true if this symbol layer will always render identically to an other symbol layer.
virtual QString layerType() const =0
Returns a string that represents this layer type.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
virtual QColor strokeColor() const
Returns the stroke color for the symbol layer.
virtual Qgis::SymbolLayerFlags flags() const
Returns flags which control the symbol layer's behavior.
QgsPropertyCollection mDataDefinedProperties
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
static const bool SELECT_FILL_STYLE
Whether fill styles for selected features uses symbol layer style.
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
QgsSymbolLayer(const QgsSymbolLayer &other)
Encapsulates the context in which a symbol is being rendered.
const QgsFeature * feature() const
Returns the current feature being rendered.
QgsFields fields() const
Fields of the layer.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
bool forceVectorRendering() const
Returns true if symbol must be rendered using vector methods, and optimisations like pre-rendered ima...
qreal opacity() const
Returns the opacity for the symbol.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
QColor color() const
Returns the symbol's color.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:294
double interval() const
Returns the interval between individual symbols.
const QgsMapUnitScale & intervalMapUnitScale() const
Returns the map unit scale for the interval between symbols.
void setInterval(double interval)
Sets the interval between individual symbols.
Qgis::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
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.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6867
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6950
QMap< QString, QString > QgsStringMap
Definition qgis.h:7463
T qgsgeometry_cast(QgsAbstractGeometry *geom)
#define DEFAULT_SIMPLEFILL_JOINSTYLE
#define INF
#define DEFAULT_SIMPLEFILL_COLOR
#define DEFAULT_SIMPLEFILL_STYLE
#define DEFAULT_SIMPLEFILL_BORDERSTYLE
#define DEFAULT_SIMPLEFILL_BORDERCOLOR
#define DEFAULT_SIMPLEFILL_BORDERWIDTH
#define INF
#define QgsDebugError(str)
Definition qgslogger.h:59
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
Single variable definition for use within a QgsExpressionContextScope.