QGIS API Documentation 3.99.0-Master (d270888f95f)
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 copyDataDefinedProperties( sl.get() );
390 copyPaintEffect( sl.get() );
391 return sl.release();
392}
393
394void QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
395{
396 QgsSldExportContext context;
397 context.setExtraProperties( props );
398 toSld( doc, element, context );
399}
400
401bool QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
402{
403 if ( mBrushStyle == Qt::NoBrush && mStrokeStyle == Qt::NoPen )
404 return true;
405
406 const QVariantMap props = context.extraProperties();
407 QDomElement symbolizerElem = doc.createElement( u"se:PolygonSymbolizer"_s );
408 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
409 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
410 element.appendChild( symbolizerElem );
411
412 // <Geometry>
413 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
414
415 // Export to PNG
416 bool exportOk { false };
417 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
418 {
419 const QImage image { toTiledPatternImage( ) };
420 if ( ! image.isNull() )
421 {
422 // <Fill>
423 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
424 symbolizerElem.appendChild( fillElem );
425 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
426 fillElem.appendChild( graphicFillElem );
427 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
428 graphicFillElem.appendChild( graphicElem );
429 QgsRenderContext renderContext;
430 const QFileInfo info { context.exportFilePath() };
431 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( u"png"_s ) };
432 pngPath = QgsFileUtils::uniquePath( pngPath );
433 image.save( pngPath );
434 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), u"image/png"_s, QColor(), image.height() );
435 exportOk = true;
436 }
437 }
438
439 if ( ! exportOk )
440 {
441 if ( mBrushStyle != Qt::NoBrush )
442 {
443
444 QColor color { mColor };
445
446 // Apply alpha from symbol
447 bool ok;
448 const double alpha { props.value( u"alpha"_s, QVariant() ).toDouble( &ok ) };
449 if ( ok )
450 {
451 color.setAlphaF( color.alphaF() * alpha );
452 }
453 // <Fill>
454 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
455 symbolizerElem.appendChild( fillElem );
456 QgsSymbolLayerUtils::fillToSld( doc, fillElem, context, mBrushStyle, color );
457 }
458
459 if ( mStrokeStyle != Qt::NoPen )
460 {
461 // <Stroke>
462 QDomElement strokeElem = doc.createElement( u"se:Stroke"_s );
463 symbolizerElem.appendChild( strokeElem );
465 // Apply alpha from symbol
466 bool ok;
467 const double alpha { props.value( u"alpha"_s, QVariant() ).toDouble( &ok ) };
468 QColor strokeColor { mStrokeColor };
469 if ( ok )
470 {
471 strokeColor.setAlphaF( strokeColor.alphaF() * alpha );
472 }
474 }
475 }
476
477 // <se:Displacement>
480 return true;
481}
482
483QString QgsSimpleFillSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
484{
485 //brush
486 QString symbolStyle;
487 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStyleBrush( mColor ) );
488 symbolStyle.append( ';' );
489 //pen
490 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStylePen( mStrokeWidth, mmScaleFactor, mapUnitScaleFactor, mStrokeColor, mPenJoinStyle ) );
491 return symbolStyle;
492}
493
495{
496 QColor color, strokeColor;
497 Qt::BrushStyle fillStyle;
498 Qt::PenStyle strokeStyle;
499 double strokeWidth;
500
501 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
502 QgsSymbolLayerUtils::fillFromSld( fillElem, fillStyle, color );
503
504 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
506
507 QPointF offset;
509
510 double scaleFactor = 1.0;
511 const QString uom = element.attribute( u"uom"_s );
512 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
513 offset.setX( offset.x() * scaleFactor );
514 offset.setY( offset.y() * scaleFactor );
515 strokeWidth = strokeWidth * scaleFactor;
516
517 auto sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, fillStyle, strokeColor, strokeStyle, strokeWidth );
518 sl->setOutputUnit( sldUnitSize );
519 sl->setOffset( offset );
520 return sl.release();
521}
522
524{
525 double penBleed = context.convertToPainterUnits( mStrokeStyle == Qt::NoPen ? 0 : ( mStrokeWidth / 2.0 ), mStrokeWidthUnit, mStrokeWidthMapUnitScale );
526 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
527 return penBleed + offsetBleed;
528}
529
540
551
562
564{
565 return mStrokeStyle;
566}
567
569{
570 QColor c = mColor;
572 {
574 }
575 return c;
576}
577
579{
580 return mBrushStyle;
581}
582
584{
585 QPixmap pixmap( QSize( 32, 32 ) );
586 pixmap.fill( Qt::transparent );
587 QPainter painter;
588 painter.begin( &pixmap );
589 painter.setRenderHint( QPainter::Antialiasing );
590 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
595 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
596
597 std::unique_ptr< QgsSimpleFillSymbolLayer > layerClone( clone() );
598 layerClone->setStrokeStyle( Qt::PenStyle::NoPen );
599 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
600 painter.end();
601 return pixmap.toImage();
602}
603
604//QgsGradientFillSymbolLayer
605
619
624
626{
627 //default to a two-color, linear gradient with feature mode and pad spreading
632 //default to gradient from the default fill color to white
633 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
634 QPointF referencePoint1 = QPointF( 0.5, 0 );
635 bool refPoint1IsCentroid = false;
636 QPointF referencePoint2 = QPointF( 0.5, 1 );
637 bool refPoint2IsCentroid = false;
638 double angle = 0;
639 QPointF offset;
640
641 //update gradient properties from props
642 if ( props.contains( u"type"_s ) )
643 type = static_cast< Qgis::GradientType >( props[u"type"_s].toInt() );
644 if ( props.contains( u"coordinate_mode"_s ) )
645 coordinateMode = static_cast< Qgis::SymbolCoordinateReference >( props[u"coordinate_mode"_s].toInt() );
646 if ( props.contains( u"spread"_s ) )
647 gradientSpread = static_cast< Qgis::GradientSpread >( props[u"spread"_s].toInt() );
648 if ( props.contains( u"color_type"_s ) )
649 colorType = static_cast< Qgis::GradientColorSource >( props[u"color_type"_s].toInt() );
650 if ( props.contains( u"gradient_color"_s ) )
651 {
652 //pre 2.5 projects used "gradient_color"
653 color = QgsColorUtils::colorFromString( props[u"gradient_color"_s].toString() );
654 }
655 else if ( props.contains( u"color"_s ) )
656 {
657 color = QgsColorUtils::colorFromString( props[u"color"_s].toString() );
658 }
659 if ( props.contains( u"gradient_color2"_s ) )
660 {
661 color2 = QgsColorUtils::colorFromString( props[u"gradient_color2"_s].toString() );
662 }
663
664 if ( props.contains( u"reference_point1"_s ) )
665 referencePoint1 = QgsSymbolLayerUtils::decodePoint( props[u"reference_point1"_s].toString() );
666 if ( props.contains( u"reference_point1_iscentroid"_s ) )
667 refPoint1IsCentroid = props[u"reference_point1_iscentroid"_s].toInt();
668 if ( props.contains( u"reference_point2"_s ) )
669 referencePoint2 = QgsSymbolLayerUtils::decodePoint( props[u"reference_point2"_s].toString() );
670 if ( props.contains( u"reference_point2_iscentroid"_s ) )
671 refPoint2IsCentroid = props[u"reference_point2_iscentroid"_s].toInt();
672 if ( props.contains( u"angle"_s ) )
673 angle = props[u"angle"_s].toDouble();
674
675 if ( props.contains( u"offset"_s ) )
676 offset = QgsSymbolLayerUtils::decodePoint( props[u"offset"_s].toString() );
677
678 //attempt to create color ramp from props
679 QgsColorRamp *gradientRamp = nullptr;
680 if ( props.contains( u"rampType"_s ) && props[u"rampType"_s] == QgsCptCityColorRamp::typeString() )
681 {
682 gradientRamp = QgsCptCityColorRamp::create( props );
683 }
684 else
685 {
686 gradientRamp = QgsGradientColorRamp::create( props );
687 }
688
689 //create a new gradient fill layer with desired properties
690 auto sl = std::make_unique< QgsGradientFillSymbolLayer >( color, color2, colorType, type, coordinateMode, gradientSpread );
691 sl->setOffset( offset );
692 if ( props.contains( u"offset_unit"_s ) )
693 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
694 if ( props.contains( u"offset_map_unit_scale"_s ) )
695 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_map_unit_scale"_s].toString() ) );
696 sl->setReferencePoint1( referencePoint1 );
697 sl->setReferencePoint1IsCentroid( refPoint1IsCentroid );
698 sl->setReferencePoint2( referencePoint2 );
699 sl->setReferencePoint2IsCentroid( refPoint2IsCentroid );
700 sl->setAngle( angle );
701 if ( gradientRamp )
702 sl->setColorRamp( gradientRamp );
703
704 sl->restoreOldDataDefinedProperties( props );
705
706 return sl.release();
707}
708
713
715{
716 mGradientRamp.reset( ramp );
717}
718
720{
721 return u"GradientFill"_s;
722}
723
724void QgsGradientFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, const QPolygonF &points )
725{
727 {
728 //shortcut
731 return;
732 }
733
734 bool ok;
735
736 //first gradient color
737 QColor color = mColor;
739 {
742 color.setAlphaF( context.opacity() * color.alphaF() );
743 }
744
745 //second gradient color
746 QColor color2 = mColor2;
748 {
751 color2.setAlphaF( context.opacity() * color2.alphaF() );
752 }
753
754 //gradient rotation angle
755 double angle = mAngle;
757 {
760 }
761
762 //gradient type
765 {
766 QString currentType = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::GradientType, context.renderContext().expressionContext(), QString(), &ok );
767 if ( ok )
768 {
769 if ( currentType == QObject::tr( "linear" ) )
770 {
772 }
773 else if ( currentType == QObject::tr( "radial" ) )
774 {
776 }
777 else if ( currentType == QObject::tr( "conical" ) )
778 {
780 }
781 }
782 }
783
784 //coordinate mode
787 {
788 QString currentCoordMode = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::CoordinateMode, context.renderContext().expressionContext(), QString(), &ok );
789 if ( ok )
790 {
791 if ( currentCoordMode == QObject::tr( "feature" ) )
792 {
794 }
795 else if ( currentCoordMode == QObject::tr( "viewport" ) )
796 {
798 }
799 }
800 }
801
802 //gradient spread
805 {
806 QString currentSpread = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::GradientSpread, context.renderContext().expressionContext(), QString(), &ok );
807 if ( ok )
808 {
809 if ( currentSpread == QObject::tr( "pad" ) )
810 {
812 }
813 else if ( currentSpread == QObject::tr( "repeat" ) )
814 {
816 }
817 else if ( currentSpread == QObject::tr( "reflect" ) )
818 {
820 }
821 }
822 }
823
824 //reference point 1 x & y
825 double refPoint1X = mReferencePoint1.x();
827 {
828 context.setOriginalValueVariable( refPoint1X );
830 }
831 double refPoint1Y = mReferencePoint1.y();
833 {
834 context.setOriginalValueVariable( refPoint1Y );
836 }
837 bool refPoint1IsCentroid = mReferencePoint1IsCentroid;
839 {
840 context.setOriginalValueVariable( refPoint1IsCentroid );
841 refPoint1IsCentroid = mDataDefinedProperties.valueAsBool( QgsSymbolLayer::Property::GradientReference1IsCentroid, context.renderContext().expressionContext(), refPoint1IsCentroid );
842 }
843
844 //reference point 2 x & y
845 double refPoint2X = mReferencePoint2.x();
847 {
848 context.setOriginalValueVariable( refPoint2X );
850 }
851 double refPoint2Y = mReferencePoint2.y();
853 {
854 context.setOriginalValueVariable( refPoint2Y );
856 }
857 bool refPoint2IsCentroid = mReferencePoint2IsCentroid;
859 {
860 context.setOriginalValueVariable( refPoint2IsCentroid );
861 refPoint2IsCentroid = mDataDefinedProperties.valueAsBool( QgsSymbolLayer::Property::GradientReference2IsCentroid, context.renderContext().expressionContext(), refPoint2IsCentroid );
862 }
863
864 if ( refPoint1IsCentroid || refPoint2IsCentroid )
865 {
866 //either the gradient is starting or ending at a centroid, so calculate it
867 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
868 //centroid coordinates need to be scaled to a range [0, 1] relative to polygon bounds
869 QRectF bbox = points.boundingRect();
870 double centroidX = ( centroid.x() - bbox.left() ) / bbox.width();
871 double centroidY = ( centroid.y() - bbox.top() ) / bbox.height();
872
873 if ( refPoint1IsCentroid )
874 {
875 refPoint1X = centroidX;
876 refPoint1Y = centroidY;
877 }
878 if ( refPoint2IsCentroid )
879 {
880 refPoint2X = centroidX;
881 refPoint2Y = centroidY;
882 }
883 }
884
885 //update gradient with data defined values
887 spread, QPointF( refPoint1X, refPoint1Y ), QPointF( refPoint2X, refPoint2Y ), angle );
888}
889
890QPointF QgsGradientFillSymbolLayer::rotateReferencePoint( QPointF refPoint, double angle )
891{
892 //rotate a reference point by a specified angle around the point (0.5, 0.5)
893
894 //create a line from the centrepoint of a rectangle bounded by (0, 0) and (1, 1) to the reference point
895 QLineF refLine = QLineF( QPointF( 0.5, 0.5 ), refPoint );
896 //rotate this line by the current rotation angle
897 refLine.setAngle( refLine.angle() + angle );
898 //get new end point of line
899 QPointF rotatedReferencePoint = refLine.p2();
900 //make sure coords of new end point is within [0, 1]
901 if ( rotatedReferencePoint.x() > 1 )
902 rotatedReferencePoint.setX( 1 );
903 if ( rotatedReferencePoint.x() < 0 )
904 rotatedReferencePoint.setX( 0 );
905 if ( rotatedReferencePoint.y() > 1 )
906 rotatedReferencePoint.setY( 1 );
907 if ( rotatedReferencePoint.y() < 0 )
908 rotatedReferencePoint.setY( 0 );
909
910 return rotatedReferencePoint;
911}
912
913void QgsGradientFillSymbolLayer::applyGradient( const QgsSymbolRenderContext &context, QBrush &brush,
914 const QColor &color, const QColor &color2, Qgis::GradientColorSource gradientColorType,
915 QgsColorRamp *gradientRamp, Qgis::GradientType gradientType,
916 Qgis::SymbolCoordinateReference coordinateMode, Qgis::GradientSpread gradientSpread,
917 QPointF referencePoint1, QPointF referencePoint2, const double angle )
918{
919 //update alpha of gradient colors
920 QColor fillColor = color;
921 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
922 QColor fillColor2 = color2;
923 fillColor2.setAlphaF( context.opacity() * fillColor2.alphaF() );
924
925 //rotate reference points
926 QPointF rotatedReferencePoint1 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint1, angle ) : referencePoint1;
927 QPointF rotatedReferencePoint2 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint2, angle ) : referencePoint2;
928
929 //create a QGradient with the desired properties
930 QGradient gradient;
931 switch ( gradientType )
932 {
934 gradient = QLinearGradient( rotatedReferencePoint1, rotatedReferencePoint2 );
935 break;
937 gradient = QRadialGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).length() );
938 break;
940 gradient = QConicalGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).angle() );
941 break;
942 }
943 switch ( coordinateMode )
944 {
946 gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
947 break;
949 gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
950 break;
951 }
952 switch ( gradientSpread )
953 {
955 gradient.setSpread( QGradient::PadSpread );
956 break;
958 gradient.setSpread( QGradient::ReflectSpread );
959 break;
961 gradient.setSpread( QGradient::RepeatSpread );
962 break;
963 }
964
965 //add stops to gradient
967 ( gradientRamp->type() == QgsGradientColorRamp::typeString() || gradientRamp->type() == QgsCptCityColorRamp::typeString() ) )
968 {
969 //color ramp gradient
970 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( gradientRamp );
971 gradRamp->addStopsToGradient( &gradient, context.opacity() );
972 }
973 else
974 {
975 //two color gradient
976 gradient.setColorAt( 0.0, fillColor );
977 gradient.setColorAt( 1.0, fillColor2 );
978 }
979
980 //update QBrush use gradient
981 brush = QBrush( gradient );
982}
983
985{
986 QColor selColor = context.renderContext().selectionColor();
987 if ( ! SELECTION_IS_OPAQUE )
988 selColor.setAlphaF( context.opacity() );
989 mSelBrush = QBrush( selColor );
990}
991
993{
994 Q_UNUSED( context )
995}
996
997void QgsGradientFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
998{
999 QPainter *p = context.renderContext().painter();
1000 if ( !p )
1001 {
1002 return;
1003 }
1004
1005 applyDataDefinedSymbology( context, points );
1006
1007 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1008 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
1009 p->setPen( Qt::NoPen );
1010
1011 QPointF offset = mOffset;
1013 {
1015 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::Property::Offset, context.renderContext().expressionContext(), QString() );
1016 bool ok = false;
1017 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1018 if ( ok )
1019 offset = res;
1020 }
1021
1022 if ( !offset.isNull() )
1023 {
1026 p->translate( offset );
1027 }
1028
1029 _renderPolygon( p, points, rings, context );
1030
1031 if ( !offset.isNull() )
1032 {
1033 p->translate( -offset );
1034 }
1035}
1036
1038{
1039 QVariantMap map;
1040 map[u"color"_s] = QgsColorUtils::colorToString( mColor );
1041 map[u"gradient_color2"_s] = QgsColorUtils::colorToString( mColor2 );
1042 map[u"color_type"_s] = QString::number( static_cast< int >( mGradientColorType ) );
1043 map[u"type"_s] = QString::number( static_cast<int>( mGradientType ) );
1044 map[u"coordinate_mode"_s] = QString::number( static_cast< int >( mCoordinateMode ) );
1045 map[u"spread"_s] = QString::number( static_cast< int >( mGradientSpread ) );
1046 map[u"reference_point1"_s] = QgsSymbolLayerUtils::encodePoint( mReferencePoint1 );
1047 map[u"reference_point1_iscentroid"_s] = QString::number( mReferencePoint1IsCentroid );
1048 map[u"reference_point2"_s] = QgsSymbolLayerUtils::encodePoint( mReferencePoint2 );
1049 map[u"reference_point2_iscentroid"_s] = QString::number( mReferencePoint2IsCentroid );
1050 map[u"angle"_s] = QString::number( mAngle );
1051 map[u"offset"_s] = QgsSymbolLayerUtils::encodePoint( mOffset );
1052 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1053 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1054 if ( mGradientRamp )
1055 {
1056 map.insert( mGradientRamp->properties() );
1057 }
1058 return map;
1059}
1060
1062{
1063 auto sl = std::make_unique< QgsGradientFillSymbolLayer >( mColor, mColor2, mGradientColorType, mGradientType, mCoordinateMode, mGradientSpread );
1064 if ( mGradientRamp )
1065 sl->setColorRamp( mGradientRamp->clone() );
1066 sl->setReferencePoint1( mReferencePoint1 );
1067 sl->setReferencePoint1IsCentroid( mReferencePoint1IsCentroid );
1068 sl->setReferencePoint2( mReferencePoint2 );
1069 sl->setReferencePoint2IsCentroid( mReferencePoint2IsCentroid );
1070 sl->setAngle( mAngle );
1071 sl->setOffset( mOffset );
1072 sl->setOffsetUnit( mOffsetUnit );
1073 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1074 copyDataDefinedProperties( sl.get() );
1075 copyPaintEffect( sl.get() );
1076 return sl.release();
1077}
1078
1080{
1081 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1082 return offsetBleed;
1083}
1084
1089
1094
1099
1104
1109
1114
1115//QgsShapeburstFillSymbolLayer
1116
1118 int blurRadius, bool useWholeShape, double maxDistance )
1119 : mBlurRadius( blurRadius )
1120 , mUseWholeShape( useWholeShape )
1121 , mMaxDistance( maxDistance )
1122 , mColorType( colorType )
1123 , mColor2( color2 )
1124{
1125 mColor = color;
1126}
1127
1129
1131{
1132 //default to a two-color gradient
1134 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
1135 int blurRadius = 0;
1136 bool useWholeShape = true;
1137 double maxDistance = 5;
1138 QPointF offset;
1139
1140 //update fill properties from props
1141 if ( props.contains( u"color_type"_s ) )
1142 {
1143 colorType = static_cast< Qgis::GradientColorSource >( props[u"color_type"_s].toInt() );
1144 }
1145 if ( props.contains( u"shapeburst_color"_s ) )
1146 {
1147 //pre 2.5 projects used "shapeburst_color"
1148 color = QgsColorUtils::colorFromString( props[u"shapeburst_color"_s].toString() );
1149 }
1150 else if ( props.contains( u"color"_s ) )
1151 {
1152 color = QgsColorUtils::colorFromString( props[u"color"_s].toString() );
1153 }
1154
1155 if ( props.contains( u"shapeburst_color2"_s ) )
1156 {
1157 //pre 2.5 projects used "shapeburst_color2"
1158 color2 = QgsColorUtils::colorFromString( props[u"shapeburst_color2"_s].toString() );
1159 }
1160 else if ( props.contains( u"gradient_color2"_s ) )
1161 {
1162 color2 = QgsColorUtils::colorFromString( props[u"gradient_color2"_s].toString() );
1163 }
1164 if ( props.contains( u"blur_radius"_s ) )
1165 {
1166 blurRadius = props[u"blur_radius"_s].toInt();
1167 }
1168 if ( props.contains( u"use_whole_shape"_s ) )
1169 {
1170 useWholeShape = props[u"use_whole_shape"_s].toInt();
1171 }
1172 if ( props.contains( u"max_distance"_s ) )
1173 {
1174 maxDistance = props[u"max_distance"_s].toDouble();
1175 }
1176 if ( props.contains( u"offset"_s ) )
1177 {
1178 offset = QgsSymbolLayerUtils::decodePoint( props[u"offset"_s].toString() );
1179 }
1180
1181 //attempt to create color ramp from props
1182 QgsColorRamp *gradientRamp = nullptr;
1183 if ( props.contains( u"rampType"_s ) && props[u"rampType"_s] == QgsCptCityColorRamp::typeString() )
1184 {
1185 gradientRamp = QgsCptCityColorRamp::create( props );
1186 }
1187 else
1188 {
1189 gradientRamp = QgsGradientColorRamp::create( props );
1190 }
1191
1192 //create a new shapeburst fill layer with desired properties
1193 auto sl = std::make_unique< QgsShapeburstFillSymbolLayer >( color, color2, colorType, blurRadius, useWholeShape, maxDistance );
1194 sl->setOffset( offset );
1195 if ( props.contains( u"offset_unit"_s ) )
1196 {
1197 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[u"offset_unit"_s].toString() ) );
1198 }
1199 if ( props.contains( u"distance_unit"_s ) )
1200 {
1201 sl->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( props[u"distance_unit"_s].toString() ) );
1202 }
1203 if ( props.contains( u"offset_map_unit_scale"_s ) )
1204 {
1205 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"offset_map_unit_scale"_s].toString() ) );
1206 }
1207 if ( props.contains( u"distance_map_unit_scale"_s ) )
1208 {
1209 sl->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[u"distance_map_unit_scale"_s].toString() ) );
1210 }
1211 if ( props.contains( u"ignore_rings"_s ) )
1212 {
1213 sl->setIgnoreRings( props[u"ignore_rings"_s].toInt() );
1214 }
1215 if ( gradientRamp )
1216 {
1217 sl->setColorRamp( gradientRamp );
1218 }
1219
1220 sl->restoreOldDataDefinedProperties( props );
1221
1222 return sl.release();
1223}
1224
1226{
1227 return u"ShapeburstFill"_s;
1228}
1229
1234
1236{
1237 if ( mGradientRamp.get() == ramp )
1238 return;
1239
1240 mGradientRamp.reset( ramp );
1241}
1242
1243void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
1244 double &maxDistance, bool &ignoreRings )
1245{
1246 //first gradient color
1247 color = mColor;
1249 {
1252 }
1253
1254 //second gradient color
1255 color2 = mColor2;
1257 {
1260 }
1261
1262 //blur radius
1263 blurRadius = mBlurRadius;
1265 {
1266 context.setOriginalValueVariable( mBlurRadius );
1268 }
1269
1270 //use whole shape
1271 useWholeShape = mUseWholeShape;
1273 {
1274 context.setOriginalValueVariable( mUseWholeShape );
1276 }
1277
1278 //max distance
1279 maxDistance = mMaxDistance;
1281 {
1282 context.setOriginalValueVariable( mMaxDistance );
1284 }
1285
1286 //ignore rings
1287 ignoreRings = mIgnoreRings;
1289 {
1290 context.setOriginalValueVariable( mIgnoreRings );
1292 }
1293
1294}
1295
1297{
1298 //TODO - check this
1299 QColor selColor = context.renderContext().selectionColor();
1300 if ( ! SELECTION_IS_OPAQUE )
1301 selColor.setAlphaF( context.opacity() );
1302 mSelBrush = QBrush( selColor );
1303}
1304
1306{
1307 Q_UNUSED( context )
1308}
1309
1310void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1311{
1312 QPainter *p = context.renderContext().painter();
1313 if ( !p )
1314 {
1315 return;
1316 }
1317
1318 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1319 if ( useSelectedColor )
1320 {
1321 //feature is selected, draw using selection style
1322 p->setBrush( mSelBrush );
1323 QPointF offset = mOffset;
1324
1326 {
1328 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::Property::Offset, context.renderContext().expressionContext(), QString() );
1329 bool ok = false;
1330 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1331 if ( ok )
1332 offset = res;
1333 }
1334
1335 if ( !offset.isNull() )
1336 {
1337 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1338 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1339 p->translate( offset );
1340 }
1341 _renderPolygon( p, points, rings, context );
1342 if ( !offset.isNull() )
1343 {
1344 p->translate( -offset );
1345 }
1346 return;
1347 }
1348
1349 QColor color1, color2;
1350 int blurRadius;
1351 bool useWholeShape;
1352 double maxDistance;
1353 bool ignoreRings;
1354 //calculate data defined symbology
1355 applyDataDefinedSymbology( context, color1, color2, blurRadius, useWholeShape, maxDistance, ignoreRings );
1356
1357 //calculate max distance for shapeburst fill to extend from polygon boundary, in pixels
1358 int outputPixelMaxDist = 0;
1359 if ( !useWholeShape && !qgsDoubleNear( maxDistance, 0.0 ) )
1360 {
1361 //convert max distance to pixels
1362 outputPixelMaxDist = static_cast< int >( std::round( context.renderContext().convertToPainterUnits( maxDistance, mDistanceUnit, mDistanceMapUnitScale ) ) );
1363 }
1364
1365 //if we are using the two color mode, create a gradient ramp
1366 std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
1368 {
1369 twoColorGradientRamp = std::make_unique< QgsGradientColorRamp >( color1, color2 );
1370 }
1371
1372 //no stroke for shapeburst fills
1373 p->setPen( QPen( Qt::NoPen ) );
1374
1375 //calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
1376 int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
1377 //create a QImage to draw shapeburst in
1378 int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1379 int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1380 int imWidth = pointsWidth + ( sideBuffer * 2 );
1381 int imHeight = pointsHeight + ( sideBuffer * 2 );
1382
1383 // these are all potentially very expensive operations, so check regularly if the job is canceled and abort responsively
1384 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1385 return;
1386
1387 auto fillImage = std::make_unique< QImage >( imWidth,
1388 imHeight, QImage::Format_ARGB32_Premultiplied );
1389 if ( fillImage->isNull() )
1390 {
1391 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1392 return;
1393 }
1394
1395 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1396 return;
1397
1398 //also create an image to store the alpha channel
1399 auto alphaImage = std::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1400 if ( alphaImage->isNull() )
1401 {
1402 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1403 return;
1404 }
1405
1406 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1407 return;
1408
1409 //Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
1410 //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
1411 //polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
1412 fillImage->fill( Qt::black );
1413
1414 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1415 return;
1416
1417 //initially fill the alpha channel image with a transparent color
1418 alphaImage->fill( Qt::transparent );
1419
1420 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1421 return;
1422
1423 //now, draw the polygon in the alpha channel image
1424 QPainter imgPainter;
1425 imgPainter.begin( alphaImage.get() );
1426 imgPainter.setRenderHint( QPainter::Antialiasing, true );
1427 imgPainter.setBrush( QBrush( Qt::white ) );
1428 imgPainter.setPen( QPen( Qt::black ) );
1429 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1430 _renderPolygon( &imgPainter, points, rings, context );
1431 imgPainter.end();
1432
1433 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1434 return;
1435
1436 //now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
1437 //(this avoids calling _renderPolygon twice, since that can be slow)
1438 imgPainter.begin( fillImage.get() );
1439 if ( !ignoreRings )
1440 {
1441 imgPainter.drawImage( 0, 0, *alphaImage );
1442 }
1443 else
1444 {
1445 //using ignore rings mode, so the alpha image can't be used
1446 //directly as the alpha channel contains polygon rings and we need
1447 //to draw now without any rings
1448 imgPainter.setBrush( QBrush( Qt::white ) );
1449 imgPainter.setPen( QPen( Qt::black ) );
1450 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1451 _renderPolygon( &imgPainter, points, nullptr, context );
1452 }
1453 imgPainter.end();
1454
1455 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1456 return;
1457
1458 //apply distance transform to image, uses the current color ramp to calculate final pixel colors
1459 double *dtArray = distanceTransform( fillImage.get(), context.renderContext() );
1460
1461 //copy distance transform values back to QImage, shading by appropriate color ramp
1462 dtArrayToQImage( dtArray, fillImage.get(), mColorType == Qgis::GradientColorSource::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
1463 context.renderContext(), useWholeShape, outputPixelMaxDist );
1464 if ( context.opacity() < 1 )
1465 {
1466 QgsImageOperation::multiplyOpacity( *fillImage, context.opacity(), context.renderContext().feedback() );
1467 }
1468
1469 //clean up some variables
1470 delete [] dtArray;
1471
1472 //apply blur if desired
1473 if ( blurRadius > 0 )
1474 {
1475 QgsImageOperation::stackBlur( *fillImage, blurRadius, false, context.renderContext().feedback() );
1476 }
1477
1478 //apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1479 imgPainter.begin( fillImage.get() );
1480 imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
1481 imgPainter.drawImage( 0, 0, *alphaImage );
1482 imgPainter.end();
1483 //we're finished with the alpha channel image now
1484 alphaImage.reset();
1485
1486 //draw shapeburst image in correct place in the destination painter
1487
1488 QgsScopedQPainterState painterState( p );
1489 QPointF offset = mOffset;
1491 {
1493 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::Property::Offset, context.renderContext().expressionContext(), QString() );
1494 bool ok = false;
1495 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1496 if ( ok )
1497 offset = res;
1498 }
1499 if ( !offset.isNull() )
1500 {
1501 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1502 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1503 p->translate( offset );
1504 }
1505
1506 p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
1507
1508 if ( !offset.isNull() )
1509 {
1510 p->translate( -offset );
1511 }
1512}
1513
1514//fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
1515
1516/* distance transform of a 1d function using squared distance */
1517void QgsShapeburstFillSymbolLayer::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
1518{
1519 int k = 0;
1520 v[0] = 0;
1521 z[0] = -INF;
1522 z[1] = + INF;
1523 for ( int q = 1; q <= n - 1; q++ )
1524 {
1525 double s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1526 while ( s <= z[k] )
1527 {
1528 k--;
1529 s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1530 }
1531 k++;
1532 v[k] = q;
1533 z[k] = s;
1534 z[k + 1] = + INF;
1535 }
1536
1537 k = 0;
1538 for ( int q = 0; q <= n - 1; q++ )
1539 {
1540 while ( z[k + 1] < q )
1541 k++;
1542 d[q] = static_cast< double >( q - v[k] ) * ( q - v[k] ) + f[v[k]];
1543 }
1544}
1545
1546/* distance transform of 2d function using squared distance */
1547void QgsShapeburstFillSymbolLayer::distanceTransform2d( double *im, int width, int height, QgsRenderContext &context )
1548{
1549 int maxDimension = std::max( width, height );
1550 double *f = new double[ maxDimension ];
1551 int *v = new int[ maxDimension ];
1552 double *z = new double[ maxDimension + 1 ];
1553 double *d = new double[ maxDimension ];
1554
1555 // transform along columns
1556 for ( int x = 0; x < width; x++ )
1557 {
1558 if ( context.renderingStopped() )
1559 break;
1560
1561 for ( int y = 0; y < height; y++ )
1562 {
1563 f[y] = im[ x + static_cast< std::size_t>( y ) * width ];
1564 }
1565 distanceTransform1d( f, height, v, z, d );
1566 for ( int y = 0; y < height; y++ )
1567 {
1568 im[ x + static_cast< std::size_t>( y ) * width ] = d[y];
1569 }
1570 }
1571
1572 // transform along rows
1573 for ( int y = 0; y < height; y++ )
1574 {
1575 if ( context.renderingStopped() )
1576 break;
1577
1578 for ( int x = 0; x < width; x++ )
1579 {
1580 f[x] = im[ x + static_cast< std::size_t>( y ) * width ];
1581 }
1582 distanceTransform1d( f, width, v, z, d );
1583 for ( int x = 0; x < width; x++ )
1584 {
1585 im[ x + static_cast< std::size_t>( y ) * width ] = d[x];
1586 }
1587 }
1588
1589 delete [] d;
1590 delete [] f;
1591 delete [] v;
1592 delete [] z;
1593}
1594
1595/* distance transform of a binary QImage */
1596double *QgsShapeburstFillSymbolLayer::distanceTransform( QImage *im, QgsRenderContext &context )
1597{
1598 int width = im->width();
1599 int height = im->height();
1600
1601 double *dtArray = new double[static_cast< std::size_t>( width ) * height];
1602
1603 //load qImage to array
1604 QRgb tmpRgb;
1605 std::size_t idx = 0;
1606 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1607 {
1608 if ( context.renderingStopped() )
1609 break;
1610
1611 const QRgb *scanLine = reinterpret_cast< const QRgb * >( im->constScanLine( heightIndex ) );
1612 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1613 {
1614 tmpRgb = scanLine[widthIndex];
1615 if ( qRed( tmpRgb ) == 0 )
1616 {
1617 //black pixel, so zero distance
1618 dtArray[ idx ] = 0;
1619 }
1620 else
1621 {
1622 //white pixel, so initially set distance as infinite
1623 dtArray[ idx ] = INF;
1624 }
1625 idx++;
1626 }
1627 }
1628
1629 //calculate squared distance transform
1630 distanceTransform2d( dtArray, width, height, context );
1631
1632 return dtArray;
1633}
1634
1635void QgsShapeburstFillSymbolLayer::dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, QgsRenderContext &context, bool useWholeShape, int maxPixelDistance )
1636{
1637 int width = im->width();
1638 int height = im->height();
1639
1640 //find maximum distance value
1641 double maxDistanceValue;
1642
1643 if ( useWholeShape )
1644 {
1645 //no max distance specified in symbol properties, so calculate from maximum value in distance transform results
1646 double dtMaxValue = array[0];
1647 for ( std::size_t i = 1; i < static_cast< std::size_t >( width ) * height; ++i )
1648 {
1649 if ( array[i] > dtMaxValue )
1650 {
1651 dtMaxValue = array[i];
1652 }
1653 }
1654
1655 //values in distance transform are squared
1656 maxDistanceValue = std::sqrt( dtMaxValue );
1657 }
1658 else
1659 {
1660 //use max distance set in symbol properties
1661 maxDistanceValue = maxPixelDistance;
1662 }
1663
1664 //update the pixels in the provided QImage
1665 std::size_t idx = 0;
1666 double squaredVal = 0;
1667 double pixVal = 0;
1668
1669 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1670 {
1671 if ( context.renderingStopped() )
1672 break;
1673
1674 QRgb *scanLine = reinterpret_cast< QRgb * >( im->scanLine( heightIndex ) );
1675 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1676 {
1677 //result of distance transform
1678 squaredVal = array[idx];
1679
1680 //scale result to fit in the range [0, 1]
1681 if ( maxDistanceValue > 0 )
1682 {
1683 pixVal = squaredVal > 0 ? std::min( ( std::sqrt( squaredVal ) / maxDistanceValue ), 1.0 ) : 0;
1684 }
1685 else
1686 {
1687 pixVal = 1.0;
1688 }
1689
1690 //convert value to color from ramp
1691 //premultiply ramp color since we are storing this in a ARGB32_Premultiplied QImage
1692 scanLine[widthIndex] = qPremultiply( ramp->color( pixVal ).rgba() );
1693 idx++;
1694 }
1695 }
1696}
1697
1699{
1700 QVariantMap map;
1701 map[u"color"_s] = QgsColorUtils::colorToString( mColor );
1702 map[u"gradient_color2"_s] = QgsColorUtils::colorToString( mColor2 );
1703 map[u"color_type"_s] = QString::number( static_cast< int >( mColorType ) );
1704 map[u"blur_radius"_s] = QString::number( mBlurRadius );
1705 map[u"use_whole_shape"_s] = QString::number( mUseWholeShape );
1706 map[u"max_distance"_s] = QString::number( mMaxDistance );
1707 map[u"distance_unit"_s] = QgsUnitTypes::encodeUnit( mDistanceUnit );
1708 map[u"distance_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale );
1709 map[u"ignore_rings"_s] = QString::number( mIgnoreRings );
1710 map[u"offset"_s] = QgsSymbolLayerUtils::encodePoint( mOffset );
1711 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1712 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1713 if ( mGradientRamp )
1714 {
1715 map.insert( mGradientRamp->properties() );
1716 }
1717
1718 return map;
1719}
1720
1722{
1723 auto sl = std::make_unique< QgsShapeburstFillSymbolLayer >( mColor, mColor2, mColorType, mBlurRadius, mUseWholeShape, mMaxDistance );
1724 if ( mGradientRamp )
1725 {
1726 sl->setColorRamp( mGradientRamp->clone() );
1727 }
1728 sl->setDistanceUnit( mDistanceUnit );
1729 sl->setDistanceMapUnitScale( mDistanceMapUnitScale );
1730 sl->setIgnoreRings( mIgnoreRings );
1731 sl->setOffset( mOffset );
1732 sl->setOffsetUnit( mOffsetUnit );
1733 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1734 copyDataDefinedProperties( sl.get() );
1735 copyPaintEffect( sl.get() );
1736 return sl.release();
1737}
1738
1740{
1741 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1742 return offsetBleed;
1743}
1744
1749
1751{
1752 mDistanceUnit = unit;
1753 mOffsetUnit = unit;
1754}
1755
1757{
1758 if ( mDistanceUnit == mOffsetUnit )
1759 {
1760 return mDistanceUnit;
1761 }
1763}
1764
1766{
1767 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
1768 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
1769}
1770
1772{
1773 mDistanceMapUnitScale = scale;
1774 mOffsetMapUnitScale = scale;
1775}
1776
1778{
1779 if ( mDistanceMapUnitScale == mOffsetMapUnitScale )
1780 {
1781 return mDistanceMapUnitScale;
1782 }
1783 return QgsMapUnitScale();
1784}
1785
1786
1787//QgsImageFillSymbolLayer
1788
1792
1794
1795void QgsImageFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1796{
1797 QPainter *p = context.renderContext().painter();
1798 if ( !p )
1799 {
1800 return;
1801 }
1802
1804 applyDataDefinedSettings( context );
1805
1806 p->setPen( QPen( Qt::NoPen ) );
1807
1808 QTransform bkTransform = mBrush.transform();
1809 if ( applyBrushTransformFromContext( &context ) && !context.renderContext().textureOrigin().isNull() )
1810 {
1811 QPointF leftCorner = context.renderContext().textureOrigin();
1812 QTransform t = mBrush.transform();
1813 t.translate( leftCorner.x(), leftCorner.y() );
1814 mBrush.setTransform( t );
1815 }
1816 else
1817 {
1818 QTransform t = mBrush.transform();
1819 t.translate( 0, 0 );
1820 mBrush.setTransform( t );
1821 }
1822
1823 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1824 if ( useSelectedColor )
1825 {
1826 QColor selColor = context.renderContext().selectionColor();
1827 p->setBrush( QBrush( selColor ) );
1828 _renderPolygon( p, points, rings, context );
1829 }
1830
1831 if ( !qgsDoubleNear( mNextAngle, 0.0 ) )
1832 {
1833 QTransform t = mBrush.transform();
1834 t.rotate( mNextAngle );
1835 mBrush.setTransform( t );
1836 }
1837 p->setBrush( mBrush );
1838 _renderPolygon( p, points, rings, context );
1839
1840 mBrush.setTransform( bkTransform );
1841}
1842
1847
1852
1857
1862
1873
1875{
1876 return Qt::SolidLine;
1877#if 0
1878 if ( !mStroke )
1879 {
1880 return Qt::SolidLine;
1881 }
1882 else
1883 {
1884 return mStroke->dxfPenStyle();
1885 }
1886#endif //0
1887}
1888
1890{
1891 QVariantMap map;
1892 map.insert( u"coordinate_reference"_s, QgsSymbolLayerUtils::encodeCoordinateReference( mCoordinateReference ) );
1893 return map;
1894}
1895
1897{
1898 //coordinate reference
1901 {
1902 bool ok = false;
1903 QString string = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::CoordinateMode, context->renderContext().expressionContext(), QString(), &ok );
1904 if ( ok )
1905 {
1907 if ( !ok )
1909 }
1910 }
1911
1913}
1914
1915
1916//QgsSVGFillSymbolLayer
1917
1920 , mPatternWidth( width )
1921{
1922 mStrokeWidth = 0.3;
1923 mAngle = angle;
1924 mColor = QColor( 255, 255, 255 );
1926}
1927
1928QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray &svgData, double width, double angle )
1930 , mPatternWidth( width )
1931 , mSvgData( svgData )
1932{
1933 storeViewBox();
1934 mStrokeWidth = 0.3;
1935 mAngle = angle;
1936 mColor = QColor( 255, 255, 255 );
1937 setDefaultSvgParams();
1938}
1939
1941
1943{
1945 mPatternWidthUnit = unit;
1946 mSvgStrokeWidthUnit = unit;
1947 mStrokeWidthUnit = unit;
1948 if ( mStroke )
1949 mStroke->setOutputUnit( unit );
1950}
1951
1953{
1955 if ( mPatternWidthUnit != unit || mSvgStrokeWidthUnit != unit || mStrokeWidthUnit != unit )
1956 {
1958 }
1959 return unit;
1960}
1961
1963{
1965 mPatternWidthMapUnitScale = scale;
1966 mSvgStrokeWidthMapUnitScale = scale;
1967}
1968
1970{
1971 if ( QgsImageFillSymbolLayer::mapUnitScale() == mPatternWidthMapUnitScale &&
1972 mPatternWidthMapUnitScale == mSvgStrokeWidthMapUnitScale &&
1973 mSvgStrokeWidthMapUnitScale == mStrokeWidthMapUnitScale )
1974 {
1975 return mPatternWidthMapUnitScale;
1976 }
1977 return QgsMapUnitScale();
1978}
1979
1980void QgsSVGFillSymbolLayer::setSvgFilePath( const QString &svgPath )
1981{
1982 mSvgData = QgsApplication::svgCache()->getImageData( svgPath );
1983 storeViewBox();
1984
1985 mSvgFilePath = svgPath;
1986 setDefaultSvgParams();
1987}
1988
1990{
1991 QByteArray data;
1992 double width = 20;
1993 QString svgFilePath;
1994 double angle = 0.0;
1995
1996 if ( properties.contains( u"width"_s ) )
1997 {
1998 width = properties[u"width"_s].toDouble();
1999 }
2000 if ( properties.contains( u"svgFile"_s ) )
2001 {
2002 svgFilePath = properties[u"svgFile"_s].toString();
2003 }
2004 if ( properties.contains( u"angle"_s ) )
2005 {
2006 angle = properties[u"angle"_s].toDouble();
2007 }
2008
2009 std::unique_ptr< QgsSVGFillSymbolLayer > symbolLayer;
2010 if ( !svgFilePath.isEmpty() )
2011 {
2012 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( svgFilePath, width, angle );
2013 }
2014 else
2015 {
2016 if ( properties.contains( u"data"_s ) )
2017 {
2018 data = QByteArray::fromHex( properties[u"data"_s].toString().toLocal8Bit() );
2019 }
2020 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( data, width, angle );
2021 }
2022
2023 //svg parameters
2024 if ( properties.contains( u"svgFillColor"_s ) )
2025 {
2026 //pre 2.5 projects used "svgFillColor"
2027 symbolLayer->setSvgFillColor( QgsColorUtils::colorFromString( properties[u"svgFillColor"_s].toString() ) );
2028 }
2029 else if ( properties.contains( u"color"_s ) )
2030 {
2031 symbolLayer->setSvgFillColor( QgsColorUtils::colorFromString( properties[u"color"_s].toString() ) );
2032 }
2033 if ( properties.contains( u"svgOutlineColor"_s ) )
2034 {
2035 //pre 2.5 projects used "svgOutlineColor"
2036 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[u"svgOutlineColor"_s].toString() ) );
2037 }
2038 else if ( properties.contains( u"outline_color"_s ) )
2039 {
2040 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[u"outline_color"_s].toString() ) );
2041 }
2042 else if ( properties.contains( u"line_color"_s ) )
2043 {
2044 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[u"line_color"_s].toString() ) );
2045 }
2046 if ( properties.contains( u"svgOutlineWidth"_s ) )
2047 {
2048 //pre 2.5 projects used "svgOutlineWidth"
2049 symbolLayer->setSvgStrokeWidth( properties[u"svgOutlineWidth"_s].toDouble() );
2050 }
2051 else if ( properties.contains( u"outline_width"_s ) )
2052 {
2053 symbolLayer->setSvgStrokeWidth( properties[u"outline_width"_s].toDouble() );
2054 }
2055 else if ( properties.contains( u"line_width"_s ) )
2056 {
2057 symbolLayer->setSvgStrokeWidth( properties[u"line_width"_s].toDouble() );
2058 }
2059
2060 //units
2061 if ( properties.contains( u"pattern_width_unit"_s ) )
2062 {
2063 symbolLayer->setPatternWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"pattern_width_unit"_s].toString() ) );
2064 }
2065 if ( properties.contains( u"pattern_width_map_unit_scale"_s ) )
2066 {
2067 symbolLayer->setPatternWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"pattern_width_map_unit_scale"_s].toString() ) );
2068 }
2069 if ( properties.contains( u"svg_outline_width_unit"_s ) )
2070 {
2071 symbolLayer->setSvgStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"svg_outline_width_unit"_s].toString() ) );
2072 }
2073 if ( properties.contains( u"svg_outline_width_map_unit_scale"_s ) )
2074 {
2075 symbolLayer->setSvgStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"svg_outline_width_map_unit_scale"_s].toString() ) );
2076 }
2077 if ( properties.contains( u"outline_width_unit"_s ) )
2078 {
2079 symbolLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"outline_width_unit"_s].toString() ) );
2080 }
2081 if ( properties.contains( u"outline_width_map_unit_scale"_s ) )
2082 {
2083 symbolLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"outline_width_map_unit_scale"_s].toString() ) );
2084 }
2085
2086 if ( properties.contains( u"parameters"_s ) )
2087 {
2088 const QVariantMap parameters = properties[u"parameters"_s].toMap();
2089 symbolLayer->setParameters( QgsProperty::variantMapToPropertyMap( parameters ) );
2090 }
2091
2092 symbolLayer->restoreOldDataDefinedProperties( properties );
2093
2094 return symbolLayer.release();
2095}
2096
2097void QgsSVGFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2098{
2099 QVariantMap::iterator it = properties.find( u"svgFile"_s );
2100 if ( it != properties.end() )
2101 {
2102 if ( saving )
2103 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2104 else
2105 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2106 }
2107}
2108
2110{
2111 return u"SVGFill"_s;
2112}
2113
2114void QgsSVGFillSymbolLayer::applyPattern( QBrush &brush, const QString &svgFilePath, double patternWidth, Qgis::RenderUnit patternWidthUnit,
2115 const QColor &svgFillColor, const QColor &svgStrokeColor, double svgStrokeWidth,
2116 Qgis::RenderUnit svgStrokeWidthUnit, const QgsSymbolRenderContext &context,
2117 const QgsMapUnitScale &patternWidthMapUnitScale, const QgsMapUnitScale &svgStrokeWidthMapUnitScale, const QgsStringMap svgParameters )
2118{
2119 if ( mSvgViewBox.isNull() )
2120 {
2121 return;
2122 }
2123
2125
2126 if ( static_cast< int >( size ) < 1.0 || 10000.0 < size )
2127 {
2128 brush.setTextureImage( QImage() );
2129 }
2130 else
2131 {
2132 bool fitsInCache = true;
2134 QImage patternImage = QgsApplication::svgCache()->svgAsImage( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2135 context.renderContext().scaleFactor(), fitsInCache, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), svgParameters );
2136 if ( !fitsInCache )
2137 {
2138 QPicture patternPict = QgsApplication::svgCache()->svgAsPicture( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2139 context.renderContext().scaleFactor(), false, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
2140 double hwRatio = 1.0;
2141 if ( patternPict.width() > 0 )
2142 {
2143 hwRatio = static_cast< double >( patternPict.height() ) / static_cast< double >( patternPict.width() );
2144 }
2145 patternImage = QImage( static_cast< int >( size ), static_cast< int >( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
2146 patternImage.fill( 0 ); // transparent background
2147
2148 QPainter p( &patternImage );
2149 p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
2150 }
2151
2152 QTransform brushTransform;
2153 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2154 {
2155 QImage transparentImage = patternImage.copy();
2156 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2157 brush.setTextureImage( transparentImage );
2158 }
2159 else
2160 {
2161 brush.setTextureImage( patternImage );
2162 }
2163 brush.setTransform( brushTransform );
2164 }
2165}
2166
2168{
2169 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2170
2171 applyPattern( mBrush, mSvgFilePath, mPatternWidth, mPatternWidthUnit, mColor, mSvgStrokeColor, mSvgStrokeWidth, mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2172
2173 if ( mStroke )
2174 {
2175 mStroke->setRenderHints( mStroke->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
2176 mStroke->startRender( context.renderContext(), context.fields() );
2177 }
2178}
2179
2181{
2182 if ( mStroke )
2183 {
2184 mStroke->stopRender( context.renderContext() );
2185 }
2186}
2187
2188void QgsSVGFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
2189{
2190 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
2191
2192 if ( mStroke )
2193 {
2194 const bool useSelectedColor = SELECT_FILL_BORDER && shouldRenderUsingSelectionColor( context );
2195 mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, useSelectedColor );
2196 if ( rings )
2197 {
2198 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
2199 {
2200 mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, useSelectedColor );
2201 }
2202 }
2203 }
2204}
2205
2207{
2208 QVariantMap map;
2209 if ( !mSvgFilePath.isEmpty() )
2210 {
2211 map.insert( u"svgFile"_s, mSvgFilePath );
2212 }
2213 else
2214 {
2215 map.insert( u"data"_s, QString( mSvgData.toHex() ) );
2216 }
2217
2218 map.insert( u"width"_s, QString::number( mPatternWidth ) );
2219 map.insert( u"angle"_s, QString::number( mAngle ) );
2220
2221 //svg parameters
2222 map.insert( u"color"_s, QgsColorUtils::colorToString( mColor ) );
2223 map.insert( u"outline_color"_s, QgsColorUtils::colorToString( mSvgStrokeColor ) );
2224 map.insert( u"outline_width"_s, QString::number( mSvgStrokeWidth ) );
2225
2226 //units
2227 map.insert( u"pattern_width_unit"_s, QgsUnitTypes::encodeUnit( mPatternWidthUnit ) );
2228 map.insert( u"pattern_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mPatternWidthMapUnitScale ) );
2229 map.insert( u"svg_outline_width_unit"_s, QgsUnitTypes::encodeUnit( mSvgStrokeWidthUnit ) );
2230 map.insert( u"svg_outline_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mSvgStrokeWidthMapUnitScale ) );
2231 map.insert( u"outline_width_unit"_s, QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
2232 map.insert( u"outline_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
2233
2234 map[u"parameters"_s] = QgsProperty::propertyMapToVariantMap( mParameters );
2235
2236 return map;
2237}
2238
2240{
2241 std::unique_ptr< QgsSVGFillSymbolLayer > clonedLayer;
2242 if ( !mSvgFilePath.isEmpty() )
2243 {
2244 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgFilePath, mPatternWidth, mAngle );
2245 clonedLayer->setSvgFillColor( mColor );
2246 clonedLayer->setSvgStrokeColor( mSvgStrokeColor );
2247 clonedLayer->setSvgStrokeWidth( mSvgStrokeWidth );
2248 }
2249 else
2250 {
2251 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgData, mPatternWidth, mAngle );
2252 }
2253
2254 clonedLayer->setPatternWidthUnit( mPatternWidthUnit );
2255 clonedLayer->setPatternWidthMapUnitScale( mPatternWidthMapUnitScale );
2256 clonedLayer->setSvgStrokeWidthUnit( mSvgStrokeWidthUnit );
2257 clonedLayer->setSvgStrokeWidthMapUnitScale( mSvgStrokeWidthMapUnitScale );
2258 clonedLayer->setStrokeWidthUnit( mStrokeWidthUnit );
2259 clonedLayer->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
2260
2261 clonedLayer->setParameters( mParameters );
2262
2263 if ( mStroke )
2264 {
2265 clonedLayer->setSubSymbol( mStroke->clone() );
2266 }
2267 copyDataDefinedProperties( clonedLayer.get() );
2268 copyPaintEffect( clonedLayer.get() );
2269 return clonedLayer.release();
2270}
2271
2272void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2273{
2274 QgsSldExportContext context;
2275 context.setExtraProperties( props );
2276 toSld( doc, element, context );
2277}
2278
2279bool QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
2280{
2281 const QVariantMap props = context.extraProperties();
2282 QDomElement symbolizerElem = doc.createElement( u"se:PolygonSymbolizer"_s );
2283 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
2284 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
2285 element.appendChild( symbolizerElem );
2286
2287 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
2288
2289 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
2290 symbolizerElem.appendChild( fillElem );
2291
2292 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
2293 fillElem.appendChild( graphicFillElem );
2294
2295 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
2296 graphicFillElem.appendChild( graphicElem );
2297
2298 if ( !mSvgFilePath.isEmpty() )
2299 {
2300 // encode a parametric SVG reference
2301 double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
2302 double strokeWidth = QgsSymbolLayerUtils::rescaleUom( mSvgStrokeWidth, mSvgStrokeWidthUnit, props );
2303 QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgStrokeColor, strokeWidth, context );
2304 }
2305 else
2306 {
2307 // TODO: create svg from data
2308 // <se:InlineContent>
2309 // watch out for https://osgeo-org.atlassian.net/browse/GEOT-5206 !
2310 context.pushWarning( QObject::tr( "Exporting embedded SVG content to SLD is not supported" ) );
2311 }
2312
2313 // <Rotation>
2314 QString angleFunc;
2315 bool ok;
2316 double angle = props.value( u"angle"_s, u"0"_s ).toDouble( &ok );
2317 if ( !ok )
2318 {
2319 angleFunc = u"%1 + %2"_s.arg( props.value( u"angle"_s, u"0"_s ).toString() ).arg( mAngle );
2320 }
2321 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2322 {
2323 angleFunc = QString::number( angle + mAngle );
2324 }
2325 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc, context );
2326
2327 if ( mStroke )
2328 {
2329 // the stroke sub symbol should be stored within the Stroke element,
2330 // but it will be stored in a separated LineSymbolizer because it could
2331 // have more than one layer
2332 mStroke->toSld( doc, element, context );
2333 }
2334 return true;
2335}
2336
2338{
2339 return mPatternWidthUnit == Qgis::RenderUnit::MapUnits || mPatternWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2340 || mSvgStrokeWidthUnit == Qgis::RenderUnit::MapUnits || mSvgStrokeWidthUnit == Qgis::RenderUnit::MetersInMapUnits;
2341}
2342
2344{
2345 return mStroke.get();
2346}
2347
2349{
2350 if ( !symbol ) //unset current stroke
2351 {
2352 mStroke.reset( nullptr );
2353 return true;
2354 }
2355
2356 if ( symbol->type() != Qgis::SymbolType::Line )
2357 {
2358 delete symbol;
2359 return false;
2360 }
2361
2362 QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
2363 if ( lineSymbol )
2364 {
2365 mStroke.reset( lineSymbol );
2366 return true;
2367 }
2368
2369 delete symbol;
2370 return false;
2371}
2372
2374{
2375 if ( mStroke && mStroke->symbolLayer( 0 ) )
2376 {
2377 double subLayerBleed = mStroke->symbolLayer( 0 )->estimateMaxBleed( context );
2378 return subLayerBleed;
2379 }
2380 return 0;
2381}
2382
2384{
2385 Q_UNUSED( context )
2386 if ( !mStroke )
2387 {
2388 return QColor( Qt::black );
2389 }
2390 return mStroke->color();
2391}
2392
2393QSet<QString> QgsSVGFillSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2394{
2395 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2396 if ( mStroke )
2397 attr.unite( mStroke->usedAttributes( context ) );
2398 return attr;
2399}
2400
2402{
2404 return true;
2405 if ( mStroke && mStroke->hasDataDefinedProperties() )
2406 return true;
2407 return false;
2408}
2409
2411{
2412 QString path, mimeType;
2413 QColor fillColor, strokeColor;
2414 Qt::PenStyle penStyle;
2415 double size, strokeWidth;
2416
2417 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
2418 if ( fillElem.isNull() )
2419 return nullptr;
2420
2421 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
2422 if ( graphicFillElem.isNull() )
2423 return nullptr;
2424
2425 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
2426 if ( graphicElem.isNull() )
2427 return nullptr;
2428
2429 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
2430 return nullptr;
2431
2432 if ( mimeType != "image/svg+xml"_L1 )
2433 return nullptr;
2434
2435 QgsSymbolLayerUtils::lineFromSld( graphicElem, penStyle, strokeColor, strokeWidth );
2436
2437 double scaleFactor = 1.0;
2438 const QString uom = element.attribute( u"uom"_s );
2439 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2440 size = size * scaleFactor;
2441 strokeWidth = strokeWidth * scaleFactor;
2442
2443 double angle = 0.0;
2444 QString angleFunc;
2445 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2446 {
2447 bool ok;
2448 double d = angleFunc.toDouble( &ok );
2449 if ( ok )
2450 angle = d;
2451 }
2452
2453 auto sl = std::make_unique< QgsSVGFillSymbolLayer >( path, size, angle );
2454 sl->setOutputUnit( sldUnitSize );
2455 sl->setSvgFillColor( fillColor );
2456 sl->setSvgStrokeColor( strokeColor );
2457 sl->setSvgStrokeWidth( strokeWidth );
2458
2459 // try to get the stroke
2460 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
2461 if ( !strokeElem.isNull() )
2462 {
2463 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createLineLayerFromSld( strokeElem );
2464 if ( l )
2465 {
2466 QgsSymbolLayerList layers;
2467 layers.append( l.release() );
2468 sl->setSubSymbol( new QgsLineSymbol( layers ) );
2469 }
2470 }
2471
2472 return sl.release();
2473}
2474
2476{
2480 {
2481 return; //no data defined settings
2482 }
2483
2485 {
2488 }
2489
2490 double width = mPatternWidth;
2492 {
2493 context.setOriginalValueVariable( mPatternWidth );
2494 width = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::Width, context.renderContext().expressionContext(), mPatternWidth );
2495 }
2496 QString svgFile = mSvgFilePath;
2498 {
2499 context.setOriginalValueVariable( mSvgFilePath );
2501 context.renderContext().pathResolver() );
2502 }
2503 QColor svgFillColor = mColor;
2505 {
2508 }
2509 QColor svgStrokeColor = mSvgStrokeColor;
2511 {
2512 context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2514 }
2515 double strokeWidth = mSvgStrokeWidth;
2517 {
2518 context.setOriginalValueVariable( mSvgStrokeWidth );
2519 strokeWidth = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::StrokeWidth, context.renderContext().expressionContext(), mSvgStrokeWidth );
2520 }
2521 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2522
2523 applyPattern( mBrush, svgFile, width, mPatternWidthUnit, svgFillColor, svgStrokeColor, strokeWidth,
2524 mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2525
2526}
2527
2528void QgsSVGFillSymbolLayer::storeViewBox()
2529{
2530 if ( !mSvgData.isEmpty() )
2531 {
2532 QSvgRenderer r( mSvgData );
2533 if ( r.isValid() )
2534 {
2535 mSvgViewBox = r.viewBoxF();
2536 return;
2537 }
2538 }
2539
2540 mSvgViewBox = QRectF();
2541}
2542
2543void QgsSVGFillSymbolLayer::setDefaultSvgParams()
2544{
2545 if ( mSvgFilePath.isEmpty() )
2546 {
2547 return;
2548 }
2549
2550 bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
2551 bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
2552 QColor defaultFillColor, defaultStrokeColor;
2553 double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
2554 QgsApplication::svgCache()->containsParams( mSvgFilePath, hasFillParam, hasDefaultFillColor, defaultFillColor,
2555 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
2556 hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2557 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
2558 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
2559
2560 double newFillOpacity = hasFillOpacityParam ? mColor.alphaF() : 1.0;
2561 double newStrokeOpacity = hasStrokeOpacityParam ? mSvgStrokeColor.alphaF() : 1.0;
2562
2563 if ( hasDefaultFillColor )
2564 {
2565 mColor = defaultFillColor;
2566 mColor.setAlphaF( newFillOpacity );
2567 }
2568 if ( hasDefaultFillOpacity )
2569 {
2570 mColor.setAlphaF( defaultFillOpacity );
2571 }
2572 if ( hasDefaultStrokeColor )
2573 {
2574 mSvgStrokeColor = defaultStrokeColor;
2575 mSvgStrokeColor.setAlphaF( newStrokeOpacity );
2576 }
2577 if ( hasDefaultStrokeOpacity )
2578 {
2579 mSvgStrokeColor.setAlphaF( defaultStrokeOpacity );
2580 }
2581 if ( hasDefaultStrokeWidth )
2582 {
2583 mSvgStrokeWidth = defaultStrokeWidth;
2584 }
2585}
2586
2587void QgsSVGFillSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2588{
2589 mParameters = parameters;
2590}
2591
2592
2595{
2596 mFillLineSymbol = std::make_unique<QgsLineSymbol>( );
2597 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
2598}
2599
2601
2603{
2604 mFillLineSymbol->setWidth( w );
2605 mLineWidth = w;
2606}
2607
2609{
2610 mFillLineSymbol->setColor( c );
2611 mColor = c;
2612}
2613
2615{
2616 return mFillLineSymbol ? mFillLineSymbol->color() : mColor;
2617}
2618
2620{
2621 if ( !symbol )
2622 {
2623 return false;
2624 }
2625
2626 if ( symbol->type() == Qgis::SymbolType::Line )
2627 {
2628 mFillLineSymbol.reset( qgis::down_cast<QgsLineSymbol *>( symbol ) );
2629 return true;
2630 }
2631 delete symbol;
2632 return false;
2633}
2634
2636{
2637 return mFillLineSymbol.get();
2638}
2639
2641{
2642 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2643 if ( mFillLineSymbol )
2644 attr.unite( mFillLineSymbol->usedAttributes( context ) );
2645 return attr;
2646}
2647
2649{
2651 return true;
2652 if ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
2653 return true;
2654 return false;
2655}
2656
2658{
2659 installMasks( context, true );
2660
2661 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2662}
2663
2665{
2666 removeMasks( context, true );
2667
2668 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2669}
2670
2672{
2673
2674 double lineAngleRad { qDegreesToRadians( mLineAngle ) };
2675
2676 const int quadrant { static_cast<int>( lineAngleRad / M_PI_2 ) };
2677 Q_ASSERT( quadrant >= 0 && quadrant <= 3 );
2678
2679 switch ( quadrant )
2680 {
2681 case 0:
2682 {
2683 break;
2684 }
2685 case 1:
2686 {
2687 lineAngleRad -= M_PI / 2;
2688 break;
2689 }
2690 case 2:
2691 {
2692 lineAngleRad -= M_PI;
2693 break;
2694 }
2695 case 3:
2696 {
2697 lineAngleRad -= M_PI + M_PI_2;
2698 break;
2699 }
2700 }
2701
2702
2703 double distancePx { QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, {} ) };
2704
2705 QSize size { static_cast<int>( distancePx ), static_cast<int>( distancePx ) };
2706
2707 if ( static_cast<int>( mLineAngle ) % 90 != 0 )
2708 {
2709 size = QSize( static_cast<int>( distancePx / std::sin( lineAngleRad ) ), static_cast<int>( distancePx / std::cos( lineAngleRad ) ) );
2710 }
2711
2712 QPixmap pixmap( size );
2713 pixmap.fill( Qt::transparent );
2714 QPainter painter;
2715 painter.begin( &pixmap );
2716 painter.setRenderHint( QPainter::Antialiasing );
2717 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
2722 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
2723
2724 std::unique_ptr< QgsLinePatternFillSymbolLayer > layerClone( clone() );
2725 layerClone->setOffset( 0 );
2726 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
2727 painter.end();
2728 return pixmap.toImage();
2729}
2730
2732{
2733 return 0;
2734}
2735
2737{
2739 mDistanceUnit = unit;
2740 mLineWidthUnit = unit;
2741 mOffsetUnit = unit;
2742
2743 if ( mFillLineSymbol )
2744 mFillLineSymbol->setOutputUnit( unit );
2745}
2746
2748{
2750 if ( mDistanceUnit != unit || mLineWidthUnit != unit || ( mOffsetUnit != unit && mOffsetUnit != Qgis::RenderUnit::Percentage ) )
2751 {
2753 }
2754 return unit;
2755}
2756
2758{
2759 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
2760 || mLineWidthUnit == Qgis::RenderUnit::MapUnits || mLineWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2761 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
2762}
2763
2765{
2767 mDistanceMapUnitScale = scale;
2768 mLineWidthMapUnitScale = scale;
2769 mOffsetMapUnitScale = scale;
2770}
2771
2773{
2774 if ( QgsImageFillSymbolLayer::mapUnitScale() == mDistanceMapUnitScale &&
2775 mDistanceMapUnitScale == mLineWidthMapUnitScale &&
2776 mLineWidthMapUnitScale == mOffsetMapUnitScale )
2777 {
2778 return mDistanceMapUnitScale;
2779 }
2780 return QgsMapUnitScale();
2781}
2782
2784{
2785 auto patternLayer = std::make_unique< QgsLinePatternFillSymbolLayer >();
2786
2787 //default values
2788 double lineAngle = 45;
2789 double distance = 5;
2790 double lineWidth = 0.5;
2791 QColor color( Qt::black );
2792 double offset = 0.0;
2793
2794 if ( properties.contains( u"lineangle"_s ) )
2795 {
2796 //pre 2.5 projects used "lineangle"
2797 lineAngle = properties[u"lineangle"_s].toDouble();
2798 }
2799 else if ( properties.contains( u"angle"_s ) )
2800 {
2801 lineAngle = properties[u"angle"_s].toDouble();
2802 }
2803 patternLayer->setLineAngle( lineAngle );
2804
2805 if ( properties.contains( u"distance"_s ) )
2806 {
2807 distance = properties[u"distance"_s].toDouble();
2808 }
2809 patternLayer->setDistance( distance );
2810
2811 if ( properties.contains( u"linewidth"_s ) )
2812 {
2813 //pre 2.5 projects used "linewidth"
2814 lineWidth = properties[u"linewidth"_s].toDouble();
2815 }
2816 else if ( properties.contains( u"outline_width"_s ) )
2817 {
2818 lineWidth = properties[u"outline_width"_s].toDouble();
2819 }
2820 else if ( properties.contains( u"line_width"_s ) )
2821 {
2822 lineWidth = properties[u"line_width"_s].toDouble();
2823 }
2824 patternLayer->setLineWidth( lineWidth );
2825
2826 if ( properties.contains( u"color"_s ) )
2827 {
2828 color = QgsColorUtils::colorFromString( properties[u"color"_s].toString() );
2829 }
2830 else if ( properties.contains( u"outline_color"_s ) )
2831 {
2832 color = QgsColorUtils::colorFromString( properties[u"outline_color"_s].toString() );
2833 }
2834 else if ( properties.contains( u"line_color"_s ) )
2835 {
2836 color = QgsColorUtils::colorFromString( properties[u"line_color"_s].toString() );
2837 }
2838 patternLayer->setColor( color );
2839
2840 if ( properties.contains( u"offset"_s ) )
2841 {
2842 offset = properties[u"offset"_s].toDouble();
2843 }
2844 patternLayer->setOffset( offset );
2845
2846
2847 if ( properties.contains( u"distance_unit"_s ) )
2848 {
2849 patternLayer->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( properties[u"distance_unit"_s].toString() ) );
2850 }
2851 if ( properties.contains( u"distance_map_unit_scale"_s ) )
2852 {
2853 patternLayer->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"distance_map_unit_scale"_s].toString() ) );
2854 }
2855 if ( properties.contains( u"line_width_unit"_s ) )
2856 {
2857 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"line_width_unit"_s].toString() ) );
2858 }
2859 else if ( properties.contains( u"outline_width_unit"_s ) )
2860 {
2861 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"outline_width_unit"_s].toString() ) );
2862 }
2863 if ( properties.contains( u"line_width_map_unit_scale"_s ) )
2864 {
2865 patternLayer->setLineWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"line_width_map_unit_scale"_s].toString() ) );
2866 }
2867 if ( properties.contains( u"offset_unit"_s ) )
2868 {
2869 patternLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
2870 }
2871 if ( properties.contains( u"offset_map_unit_scale"_s ) )
2872 {
2873 patternLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
2874 }
2875 if ( properties.contains( u"outline_width_unit"_s ) )
2876 {
2877 patternLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"outline_width_unit"_s].toString() ) );
2878 }
2879 if ( properties.contains( u"outline_width_map_unit_scale"_s ) )
2880 {
2881 patternLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"outline_width_map_unit_scale"_s].toString() ) );
2882 }
2883 if ( properties.contains( u"coordinate_reference"_s ) )
2884 {
2885 patternLayer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[u"coordinate_reference"_s].toString() ) );
2886 }
2887 if ( properties.contains( u"clip_mode"_s ) )
2888 {
2889 patternLayer->setClipMode( QgsSymbolLayerUtils::decodeLineClipMode( properties.value( u"clip_mode"_s ).toString() ) );
2890 }
2891
2892 patternLayer->restoreOldDataDefinedProperties( properties );
2893
2894 return patternLayer.release();
2895}
2896
2898{
2899 return u"LinePatternFill"_s;
2900}
2901
2902bool QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double lineAngle, double distance )
2903{
2904 mBrush.setTextureImage( QImage() ); // set empty in case we have to return
2905
2906 if ( !mFillLineSymbol )
2907 {
2908 return true;
2909 }
2910 // We have to make a copy because marker intervals will have to be adjusted
2911 std::unique_ptr< QgsLineSymbol > fillLineSymbol( mFillLineSymbol->clone() );
2912 if ( !fillLineSymbol )
2913 {
2914 return true;
2915 }
2916
2917 const QgsRenderContext &ctx = context.renderContext();
2918 //double strokePixelWidth = lineWidth * QgsSymbolLayerUtils::pixelSizeScaleFactor( ctx, mLineWidthUnit, mLineWidthMapUnitScale );
2919 double outputPixelDist = ctx.convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
2920 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDist * mOffset / 100
2921 : ctx.convertToPainterUnits( mOffset, mOffsetUnit, mOffsetMapUnitScale );
2922
2923 // NOTE: this may need to be modified if we ever change from a forced rasterized/brush approach,
2924 // because potentially we may want to allow vector based line pattern fills where the first line
2925 // is offset by a large distance
2926
2927 // fix truncated pattern with larger offsets
2928 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDist );
2929 if ( outputPixelOffset > outputPixelDist / 2.0 )
2930 outputPixelOffset -= outputPixelDist;
2931
2932 // To get all patterns into image, we have to consider symbols size (estimateMaxBleed()).
2933 // For marker lines we have to get markers interval.
2934 double outputPixelBleed = 0;
2935 double outputPixelInterval = 0; // maximum interval
2936 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2937 {
2938 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2939 double outputPixelLayerBleed = layer->estimateMaxBleed( context.renderContext() );
2940 outputPixelBleed = std::max( outputPixelBleed, outputPixelLayerBleed );
2941
2942 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2943 if ( markerLineLayer )
2944 {
2945 double outputPixelLayerInterval = ctx.convertToPainterUnits( markerLineLayer->interval(), markerLineLayer->intervalUnit(), markerLineLayer->intervalMapUnitScale() );
2946
2947 // There may be multiple marker lines with different intervals.
2948 // In theory we should find the least common multiple, but that could be too
2949 // big (multiplication of intervals in the worst case).
2950 // Because patterns without small common interval would look strange, we
2951 // believe that the longest interval should usually be sufficient.
2952 outputPixelInterval = std::max( outputPixelInterval, outputPixelLayerInterval );
2953 }
2954 }
2955
2956 if ( outputPixelInterval > 0 )
2957 {
2958 // We have to adjust marker intervals to integer pixel size to get
2959 // repeatable pattern.
2960 double intervalScale = std::round( outputPixelInterval ) / outputPixelInterval;
2961 outputPixelInterval = std::round( outputPixelInterval );
2962
2963 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2964 {
2965 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2966
2967 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2968 if ( markerLineLayer )
2969 {
2970 markerLineLayer->setInterval( intervalScale * markerLineLayer->interval() );
2971 }
2972 }
2973 }
2974
2975 //create image
2976 int height, width;
2977 lineAngle = std::fmod( lineAngle, 360 );
2978 if ( lineAngle < 0 )
2979 lineAngle += 360;
2980 if ( qgsDoubleNear( lineAngle, 0 ) || qgsDoubleNear( lineAngle, 360 ) || qgsDoubleNear( lineAngle, 180 ) )
2981 {
2982 height = outputPixelDist;
2983 width = outputPixelInterval > 0 ? outputPixelInterval : height;
2984 }
2985 else if ( qgsDoubleNear( lineAngle, 90 ) || qgsDoubleNear( lineAngle, 270 ) )
2986 {
2987 width = outputPixelDist;
2988 height = outputPixelInterval > 0 ? outputPixelInterval : width;
2989 }
2990 else
2991 {
2992 height = outputPixelDist / std::cos( lineAngle * M_PI / 180 ); //keep perpendicular distance between lines constant
2993 width = outputPixelDist / std::sin( lineAngle * M_PI / 180 );
2994
2995 // recalculate real angle and distance after rounding to pixels
2996 lineAngle = 180 * std::atan2( static_cast< double >( height ), static_cast< double >( width ) ) / M_PI;
2997 if ( lineAngle < 0 )
2998 {
2999 lineAngle += 360.;
3000 }
3001
3002 height = std::abs( height );
3003 width = std::abs( width );
3004
3005 outputPixelDist = std::abs( height * std::cos( lineAngle * M_PI / 180 ) );
3006
3007 // Round offset to correspond to one pixel height, otherwise lines may
3008 // be shifted on tile border if offset falls close to pixel center
3009 int offsetHeight = static_cast< int >( std::round( outputPixelOffset / std::cos( lineAngle * M_PI / 180 ) ) );
3010 outputPixelOffset = offsetHeight * std::cos( lineAngle * M_PI / 180 );
3011 }
3012
3013 //depending on the angle, we might need to render into a larger image and use a subset of it
3014 double dx = 0;
3015 double dy = 0;
3016
3017 // Add buffer based on bleed but keep precisely the height/width ratio (angle)
3018 // thus we add integer multiplications of width and height covering the bleed
3019 int bufferMulti = static_cast< int >( std::max( std::ceil( outputPixelBleed / width ), std::ceil( outputPixelBleed / width ) ) );
3020
3021 // Always buffer at least once so that center of line marker in upper right corner
3022 // does not fall outside due to representation error
3023 bufferMulti = std::max( bufferMulti, 1 );
3024
3025 int xBuffer = width * bufferMulti;
3026 int yBuffer = height * bufferMulti;
3027 int innerWidth = width;
3028 int innerHeight = height;
3029 width += 2 * xBuffer;
3030 height += 2 * yBuffer;
3031
3032 //protect from zero width/height image and symbol layer from eating too much memory
3033 if ( width > 2000 || height > 2000 || width == 0 || height == 0 )
3034 {
3035 return false;
3036 }
3037
3038 QImage patternImage( width, height, QImage::Format_ARGB32 );
3039 patternImage.fill( 0 );
3040
3041 QPointF p1, p2, p3, p4, p5, p6;
3042 if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
3043 {
3044 p1 = QPointF( 0, yBuffer );
3045 p2 = QPointF( width, yBuffer );
3046 p3 = QPointF( 0, yBuffer + innerHeight );
3047 p4 = QPointF( width, yBuffer + innerHeight );
3048 }
3049 else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
3050 {
3051 p1 = QPointF( xBuffer, height );
3052 p2 = QPointF( xBuffer, 0 );
3053 p3 = QPointF( xBuffer + innerWidth, height );
3054 p4 = QPointF( xBuffer + innerWidth, 0 );
3055 }
3056 else if ( lineAngle > 0 && lineAngle < 90 )
3057 {
3058 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3059 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3060 p1 = QPointF( 0, height );
3061 p2 = QPointF( width, 0 );
3062 p3 = QPointF( -dx, height - dy );
3063 p4 = QPointF( width - dx, -dy );
3064 p5 = QPointF( dx, height + dy );
3065 p6 = QPointF( width + dx, dy );
3066 }
3067 else if ( lineAngle > 180 && lineAngle < 270 )
3068 {
3069 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3070 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3071 p1 = QPointF( width, 0 );
3072 p2 = QPointF( 0, height );
3073 p3 = QPointF( width - dx, -dy );
3074 p4 = QPointF( -dx, height - dy );
3075 p5 = QPointF( width + dx, dy );
3076 p6 = QPointF( dx, height + dy );
3077 }
3078 else if ( lineAngle > 90 && lineAngle < 180 )
3079 {
3080 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3081 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3082 p1 = QPointF( 0, 0 );
3083 p2 = QPointF( width, height );
3084 p5 = QPointF( dx, -dy );
3085 p6 = QPointF( width + dx, height - dy );
3086 p3 = QPointF( -dx, dy );
3087 p4 = QPointF( width - dx, height + dy );
3088 }
3089 else if ( lineAngle > 270 && lineAngle < 360 )
3090 {
3091 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3092 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3093 p1 = QPointF( width, height );
3094 p2 = QPointF( 0, 0 );
3095 p5 = QPointF( width + dx, height - dy );
3096 p6 = QPointF( dx, -dy );
3097 p3 = QPointF( width - dx, height + dy );
3098 p4 = QPointF( -dx, dy );
3099 }
3100
3101 if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
3102 {
3103 QPointF tempPt;
3104 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
3105 p3 = QPointF( tempPt.x(), tempPt.y() );
3106 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
3107 p4 = QPointF( tempPt.x(), tempPt.y() );
3108 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
3109 p5 = QPointF( tempPt.x(), tempPt.y() );
3110 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
3111 p6 = QPointF( tempPt.x(), tempPt.y() );
3112
3113 //update p1, p2 last
3114 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
3115 p1 = QPointF( tempPt.x(), tempPt.y() );
3116 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
3117 p2 = QPointF( tempPt.x(), tempPt.y() );
3118 }
3119
3120 QPainter p( &patternImage );
3121
3122#if 0
3123 // DEBUG: Draw rectangle
3124 p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
3125 QPen pen( QColor( Qt::black ) );
3126 pen.setWidthF( 0.1 );
3127 pen.setCapStyle( Qt::FlatCap );
3128 p.setPen( pen );
3129
3130 // To see this rectangle, comment buffer cut below.
3131 // Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
3132 QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 );
3133 p.drawPolygon( polygon );
3134
3135 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 );
3136 p.drawPolygon( polygon );
3137#endif
3138
3139 // Use antialiasing because without antialiasing lines are rendered to the
3140 // right and below the mathematically defined points (not symmetrical)
3141 // and such tiles become useless for are filling
3142 p.setRenderHint( QPainter::Antialiasing, true );
3143
3144 // line rendering needs context for drawing on patternImage
3145 QgsRenderContext lineRenderContext;
3146 lineRenderContext.setPainter( &p );
3147 lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3148 QgsMapToPixel mtp( context.renderContext().mapToPixel().mapUnitsPerPixel() );
3149 lineRenderContext.setMapToPixel( mtp );
3150 lineRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3152 lineRenderContext.setDisabledSymbolLayersV2( context.renderContext().disabledSymbolLayersV2() );
3153
3154 fillLineSymbol->setRenderHints( fillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3155 fillLineSymbol->startRender( lineRenderContext, context.fields() );
3156
3157 QVector<QPolygonF> polygons;
3158 polygons.append( QPolygonF() << p1 << p2 );
3159 polygons.append( QPolygonF() << p3 << p4 );
3160 if ( !qgsDoubleNear( lineAngle, 0 ) && !qgsDoubleNear( lineAngle, 360 ) && !qgsDoubleNear( lineAngle, 90 ) && !qgsDoubleNear( lineAngle, 180 ) && !qgsDoubleNear( lineAngle, 270 ) )
3161 {
3162 polygons.append( QPolygonF() << p5 << p6 );
3163 }
3164
3165 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3166 for ( const QPolygonF &polygon : std::as_const( polygons ) )
3167 {
3168 fillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, useSelectedColor );
3169 }
3170
3171 fillLineSymbol->stopRender( lineRenderContext );
3172 p.end();
3173
3174 // Cut off the buffer
3175 patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
3176
3177 //set image to mBrush
3178 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3179 {
3180 QImage transparentImage = patternImage.copy();
3181 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3182 brush.setTextureImage( transparentImage );
3183 }
3184 else
3185 {
3186 brush.setTextureImage( patternImage );
3187 }
3188
3189 QTransform brushTransform;
3190 brush.setTransform( brushTransform );
3191
3192 return true;
3193}
3194
3196{
3197 // if we are using a vector based output, we need to render points as vectors
3198 // (OR if the line has data defined symbology, in which case we need to evaluate this line-by-line)
3199 mRenderUsingLines = context.forceVectorRendering()
3200 || ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
3203
3204 if ( !mRenderUsingLines )
3205 {
3206 // optimised render for screen only, use image based brush
3207 // (fallback to line rendering when pattern image will result in too large a memory footprint)
3208 mRenderUsingLines = !applyPattern( context, mBrush, mLineAngle, mDistance );
3209 }
3210
3211 if ( mRenderUsingLines && mFillLineSymbol )
3212 {
3213 mFillLineSymbol->setRenderHints( mFillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3214 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3215 mFillLineSymbolRenderStarted = true;
3216 }
3217}
3218
3220{
3221 if ( mFillLineSymbolRenderStarted )
3222 {
3223 mFillLineSymbol->stopRender( context.renderContext() );
3224 mFillLineSymbolRenderStarted = false;
3225 }
3226}
3227
3228void QgsLinePatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3229{
3230 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3231 if ( !useSelectedColor && !mRenderUsingLines )
3232 {
3233 // use image based brush for speed
3234 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3235 return;
3236 }
3237
3238 if ( !mFillLineSymbol )
3239 return;
3240
3241 if ( !mFillLineSymbolRenderStarted )
3242 {
3243 mFillLineSymbol->setRenderHints( mFillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3244 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3245 mFillLineSymbolRenderStarted = true;
3246 }
3247
3248 // vector based output - so draw line by line!
3249 QPainter *p = context.renderContext().painter();
3250 if ( !p )
3251 {
3252 return;
3253 }
3254
3255 double lineAngle = mLineAngle;
3257 {
3258 context.setOriginalValueVariable( mLineAngle );
3260 }
3261
3262 double distance = mDistance;
3264 {
3265 context.setOriginalValueVariable( mDistance );
3267 }
3268 // Clip distance to a reasonable distance to avoid app freezes
3269 double outputPixelDistance = context.renderContext().convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
3270
3271 double offset = mOffset;
3272 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDistance * offset / 100
3273 : context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3274
3275 // fix truncated pattern with larger offsets
3276 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDistance );
3277 if ( outputPixelOffset > outputPixelDistance / 2.0 )
3278 outputPixelOffset -= outputPixelDistance;
3279
3280 p->setPen( QPen( Qt::NoPen ) );
3281
3282 // if invalid parameters, skip out
3283 if ( qgsDoubleNear( distance, 0 ) )
3284 return;
3285
3286 // Clip distance to a reasonable distance to avoid app freezes
3287 outputPixelDistance = std::max(
3289 ? 0.1
3290 : 0.025,
3291 outputPixelDistance );
3292
3293 p->save();
3294
3295 Qgis::LineClipMode clipMode = mClipMode;
3297 {
3299 bool ok = false;
3300 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::LineClipping, context.renderContext().expressionContext(), QString(), &ok );
3301 if ( ok )
3302 {
3303 Qgis::LineClipMode decodedMode = QgsSymbolLayerUtils::decodeLineClipMode( valueString, &ok );
3304 if ( ok )
3305 clipMode = decodedMode;
3306 }
3307 }
3308
3309 std::unique_ptr< QgsPolygon > shapePolygon;
3310 std::unique_ptr< QgsGeometryEngine > shapeEngine;
3311 switch ( clipMode )
3312 {
3314 break;
3315
3317 {
3318 shapePolygon = std::make_unique< QgsPolygon >();
3319 std::unique_ptr< QgsLineString > fromPolygon = QgsLineString::fromQPolygonF( points );
3320 shapePolygon->setExteriorRing( fromPolygon.release() );
3321 if ( rings )
3322 {
3323 for ( const QPolygonF &ring : *rings )
3324 {
3325 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
3326 shapePolygon->addInteriorRing( fromRing.release() );
3327 }
3328 }
3329 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3330 shapeEngine->prepareGeometry();
3331 break;
3332 }
3333
3335 {
3336 QPainterPath path;
3337 path.addPolygon( points );
3338 if ( rings )
3339 {
3340 for ( const QPolygonF &ring : *rings )
3341 {
3342 path.addPolygon( ring );
3343 }
3344 }
3345 p->setClipPath( path, Qt::IntersectClip );
3346 break;
3347 }
3348 }
3349
3350 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3351 const QRectF boundingRect = points.boundingRect();
3352
3353 QTransform invertedRotateTransform;
3354 double left;
3355 double top;
3356 double right;
3357 double bottom;
3358
3359 QTransform transform;
3360 if ( applyBrushTransform )
3361 {
3362 // rotation applies around center of feature
3363 transform.translate( -boundingRect.center().x(),
3364 -boundingRect.center().y() );
3365 transform.rotate( lineAngle );
3366 transform.translate( boundingRect.center().x(),
3367 boundingRect.center().y() );
3368 }
3369 else
3370 {
3371 // rotation applies around top of viewport
3372 transform.rotate( lineAngle );
3373 }
3374
3375 const QRectF transformedBounds = transform.map( points ).boundingRect();
3376
3377 // bounds are expanded out a bit to account for maximum line width
3378 const double buffer = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFillLineSymbol.get(), context.renderContext() );
3379 left = transformedBounds.left() - buffer * 2;
3380 top = transformedBounds.top() - buffer * 2;
3381 right = transformedBounds.right() + buffer * 2;
3382 bottom = transformedBounds.bottom() + buffer * 2;
3383 invertedRotateTransform = transform.inverted();
3384
3385 if ( !applyBrushTransform )
3386 {
3387 top -= transformedBounds.top() - ( outputPixelDistance * std::floor( transformedBounds.top() / outputPixelDistance ) );
3388 }
3389
3391 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
3392 const bool needsExpressionContext = mFillLineSymbol->hasDataDefinedProperties();
3393
3394 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3396
3397 int currentLine = 0;
3398 for ( double currentY = top; currentY <= bottom; currentY += outputPixelDistance )
3399 {
3400 if ( context.renderContext().renderingStopped() )
3401 break;
3402
3403 if ( needsExpressionContext )
3404 scope->addVariable( QgsExpressionContextScope::StaticVariable( u"symbol_line_number"_s, ++currentLine, true ) );
3405
3406 double x1 = left;
3407 double y1 = currentY;
3408 double x2 = left;
3409 double y2 = currentY;
3410 invertedRotateTransform.map( left, currentY - outputPixelOffset, &x1, &y1 );
3411 invertedRotateTransform.map( right, currentY - outputPixelOffset, &x2, &y2 );
3412
3413 if ( shapeEngine )
3414 {
3415 QgsLineString ls( QgsPoint( x1, y1 ), QgsPoint( x2, y2 ) );
3416 std::unique_ptr< QgsAbstractGeometry > intersection( shapeEngine->intersection( &ls ) );
3417 for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
3418 {
3420 {
3421 mFillLineSymbol->renderPolyline( ls->asQPolygonF(), context.feature(), context.renderContext(), -1, useSelectedColor );
3422 }
3423 }
3424 }
3425 else
3426 {
3427 mFillLineSymbol->renderPolyline( QPolygonF() << QPointF( x1, y1 ) << QPointF( x2, y2 ), context.feature(), context.renderContext(), -1, useSelectedColor );
3428 }
3429 }
3430
3431 p->restore();
3432
3434}
3435
3437{
3438 QVariantMap map = QgsImageFillSymbolLayer::properties();
3439 map.insert( u"angle"_s, QString::number( mLineAngle ) );
3440 map.insert( u"distance"_s, QString::number( mDistance ) );
3441 map.insert( u"line_width"_s, QString::number( mLineWidth ) );
3442 map.insert( u"color"_s, QgsColorUtils::colorToString( mColor ) );
3443 map.insert( u"offset"_s, QString::number( mOffset ) );
3444 map.insert( u"distance_unit"_s, QgsUnitTypes::encodeUnit( mDistanceUnit ) );
3445 map.insert( u"line_width_unit"_s, QgsUnitTypes::encodeUnit( mLineWidthUnit ) );
3446 map.insert( u"offset_unit"_s, QgsUnitTypes::encodeUnit( mOffsetUnit ) );
3447 map.insert( u"distance_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale ) );
3448 map.insert( u"line_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mLineWidthMapUnitScale ) );
3449 map.insert( u"offset_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ) );
3450 map.insert( u"outline_width_unit"_s, QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3451 map.insert( u"outline_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3452 map.insert( u"clip_mode"_s, QgsSymbolLayerUtils::encodeLineClipMode( mClipMode ) );
3453 return map;
3454}
3455
3457{
3459 if ( mFillLineSymbol )
3460 {
3461 clonedLayer->setSubSymbol( mFillLineSymbol->clone() );
3462 }
3463 copyPaintEffect( clonedLayer );
3464 copyDataDefinedProperties( clonedLayer );
3465 return clonedLayer;
3466}
3467
3468void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3469{
3470 QgsSldExportContext context;
3471 context.setExtraProperties( props );
3472 toSld( doc, element, context );
3473}
3474
3475bool QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
3476{
3477 const QVariantMap props = context.extraProperties();
3478 QDomElement symbolizerElem = doc.createElement( u"se:PolygonSymbolizer"_s );
3479 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
3480 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
3481 element.appendChild( symbolizerElem );
3482
3483 // <Geometry>
3484 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
3485
3486 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
3487 symbolizerElem.appendChild( fillElem );
3488
3489 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
3490 fillElem.appendChild( graphicFillElem );
3491
3492 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
3493 graphicFillElem.appendChild( graphicElem );
3494
3495 // Export to PNG (TODO: SVG)
3496 bool exportOk { false };
3497 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
3498 {
3499 const QImage image { toTiledPatternImage() };
3500 if ( ! image.isNull() )
3501 {
3502 const QFileInfo info { context.exportFilePath() };
3503 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( u"png"_s ) };
3504 pngPath = QgsFileUtils::uniquePath( pngPath );
3505 image.save( pngPath );
3506 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), u"image/png"_s, QColor(), image.height() );
3507 exportOk = true;
3508 }
3509 }
3510
3511 if ( ! exportOk )
3512 {
3513 //line properties must be inside the graphic definition
3514 QColor lineColor = mFillLineSymbol ? mFillLineSymbol->color() : QColor();
3515 double lineWidth = mFillLineSymbol ? mFillLineSymbol->width() : 0.0;
3516 lineWidth = QgsSymbolLayerUtils::rescaleUom( lineWidth, mLineWidthUnit, props );
3517 double distance = QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, props );
3518 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, u"horline"_s, QColor(), lineColor, Qt::SolidLine, context, lineWidth, distance );
3519
3520 // <Rotation>
3521 QString angleFunc;
3522 bool ok;
3523 double angle = props.value( u"angle"_s, u"0"_s ).toDouble( &ok );
3524 if ( !ok )
3525 {
3526 angleFunc = u"%1 + %2"_s.arg( props.value( u"angle"_s, u"0"_s ).toString() ).arg( mLineAngle );
3527 }
3528 else if ( !qgsDoubleNear( angle + mLineAngle, 0.0 ) )
3529 {
3530 angleFunc = QString::number( angle + mLineAngle );
3531 }
3532 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc, context );
3533
3534 // <se:Displacement>
3535 QPointF lineOffset( std::sin( mLineAngle ) * mOffset, std::cos( mLineAngle ) * mOffset );
3536 lineOffset = QgsSymbolLayerUtils::rescaleUom( lineOffset, mOffsetUnit, props );
3537 QgsSymbolLayerUtils::createDisplacementElement( doc, graphicElem, lineOffset );
3538 }
3539 return true;
3540}
3541
3542QString QgsLinePatternFillSymbolLayer::ogrFeatureStyleWidth( double widthScaleFactor ) const
3543{
3544 QString featureStyle;
3545 featureStyle.append( "Brush(" );
3546 featureStyle.append( u"fc:%1"_s.arg( mColor.name() ) );
3547 featureStyle.append( u",bc:%1"_s.arg( "#00000000"_L1 ) ); //transparent background
3548 featureStyle.append( ",id:\"ogr-brush-2\"" );
3549 featureStyle.append( u",a:%1"_s.arg( mLineAngle ) );
3550 featureStyle.append( u",s:%1"_s.arg( mLineWidth * widthScaleFactor ) );
3551 featureStyle.append( ",dx:0mm" );
3552 featureStyle.append( u",dy:%1mm"_s.arg( mDistance * widthScaleFactor ) );
3553 featureStyle.append( ')' );
3554 return featureStyle;
3555}
3556
3558{
3560 && ( !mFillLineSymbol || !mFillLineSymbol->hasDataDefinedProperties() ) )
3561 {
3562 return; //no data defined settings
3563 }
3564
3565 double lineAngle = mLineAngle;
3567 {
3568 context.setOriginalValueVariable( mLineAngle );
3570 }
3571 double distance = mDistance;
3573 {
3574 context.setOriginalValueVariable( mDistance );
3576 }
3577 applyPattern( context, mBrush, lineAngle, distance );
3578}
3579
3581{
3582 QString name;
3583 QColor fillColor, lineColor;
3584 double size, lineWidth;
3585 Qt::PenStyle lineStyle;
3586
3587 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
3588 if ( fillElem.isNull() )
3589 return nullptr;
3590
3591 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
3592 if ( graphicFillElem.isNull() )
3593 return nullptr;
3594
3595 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
3596 if ( graphicElem.isNull() )
3597 return nullptr;
3598
3599 if ( !QgsSymbolLayerUtils::wellKnownMarkerFromSld( graphicElem, name, fillColor, lineColor, lineStyle, lineWidth, size ) )
3600 return nullptr;
3601
3602 if ( name != "horline"_L1 )
3603 return nullptr;
3604
3605 double angle = 0.0;
3606 QString angleFunc;
3607 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3608 {
3609 bool ok;
3610 double d = angleFunc.toDouble( &ok );
3611 if ( ok )
3612 angle = d;
3613 }
3614
3615 double offset = 0.0;
3616 QPointF vectOffset;
3617 if ( QgsSymbolLayerUtils::displacementFromSldElement( graphicElem, vectOffset ) )
3618 {
3619 offset = std::sqrt( std::pow( vectOffset.x(), 2 ) + std::pow( vectOffset.y(), 2 ) );
3620 }
3621
3622 double scaleFactor = 1.0;
3623 const QString uom = element.attribute( u"uom"_s );
3624 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
3625 size = size * scaleFactor;
3626 lineWidth = lineWidth * scaleFactor;
3627
3628 auto sl = std::make_unique< QgsLinePatternFillSymbolLayer >();
3629 sl->setOutputUnit( sldUnitSize );
3630 sl->setColor( lineColor );
3631 sl->setLineWidth( lineWidth );
3632 sl->setLineAngle( angle );
3633 sl->setOffset( offset );
3634 sl->setDistance( size );
3635
3636 // try to get the stroke
3637 QDomElement strokeElem = element.firstChildElement( u"Stroke"_s );
3638 if ( !strokeElem.isNull() )
3639 {
3640 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createLineLayerFromSld( strokeElem );
3641 if ( l )
3642 {
3643 QgsSymbolLayerList layers;
3644 layers.append( l.release() );
3645 sl->setSubSymbol( new QgsLineSymbol( layers ) );
3646 }
3647 }
3648
3649 return sl.release();
3650}
3651
3652
3654
3657{
3658 mMarkerSymbol = std::make_unique<QgsMarkerSymbol>();
3659 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
3660}
3661
3663
3665{
3667 mDistanceXUnit = unit;
3668 mDistanceYUnit = unit;
3669 // don't change "percentage" units -- since they adapt directly to whatever other unit is set
3671 mDisplacementXUnit = unit;
3673 mDisplacementYUnit = unit;
3675 mOffsetXUnit = unit;
3677 mOffsetYUnit = unit;
3679 mRandomDeviationXUnit = unit;
3681 mRandomDeviationYUnit = unit;
3682
3683 if ( mMarkerSymbol )
3684 {
3685 mMarkerSymbol->setOutputUnit( unit );
3686 }
3687}
3688
3705
3717
3730
3746
3748{
3749 auto layer = std::make_unique< QgsPointPatternFillSymbolLayer >();
3750 if ( properties.contains( u"distance_x"_s ) )
3751 {
3752 layer->setDistanceX( properties[u"distance_x"_s].toDouble() );
3753 }
3754 if ( properties.contains( u"distance_y"_s ) )
3755 {
3756 layer->setDistanceY( properties[u"distance_y"_s].toDouble() );
3757 }
3758 if ( properties.contains( u"displacement_x"_s ) )
3759 {
3760 layer->setDisplacementX( properties[u"displacement_x"_s].toDouble() );
3761 }
3762 if ( properties.contains( u"displacement_y"_s ) )
3763 {
3764 layer->setDisplacementY( properties[u"displacement_y"_s].toDouble() );
3765 }
3766 if ( properties.contains( u"offset_x"_s ) )
3767 {
3768 layer->setOffsetX( properties[u"offset_x"_s].toDouble() );
3769 }
3770 if ( properties.contains( u"offset_y"_s ) )
3771 {
3772 layer->setOffsetY( properties[u"offset_y"_s].toDouble() );
3773 }
3774
3775 if ( properties.contains( u"distance_x_unit"_s ) )
3776 {
3777 layer->setDistanceXUnit( QgsUnitTypes::decodeRenderUnit( properties[u"distance_x_unit"_s].toString() ) );
3778 }
3779 if ( properties.contains( u"distance_x_map_unit_scale"_s ) )
3780 {
3781 layer->setDistanceXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"distance_x_map_unit_scale"_s].toString() ) );
3782 }
3783 if ( properties.contains( u"distance_y_unit"_s ) )
3784 {
3785 layer->setDistanceYUnit( QgsUnitTypes::decodeRenderUnit( properties[u"distance_y_unit"_s].toString() ) );
3786 }
3787 if ( properties.contains( u"distance_y_map_unit_scale"_s ) )
3788 {
3789 layer->setDistanceYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"distance_y_map_unit_scale"_s].toString() ) );
3790 }
3791 if ( properties.contains( u"displacement_x_unit"_s ) )
3792 {
3793 layer->setDisplacementXUnit( QgsUnitTypes::decodeRenderUnit( properties[u"displacement_x_unit"_s].toString() ) );
3794 }
3795 if ( properties.contains( u"displacement_x_map_unit_scale"_s ) )
3796 {
3797 layer->setDisplacementXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"displacement_x_map_unit_scale"_s].toString() ) );
3798 }
3799 if ( properties.contains( u"displacement_y_unit"_s ) )
3800 {
3801 layer->setDisplacementYUnit( QgsUnitTypes::decodeRenderUnit( properties[u"displacement_y_unit"_s].toString() ) );
3802 }
3803 if ( properties.contains( u"displacement_y_map_unit_scale"_s ) )
3804 {
3805 layer->setDisplacementYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"displacement_y_map_unit_scale"_s].toString() ) );
3806 }
3807 if ( properties.contains( u"offset_x_unit"_s ) )
3808 {
3809 layer->setOffsetXUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_x_unit"_s].toString() ) );
3810 }
3811 if ( properties.contains( u"offset_x_map_unit_scale"_s ) )
3812 {
3813 layer->setOffsetXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_x_map_unit_scale"_s].toString() ) );
3814 }
3815 if ( properties.contains( u"offset_y_unit"_s ) )
3816 {
3817 layer->setOffsetYUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_y_unit"_s].toString() ) );
3818 }
3819 if ( properties.contains( u"offset_y_map_unit_scale"_s ) )
3820 {
3821 layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_y_map_unit_scale"_s].toString() ) );
3822 }
3823
3824 if ( properties.contains( u"random_deviation_x"_s ) )
3825 {
3826 layer->setMaximumRandomDeviationX( properties[u"random_deviation_x"_s].toDouble() );
3827 }
3828 if ( properties.contains( u"random_deviation_y"_s ) )
3829 {
3830 layer->setMaximumRandomDeviationY( properties[u"random_deviation_y"_s].toDouble() );
3831 }
3832 if ( properties.contains( u"random_deviation_x_unit"_s ) )
3833 {
3834 layer->setRandomDeviationXUnit( QgsUnitTypes::decodeRenderUnit( properties[u"random_deviation_x_unit"_s].toString() ) );
3835 }
3836 if ( properties.contains( u"random_deviation_x_map_unit_scale"_s ) )
3837 {
3838 layer->setRandomDeviationXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"random_deviation_x_map_unit_scale"_s].toString() ) );
3839 }
3840 if ( properties.contains( u"random_deviation_y_unit"_s ) )
3841 {
3842 layer->setRandomDeviationYUnit( QgsUnitTypes::decodeRenderUnit( properties[u"random_deviation_y_unit"_s].toString() ) );
3843 }
3844 if ( properties.contains( u"random_deviation_y_map_unit_scale"_s ) )
3845 {
3846 layer->setRandomDeviationYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"random_deviation_y_map_unit_scale"_s].toString() ) );
3847 }
3848 unsigned long seed = 0;
3849 if ( properties.contains( u"seed"_s ) )
3850 seed = properties.value( u"seed"_s ).toUInt();
3851 else
3852 {
3853 // if we a creating a new point pattern fill from scratch, we default to a random seed
3854 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
3855 std::random_device rd;
3856 std::mt19937 mt( static_cast< int >( rd() ) );
3857 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
3858 seed = uniformDist( mt );
3859 }
3860 layer->setSeed( seed );
3861
3862 if ( properties.contains( u"outline_width_unit"_s ) )
3863 {
3864 layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[u"outline_width_unit"_s].toString() ) );
3865 }
3866 if ( properties.contains( u"outline_width_map_unit_scale"_s ) )
3867 {
3868 layer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"outline_width_map_unit_scale"_s].toString() ) );
3869 }
3870 if ( properties.contains( u"clip_mode"_s ) )
3871 {
3872 layer->setClipMode( QgsSymbolLayerUtils::decodeMarkerClipMode( properties.value( u"clip_mode"_s ).toString() ) );
3873 }
3874 if ( properties.contains( u"coordinate_reference"_s ) )
3875 {
3876 layer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[u"coordinate_reference"_s].toString() ) );
3877 }
3878
3879 if ( properties.contains( u"angle"_s ) )
3880 {
3881 layer->setAngle( properties[u"angle"_s].toDouble() );
3882 }
3883
3885
3886 return layer.release();
3887}
3888
3890{
3891 return u"PointPatternFill"_s;
3892}
3893
3894bool QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double distanceX, double distanceY,
3895 double displacementX, double displacementY, double offsetX, double offsetY )
3896{
3897 //render 3 rows and columns in one go to easily incorporate displacement
3898 const QgsRenderContext &ctx = context.renderContext();
3901
3902 double widthOffset = std::fmod(
3904 width );
3905 double heightOffset = std::fmod(
3907 height );
3908
3909 if ( width > 2000 || height > 2000 ) //protect symbol layer from eating too much memory
3910 {
3911 brush.setTextureImage( QImage() );
3912 return false;
3913 }
3914
3915 QImage patternImage( width, height, QImage::Format_ARGB32 );
3916 patternImage.fill( 0 );
3917 if ( patternImage.isNull() )
3918 {
3919 brush.setTextureImage( QImage() );
3920 return false;
3921 }
3922 if ( mMarkerSymbol )
3923 {
3924 QPainter p( &patternImage );
3925
3926 //marker rendering needs context for drawing on patternImage
3927 QgsRenderContext pointRenderContext;
3928 pointRenderContext.setRendererScale( context.renderContext().rendererScale() );
3929 pointRenderContext.setPainter( &p );
3930 pointRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3931
3933 QgsMapToPixel mtp( context.renderContext().mapToPixel().mapUnitsPerPixel() );
3934 pointRenderContext.setMapToPixel( mtp );
3935 pointRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3937
3939 mMarkerSymbol->startRender( pointRenderContext, context.fields() );
3940
3941 //render points on distance grid
3942 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3943 {
3944 for ( double currentY = -height; currentY <= height * 2.0; currentY += height )
3945 {
3946 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset, currentY + heightOffset ), context.feature(), pointRenderContext );
3947 }
3948 }
3949
3950 //render displaced points
3951 double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
3952 ? ( width * displacementX / 200 )
3953 : ctx.convertToPainterUnits( displacementX, mDisplacementXUnit, mDisplacementXMapUnitScale );
3954 double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
3955 ? ( height * displacementY / 200 )
3957 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3958 {
3959 for ( double currentY = -height / 2.0; currentY <= height * 2.0; currentY += height )
3960 {
3961 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + displacementPixelX, currentY + heightOffset ), context.feature(), pointRenderContext );
3962 }
3963 }
3964
3965 for ( double currentX = -width / 2.0; currentX <= width * 2.0; currentX += width )
3966 {
3967 for ( double currentY = -height; currentY <= height * 2.0; currentY += height / 2.0 )
3968 {
3969 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + ( std::fmod( currentY, height ) != 0 ? displacementPixelX : 0 ), currentY + heightOffset - displacementPixelY ), context.feature(), pointRenderContext );
3970 }
3971 }
3972
3973 mMarkerSymbol->stopRender( pointRenderContext );
3974 }
3975
3976 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3977 {
3978 QImage transparentImage = patternImage.copy();
3979 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3980 brush.setTextureImage( transparentImage );
3981 }
3982 else
3983 {
3984 brush.setTextureImage( patternImage );
3985 }
3986 QTransform brushTransform;
3987 brush.setTransform( brushTransform );
3988
3989 return true;
3990}
3991
3993{
3994 // if we are using a vector based output, we need to render points as vectors
3995 // (OR if the marker has data defined symbology, in which case we need to evaluate this point-by-point)
3996 mRenderUsingMarkers = context.forceVectorRendering()
3997 || ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
4001 || mClipMode != Qgis::MarkerClipMode::Shape
4004 || !qgsDoubleNear( mAngle, 0 )
4006
4007 if ( !mRenderUsingMarkers )
4008 {
4009 // optimised render for screen only, use image based brush
4010 // (fallback to line rendering when pattern image will result in too large a memory footprint)
4011 mRenderUsingMarkers = !applyPattern( context, mBrush, mDistanceX, mDistanceY, mDisplacementX, mDisplacementY, mOffsetX, mOffsetY );
4012 }
4013
4014 if ( mRenderUsingMarkers && mMarkerSymbol )
4015 {
4017 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
4019 }
4020}
4021
4023{
4025 {
4026 mMarkerSymbol->stopRender( context.renderContext() );
4028 }
4029}
4030
4032{
4033 installMasks( context, true );
4034
4035 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4036 // Otherwise generators used in the subsymbol will only render a single point per feature (they
4037 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
4038}
4039
4041{
4042 removeMasks( context, true );
4043
4044 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4045 // Otherwise generators used in the subsymbol will only render a single point per feature (they
4046 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
4047}
4048
4049void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4050{
4051 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4052 if ( !useSelectedColor && !mRenderUsingMarkers )
4053 {
4054 // use image based brush for speed
4055 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
4056 return;
4057 }
4058
4060 {
4062 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
4064 }
4065
4066 // vector based output - so draw dot by dot!
4067 QPainter *p = context.renderContext().painter();
4068 if ( !p )
4069 {
4070 return;
4071 }
4072
4073 double angle = mAngle;
4075 {
4078 }
4079
4080 double distanceX = mDistanceX;
4082 {
4085 }
4087
4088 double distanceY = mDistanceY;
4090 {
4093 }
4095
4096 double offsetX = mOffsetX;
4098 {
4101 }
4102 const double widthOffset = std::fmod(
4104 ? ( offsetX * width / 100 )
4106 width );
4107
4108 double offsetY = mOffsetY;
4110 {
4113 }
4114 const double heightOffset = std::fmod(
4116 ? ( offsetY * height / 100 )
4118 height );
4119
4122 {
4125 }
4126 const double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
4127 ? ( displacementX * width / 100 )
4129
4132 {
4135 }
4136 const double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
4137 ? ( displacementY * height / 100 )
4139
4140 p->setPen( QPen( Qt::NoPen ) );
4141
4142 // if invalid parameters, skip out
4143 if ( qgsDoubleNear( width, 0 ) || qgsDoubleNear( height, 0 ) || width < 0 || height < 0 )
4144 return;
4145
4146 // Clip width and heights steps to a reasonable distance to avoid app freezes
4147 width = std::max( ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderLayerTree )
4148 ? 0.1
4149 : 0.025,
4150 width );
4151 height = std::max( ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderLayerTree )
4152 ? 0.1
4153 : 0.025,
4154 height );
4155
4156 p->save();
4157
4158 Qgis::MarkerClipMode clipMode = mClipMode;
4160 {
4162 bool ok = false;
4163 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::MarkerClipping, context.renderContext().expressionContext(), QString(), &ok );
4164 if ( ok )
4165 {
4166 Qgis::MarkerClipMode decodedMode = QgsSymbolLayerUtils::decodeMarkerClipMode( valueString, &ok );
4167 if ( ok )
4168 clipMode = decodedMode;
4169 }
4170 }
4171
4172 std::unique_ptr< QgsPolygon > shapePolygon;
4173 std::unique_ptr< QgsGeometryEngine > shapeEngine;
4174 switch ( clipMode )
4175 {
4179 {
4180 shapePolygon = std::make_unique< QgsPolygon >();
4181 std::unique_ptr< QgsLineString > fromPolygon = QgsLineString::fromQPolygonF( points );
4182 shapePolygon->setExteriorRing( fromPolygon.release() );
4183 if ( rings )
4184 {
4185 for ( const QPolygonF &ring : *rings )
4186 {
4187 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
4188 shapePolygon->addInteriorRing( fromRing.release() );
4189 }
4190 }
4191 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
4192 shapeEngine->prepareGeometry();
4193 break;
4194 }
4195
4197 {
4198 QPainterPath path;
4199 path.addPolygon( points );
4200 if ( rings )
4201 {
4202 for ( const QPolygonF &ring : *rings )
4203 {
4204 path.addPolygon( ring );
4205 }
4206 }
4207 p->setClipPath( path, Qt::IntersectClip );
4208 break;
4209 }
4210 }
4211
4212 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
4213 const QRectF boundingRect = points.boundingRect();
4214
4215 QTransform invertedRotateTransform;
4216 double left;
4217 double top;
4218 double right;
4219 double bottom;
4220
4221 if ( !qgsDoubleNear( angle, 0 ) )
4222 {
4223 QTransform transform;
4224 if ( applyBrushTransform )
4225 {
4226 // rotation applies around center of feature
4227 transform.translate( -boundingRect.center().x(),
4228 -boundingRect.center().y() );
4229 transform.rotate( -angle );
4230 transform.translate( boundingRect.center().x(),
4231 boundingRect.center().y() );
4232 }
4233 else
4234 {
4235 // rotation applies around top of viewport
4236 transform.rotate( -angle );
4237 }
4238
4239 const QRectF transformedBounds = transform.map( points ).boundingRect();
4240 left = transformedBounds.left() - 2 * width;
4241 top = transformedBounds.top() - 2 * height;
4242 right = transformedBounds.right() + 2 * width;
4243 bottom = transformedBounds.bottom() + 2 * height;
4244 invertedRotateTransform = transform.inverted();
4245
4246 if ( !applyBrushTransform )
4247 {
4248 left -= transformedBounds.left() - ( width * std::floor( transformedBounds.left() / width ) );
4249 top -= transformedBounds.top() - ( height * std::floor( transformedBounds.top() / height ) );
4250 }
4251 }
4252 else
4253 {
4254 left = boundingRect.left() - 2 * width;
4255 top = boundingRect.top() - 2 * height;
4256 right = boundingRect.right() + 2 * width;
4257 bottom = boundingRect.bottom() + 2 * height;
4258
4259 if ( !applyBrushTransform )
4260 {
4261 left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
4262 top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
4263 }
4264 }
4265
4266 unsigned long seed = mSeed;
4268 {
4269 context.renderContext().expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
4271 }
4272
4273 double maxRandomDeviationX = mRandomDeviationX;
4275 {
4276 context.setOriginalValueVariable( maxRandomDeviationX );
4277 maxRandomDeviationX = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::RandomOffsetX, context.renderContext().expressionContext(), maxRandomDeviationX );
4278 }
4279 const double maxRandomDeviationPixelX = mRandomDeviationXUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationX * width / 100 )
4281
4282 double maxRandomDeviationY = mRandomDeviationY;
4284 {
4285 context.setOriginalValueVariable( maxRandomDeviationY );
4286 maxRandomDeviationY = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::RandomOffsetY, context.renderContext().expressionContext(), maxRandomDeviationY );
4287 }
4288 const double maxRandomDeviationPixelY = mRandomDeviationYUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationY * height / 100 )
4290
4291 std::random_device rd;
4292 std::mt19937 mt( seed == 0 ? rd() : seed );
4293 std::uniform_real_distribution<> uniformDist( 0, 1 );
4294 const bool useRandomShift = !qgsDoubleNear( maxRandomDeviationPixelX, 0 ) || !qgsDoubleNear( maxRandomDeviationPixelY, 0 );
4295
4297 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
4298 int pointNum = 0;
4299 const bool needsExpressionContext = mMarkerSymbol->hasDataDefinedProperties();
4300
4301 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4303
4304 const double prevOpacity = mMarkerSymbol->opacity();
4305 mMarkerSymbol->setOpacity( mMarkerSymbol->opacity() * context.opacity() );
4306
4307 bool alternateColumn = false;
4308 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
4309 for ( double currentX = left; currentX <= right; currentX += width, alternateColumn = !alternateColumn )
4310 {
4311 if ( context.renderContext().renderingStopped() )
4312 break;
4313
4314 if ( needsExpressionContext )
4315 scope->addVariable( QgsExpressionContextScope::StaticVariable( u"symbol_marker_column"_s, ++currentCol, true ) );
4316
4317 bool alternateRow = false;
4318 const double columnX = currentX + widthOffset;
4319 int currentRow = -3;
4320 for ( double currentY = top; currentY <= bottom; currentY += height, alternateRow = !alternateRow )
4321 {
4322 if ( context.renderContext().renderingStopped() )
4323 break;
4324
4325 double y = currentY + heightOffset;
4326 double x = columnX;
4327 if ( alternateRow )
4328 x += displacementPixelX;
4329
4330 if ( !alternateColumn )
4331 y -= displacementPixelY;
4332
4333 if ( !qgsDoubleNear( angle, 0 ) )
4334 {
4335 double xx = x;
4336 double yy = y;
4337 invertedRotateTransform.map( xx, yy, &x, &y );
4338 }
4339
4340 if ( useRandomShift )
4341 {
4342 x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
4343 y += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelY;
4344 }
4345
4346 if ( needsExpressionContext )
4347 {
4349 scope->addVariable( QgsExpressionContextScope::StaticVariable( u"symbol_marker_row"_s, ++currentRow, true ) );
4350 }
4351
4352 if ( shapeEngine )
4353 {
4354 bool renderPoint = true;
4355 switch ( clipMode )
4356 {
4358 {
4359 // 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
4360 const QgsRectangle markerRect = QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) );
4361 QgsPoint p( markerRect.center() );
4362 renderPoint = shapeEngine->intersects( &p );
4363 break;
4364 }
4365
4368 {
4369 const QgsGeometry markerBounds = QgsGeometry::fromRect( QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) ) );
4370
4372 renderPoint = shapeEngine->contains( markerBounds.constGet() );
4373 else
4374 renderPoint = shapeEngine->intersects( markerBounds.constGet() );
4375 break;
4376 }
4377
4379 break;
4380 }
4381
4382 if ( !renderPoint )
4383 continue;
4384 }
4385
4386 mMarkerSymbol->renderPoint( QPointF( x, y ), context.feature(), context.renderContext(), -1, useSelectedColor );
4387 }
4388 }
4389
4390 mMarkerSymbol->setOpacity( prevOpacity );
4391
4392 p->restore();
4393
4395}
4396
4398{
4399 QVariantMap map = QgsImageFillSymbolLayer::properties();
4400 map.insert( u"distance_x"_s, QString::number( mDistanceX ) );
4401 map.insert( u"distance_y"_s, QString::number( mDistanceY ) );
4402 map.insert( u"displacement_x"_s, QString::number( mDisplacementX ) );
4403 map.insert( u"displacement_y"_s, QString::number( mDisplacementY ) );
4404 map.insert( u"offset_x"_s, QString::number( mOffsetX ) );
4405 map.insert( u"offset_y"_s, QString::number( mOffsetY ) );
4406 map.insert( u"distance_x_unit"_s, QgsUnitTypes::encodeUnit( mDistanceXUnit ) );
4407 map.insert( u"distance_y_unit"_s, QgsUnitTypes::encodeUnit( mDistanceYUnit ) );
4408 map.insert( u"displacement_x_unit"_s, QgsUnitTypes::encodeUnit( mDisplacementXUnit ) );
4409 map.insert( u"displacement_y_unit"_s, QgsUnitTypes::encodeUnit( mDisplacementYUnit ) );
4410 map.insert( u"offset_x_unit"_s, QgsUnitTypes::encodeUnit( mOffsetXUnit ) );
4411 map.insert( u"offset_y_unit"_s, QgsUnitTypes::encodeUnit( mOffsetYUnit ) );
4412 map.insert( u"distance_x_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceXMapUnitScale ) );
4413 map.insert( u"distance_y_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceYMapUnitScale ) );
4414 map.insert( u"displacement_x_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementXMapUnitScale ) );
4415 map.insert( u"displacement_y_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementYMapUnitScale ) );
4416 map.insert( u"offset_x_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetXMapUnitScale ) );
4417 map.insert( u"offset_y_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetYMapUnitScale ) );
4418 map.insert( u"outline_width_unit"_s, QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
4419 map.insert( u"outline_width_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
4420 map.insert( u"clip_mode"_s, QgsSymbolLayerUtils::encodeMarkerClipMode( mClipMode ) );
4421 map.insert( u"random_deviation_x"_s, QString::number( mRandomDeviationX ) );
4422 map.insert( u"random_deviation_y"_s, QString::number( mRandomDeviationY ) );
4423 map.insert( u"random_deviation_x_unit"_s, QgsUnitTypes::encodeUnit( mRandomDeviationXUnit ) );
4424 map.insert( u"random_deviation_y_unit"_s, QgsUnitTypes::encodeUnit( mRandomDeviationYUnit ) );
4425 map.insert( u"random_deviation_x_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationXMapUnitScale ) );
4426 map.insert( u"random_deviation_y_map_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationYMapUnitScale ) );
4427 map.insert( u"seed"_s, QString::number( mSeed ) );
4428 map.insert( u"angle"_s, mAngle );
4429 return map;
4430}
4431
4433{
4435 if ( mMarkerSymbol )
4436 {
4437 clonedLayer->setSubSymbol( mMarkerSymbol->clone() );
4438 }
4439 clonedLayer->setClipMode( mClipMode );
4440 copyDataDefinedProperties( clonedLayer );
4441 copyPaintEffect( clonedLayer );
4442 return clonedLayer;
4443}
4444
4445void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4446{
4447 QgsSldExportContext context;
4448 context.setExtraProperties( props );
4449 toSld( doc, element, context );
4450}
4451
4452bool QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
4453{
4454 const QVariantMap props = context.extraProperties();
4455 for ( int symbolLayerIdx = 0; symbolLayerIdx < mMarkerSymbol->symbolLayerCount(); symbolLayerIdx++ )
4456 {
4457 QDomElement symbolizerElem = doc.createElement( u"se:PolygonSymbolizer"_s );
4458 if ( !props.value( u"uom"_s, QString() ).toString().isEmpty() )
4459 symbolizerElem.setAttribute( u"uom"_s, props.value( u"uom"_s, QString() ).toString() );
4460 element.appendChild( symbolizerElem );
4461
4462 // <Geometry>
4463 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( u"geom"_s, QString() ).toString(), context );
4464
4465 QDomElement fillElem = doc.createElement( u"se:Fill"_s );
4466 symbolizerElem.appendChild( fillElem );
4467
4468 QDomElement graphicFillElem = doc.createElement( u"se:GraphicFill"_s );
4469 fillElem.appendChild( graphicFillElem );
4470
4471 QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( symbolLayerIdx );
4472
4473 // Export to PNG (TODO: SVG)
4474 bool exportOk { false };
4475 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
4476 {
4477 const QImage image { toTiledPatternImage( ) };
4478 if ( ! image.isNull() )
4479 {
4480 QDomElement graphicElem = doc.createElement( u"se:Graphic"_s );
4481 graphicFillElem.appendChild( graphicElem );
4482 const QFileInfo info { context.exportFilePath() };
4483 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( u"png"_s ) };
4484 pngPath = QgsFileUtils::uniquePath( pngPath );
4485 image.save( pngPath );
4486 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), u"image/png"_s, QColor(), image.height() );
4487 exportOk = true;
4488 }
4489 }
4490
4491 if ( ! exportOk )
4492 {
4493 // Converts to GeoServer "graphic-margin": symbol size must be subtracted from distance and then divided by 2
4494 const double markerSize { mMarkerSymbol->size() };
4495
4496 // store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
4499 // From: https://docs.geoserver.org/stable/en/user/styling/sld/extensions/margins.html
4500 // top-bottom,right-left (two values, top and bottom sharing the same value)
4501 const QString marginSpec = QString( "%1 %2" ).arg( qgsDoubleToString( ( dy - markerSize ) / 2, 2 ), qgsDoubleToString( ( dx - markerSize ) / 2, 2 ) );
4502
4503 QDomElement graphicMarginElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, u"graphic-margin"_s, marginSpec );
4504 symbolizerElem.appendChild( graphicMarginElem );
4505
4506 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
4507 {
4508 markerLayer->writeSldMarker( doc, graphicFillElem, context );
4509 }
4510 else if ( layer )
4511 {
4512 QgsDebugError( u"QgsMarkerSymbolLayer expected, %1 found. Skip it."_s.arg( layer->layerType() ) );
4513 }
4514 else
4515 {
4516 QgsDebugError( u"Missing point pattern symbol layer. Skip it."_s );
4517 }
4518 }
4519 }
4520 return true;
4521}
4522
4524{
4525
4526 double angleRads { qDegreesToRadians( mAngle ) };
4527
4528 int distanceXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, {} ) ) };
4529 int distanceYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, {} ) ) };
4530
4531 const int displacementXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementX, mDisplacementXUnit, {} ) ) };
4532 const int displacementYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementY, mDisplacementYUnit, {} ) ) };
4533
4534 // Consider displacement, double the distance.
4535 if ( displacementXPx != 0 )
4536 {
4537 distanceXPx *= 2;
4538 }
4539
4540 if ( displacementYPx != 0 )
4541 {
4542 distanceYPx *= 2;
4543 }
4544
4545 const QSize size { QgsSymbolLayerUtils::tileSize( distanceXPx, distanceYPx, angleRads ) };
4546
4547 QPixmap pixmap( size );
4548 pixmap.fill( Qt::transparent );
4549 QPainter painter;
4550 painter.begin( &pixmap );
4551 painter.setRenderHint( QPainter::Antialiasing );
4552 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
4557 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
4558
4559 std::unique_ptr< QgsPointPatternFillSymbolLayer > layerClone( clone() );
4560
4561 layerClone->setAngle( qRadiansToDegrees( angleRads ) );
4562
4563 // No way we can export a random pattern, disable it.
4564 layerClone->setMaximumRandomDeviationX( 0 );
4565 layerClone->setMaximumRandomDeviationY( 0 );
4566
4567 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
4568 painter.end();
4569 return pixmap.toImage();
4570}
4571
4573{
4574
4575 // input element is PolygonSymbolizer
4576
4577 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
4578 if ( fillElem.isNull() )
4579 return nullptr;
4580
4581 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
4582 if ( graphicFillElem.isNull() )
4583 return nullptr;
4584
4585 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
4586 if ( graphicElem.isNull() )
4587 return nullptr;
4588
4589 std::unique_ptr< QgsSymbolLayer > simpleMarkerSl = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicFillElem );
4590 if ( !simpleMarkerSl )
4591 return nullptr;
4592
4593 QgsSymbolLayerList layers;
4594 layers.append( simpleMarkerSl.release() );
4595
4596 auto marker = std::make_unique< QgsMarkerSymbol >( layers );
4597
4598 // Converts from GeoServer "graphic-margin": symbol size must be added and margin doubled
4599 const double markerSize { marker->size() };
4600
4601 auto pointPatternFillSl = std::make_unique< QgsPointPatternFillSymbolLayer >();
4602 pointPatternFillSl->setSubSymbol( marker.release() );
4603 // This may not be correct in all cases, TODO: check "uom"
4604 pointPatternFillSl->setDistanceXUnit( Qgis::RenderUnit::Pixels );
4605 pointPatternFillSl->setDistanceYUnit( Qgis::RenderUnit::Pixels );
4606
4607 auto distanceParser = [ & ]( const QStringList & values )
4608 {
4609 switch ( values.count( ) )
4610 {
4611 case 1: // top-right-bottom-left (single value for all four margins)
4612 {
4613 bool ok;
4614 const double v { values.at( 0 ).toDouble( &ok ) };
4615 if ( ok )
4616 {
4617 pointPatternFillSl->setDistanceX( v * 2 + markerSize );
4618 pointPatternFillSl->setDistanceY( v * 2 + markerSize );
4619 }
4620 break;
4621 }
4622 case 2: // top-bottom,right-left (two values, top and bottom sharing the same value)
4623 {
4624 bool ok;
4625 const double vX { values.at( 1 ).toDouble( &ok ) };
4626 if ( ok )
4627 {
4628 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4629 }
4630 const double vY { values.at( 0 ).toDouble( &ok ) };
4631 if ( ok )
4632 {
4633 pointPatternFillSl->setDistanceY( vY * 2 + markerSize );
4634 }
4635 break;
4636 }
4637 case 3: // top,right-left,bottom (three values, with right and left sharing the same value)
4638 {
4639 bool ok;
4640 const double vX { values.at( 1 ).toDouble( &ok ) };
4641 if ( ok )
4642 {
4643 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4644 }
4645 const double vYt { values.at( 0 ).toDouble( &ok ) };
4646 if ( ok )
4647 {
4648 const double vYb { values.at( 2 ).toDouble( &ok ) };
4649 if ( ok )
4650 {
4651 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4652 }
4653 }
4654 break;
4655 }
4656 case 4: // top,right,bottom,left (one explicit value per margin)
4657 {
4658 bool ok;
4659 const double vYt { values.at( 0 ).toDouble( &ok ) };
4660 if ( ok )
4661 {
4662 const double vYb { values.at( 2 ).toDouble( &ok ) };
4663 if ( ok )
4664 {
4665 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4666 }
4667 }
4668 const double vXr { values.at( 1 ).toDouble( &ok ) };
4669 if ( ok )
4670 {
4671 const double vXl { values.at( 3 ).toDouble( &ok ) };
4672 if ( ok )
4673 {
4674 pointPatternFillSl->setDistanceX( ( vXr + vXl ) + markerSize );
4675 }
4676 }
4677 break;
4678 }
4679 default:
4680 break;
4681 }
4682 };
4683
4684 // Set distance X and Y from vendor options, or from Size if no vendor options are set
4685 bool distanceFromVendorOption { false };
4686 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
4687 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
4688 {
4689 // Legacy
4690 if ( it.key() == "distance"_L1 )
4691 {
4692 distanceParser( it.value().split( ',' ) );
4693 distanceFromVendorOption = true;
4694 }
4695 // GeoServer
4696 else if ( it.key() == "graphic-margin"_L1 )
4697 {
4698 distanceParser( it.value().split( ' ' ) );
4699 distanceFromVendorOption = true;
4700 }
4701 }
4702
4703 // Get distances from size
4704 if ( ! distanceFromVendorOption && ! graphicFillElem.elementsByTagName( u"Size"_s ).isEmpty() )
4705 {
4706 const QDomElement sizeElement { graphicFillElem.elementsByTagName( u"Size"_s ).at( 0 ).toElement() };
4707 bool ok;
4708 const double size { sizeElement.text().toDouble( &ok ) };
4709 if ( ok )
4710 {
4711 pointPatternFillSl->setDistanceX( size );
4712 pointPatternFillSl->setDistanceY( size );
4713 }
4714 }
4715
4716 return pointPatternFillSl.release();
4717}
4718
4720{
4721 if ( !symbol )
4722 {
4723 return false;
4724 }
4725
4726 if ( symbol->type() == Qgis::SymbolType::Marker )
4727 {
4728 QgsMarkerSymbol *markerSymbol = static_cast<QgsMarkerSymbol *>( symbol );
4729 mMarkerSymbol.reset( markerSymbol );
4730 }
4731 return true;
4732}
4733
4738
4740{
4744 && ( !mMarkerSymbol || !mMarkerSymbol->hasDataDefinedProperties() ) )
4745 {
4746 return;
4747 }
4748
4749 double distanceX = mDistanceX;
4751 {
4754 }
4755 double distanceY = mDistanceY;
4757 {
4760 }
4763 {
4766 }
4769 {
4772 }
4773 double offsetX = mOffsetX;
4775 {
4778 }
4779 double offsetY = mOffsetY;
4781 {
4784 }
4785 applyPattern( context, mBrush, distanceX, distanceY, displacementX, displacementY, offsetX, offsetY );
4786}
4787
4789{
4790 return 0;
4791}
4792
4794{
4795 QSet<QString> attributes = QgsImageFillSymbolLayer::usedAttributes( context );
4796
4797 if ( mMarkerSymbol )
4798 attributes.unite( mMarkerSymbol->usedAttributes( context ) );
4799
4800 return attributes;
4801}
4802
4804{
4806 return true;
4807 if ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
4808 return true;
4809 return false;
4810}
4811
4813{
4814 mColor = c;
4815 if ( mMarkerSymbol )
4816 mMarkerSymbol->setColor( c );
4817}
4818
4820{
4821 return mMarkerSymbol ? mMarkerSymbol->color() : mColor;
4822}
4823
4825
4826
4831
4833
4835{
4836 auto sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4837
4838 if ( properties.contains( u"point_on_surface"_s ) )
4839 sl->setPointOnSurface( properties[u"point_on_surface"_s].toInt() != 0 );
4840 if ( properties.contains( u"point_on_all_parts"_s ) )
4841 sl->setPointOnAllParts( properties[u"point_on_all_parts"_s].toInt() != 0 );
4842 if ( properties.contains( u"clip_points"_s ) )
4843 sl->setClipPoints( properties[u"clip_points"_s].toInt() != 0 );
4844 if ( properties.contains( u"clip_on_current_part_only"_s ) )
4845 sl->setClipOnCurrentPartOnly( properties[u"clip_on_current_part_only"_s].toInt() != 0 );
4846
4847 sl->restoreOldDataDefinedProperties( properties );
4848
4849 return sl.release();
4850}
4851
4853{
4854 return u"CentroidFill"_s;
4855}
4856
4858{
4859 mMarker->setColor( color );
4860 mColor = color;
4861}
4862
4864{
4865 return mMarker ? mMarker->color() : mColor;
4866}
4867
4869{
4870 mMarker->setRenderHints( mMarker->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
4871 mMarker->startRender( context.renderContext(), context.fields() );
4872}
4873
4875{
4876 mMarker->stopRender( context.renderContext() );
4877}
4878
4879void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4880{
4881 Part part;
4882 part.exterior = points;
4883 if ( rings )
4884 part.rings = *rings;
4885
4886 if ( mRenderingFeature )
4887 {
4888 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
4889 // until after we've received the final part
4890 mFeatureSymbolOpacity = context.opacity();
4892 mCurrentParts << part;
4893 }
4894 else
4895 {
4896 // not rendering a feature, so we can just render the polygon immediately
4897 const double prevOpacity = mMarker->opacity();
4898 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
4899 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4900 render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
4901 mMarker->setOpacity( prevOpacity );
4902 }
4903}
4904
4906{
4907 installMasks( context, true );
4908
4909 mRenderingFeature = true;
4910 mCurrentParts.clear();
4911}
4912
4914{
4915 mRenderingFeature = false;
4916
4917 const double prevOpacity = mMarker->opacity();
4918 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
4919
4920 render( context, mCurrentParts, feature, mUseSelectedColor );
4922 mUseSelectedColor = false;
4923 mMarker->setOpacity( prevOpacity );
4924
4925 removeMasks( context, true );
4926}
4927
4928void QgsCentroidFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsCentroidFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
4929{
4932 bool clipPoints = mClipPoints;
4934
4935 // TODO add expressions support
4936
4937 QVector< QgsGeometry > geometryParts;
4938 geometryParts.reserve( parts.size() );
4939 QPainterPath globalPath;
4940
4941 int maxArea = 0;
4942 int maxAreaPartIdx = 0;
4943
4944 for ( int i = 0; i < parts.size(); i++ )
4945 {
4946 const Part part = parts[i];
4947 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
4948
4949 if ( !geom.isNull() && !part.rings.empty() )
4950 {
4952
4953 if ( !pointOnAllParts )
4954 {
4955 int area = poly->area();
4956
4957 if ( area > maxArea )
4958 {
4959 maxArea = area;
4960 maxAreaPartIdx = i;
4961 }
4962 }
4963 }
4964
4966 {
4967 globalPath.addPolygon( part.exterior );
4968 for ( const QPolygonF &ring : part.rings )
4969 {
4970 globalPath.addPolygon( ring );
4971 }
4972 }
4973 }
4974
4975 for ( int i = 0; i < parts.size(); i++ )
4976 {
4977 if ( !pointOnAllParts && i != maxAreaPartIdx )
4978 continue;
4979
4980 const Part part = parts[i];
4981
4982 if ( clipPoints )
4983 {
4984 QPainterPath path;
4985
4987 {
4988 path.addPolygon( part.exterior );
4989 for ( const QPolygonF &ring : part.rings )
4990 {
4991 path.addPolygon( ring );
4992 }
4993 }
4994 else
4995 {
4996 path = globalPath;
4997 }
4998
4999 context.painter()->save();
5000 context.painter()->setClipPath( path );
5001 }
5002
5003 QPointF centroid = pointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( part.exterior, &part.rings ) : QgsSymbolLayerUtils::polygonCentroid( part.exterior );
5004
5005 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
5007 mMarker->renderPoint( centroid, feature.isValid() ? &feature : nullptr, context, -1, selected );
5008 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
5009
5010 if ( clipPoints )
5011 {
5012 context.painter()->restore();
5013 }
5014 }
5015}
5016
5018{
5019 QVariantMap map;
5020 map[u"point_on_surface"_s] = QString::number( mPointOnSurface );
5021 map[u"point_on_all_parts"_s] = QString::number( mPointOnAllParts );
5022 map[u"clip_points"_s] = QString::number( mClipPoints );
5023 map[u"clip_on_current_part_only"_s] = QString::number( mClipOnCurrentPartOnly );
5024 return map;
5025}
5026
5028{
5029 auto x = std::make_unique< QgsCentroidFillSymbolLayer >();
5030 x->mAngle = mAngle;
5031 x->mColor = mColor;
5032 x->setSubSymbol( mMarker->clone() );
5033 x->setPointOnSurface( mPointOnSurface );
5034 x->setPointOnAllParts( mPointOnAllParts );
5035 x->setClipPoints( mClipPoints );
5036 x->setClipOnCurrentPartOnly( mClipOnCurrentPartOnly );
5037 copyDataDefinedProperties( x.get() );
5038 copyPaintEffect( x.get() );
5039 return x.release();
5040}
5041
5042void QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
5043{
5044 QgsSldExportContext context;
5045 context.setExtraProperties( props );
5046 toSld( doc, element, context );
5047}
5048
5049bool QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
5050{
5051 // SLD 1.0 specs says: "if a line, polygon, or raster geometry is
5052 // used with PointSymbolizer, then the semantic is to use the centroid
5053 // of the geometry, or any similar representative point.
5054 return mMarker->toSld( doc, element, context );
5055}
5056
5058{
5059 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createMarkerLayerFromSld( element );
5060 if ( !l )
5061 return nullptr;
5062
5063 QgsSymbolLayerList layers;
5064 layers.append( l.release() );
5065 auto marker = std::make_unique<QgsMarkerSymbol>( layers );
5066
5067 auto sl = std::make_unique< QgsCentroidFillSymbolLayer >();
5068 sl->setSubSymbol( marker.release() );
5069 sl->setPointOnAllParts( false );
5070 return sl.release();
5071}
5072
5073
5078
5080{
5081 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5082 {
5083 delete symbol;
5084 return false;
5085 }
5086
5087 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5088 mColor = mMarker->color();
5089 return true;
5090}
5091
5093{
5094 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5095
5096 if ( mMarker )
5097 attributes.unite( mMarker->usedAttributes( context ) );
5098
5099 return attributes;
5100}
5101
5103{
5105 return true;
5106 if ( mMarker && mMarker->hasDataDefinedProperties() )
5107 return true;
5108 return false;
5109}
5110
5115
5117{
5118 if ( mMarker )
5119 {
5120 mMarker->setOutputUnit( unit );
5121 }
5122}
5123
5125{
5126 if ( mMarker )
5127 {
5128 return mMarker->outputUnit();
5129 }
5130 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5131}
5132
5134{
5135 if ( mMarker )
5136 {
5137 return mMarker->usesMapUnits();
5138 }
5139 return false;
5140}
5141
5143{
5144 if ( mMarker )
5145 {
5146 mMarker->setMapUnitScale( scale );
5147 }
5148}
5149
5151{
5152 if ( mMarker )
5153 {
5154 return mMarker->mapUnitScale();
5155 }
5156 return QgsMapUnitScale();
5157}
5158
5159
5160
5161
5169
5171
5173{
5175 double alpha = 1.0;
5176 QPointF offset;
5177 double angle = 0.0;
5178 double width = 0.0;
5179
5180 QString imagePath;
5181 if ( properties.contains( u"imageFile"_s ) )
5182 {
5183 imagePath = properties[u"imageFile"_s].toString();
5184 }
5185 if ( properties.contains( u"coordinate_mode"_s ) )
5186 {
5187 mode = static_cast< Qgis::SymbolCoordinateReference >( properties[u"coordinate_mode"_s].toInt() );
5188 }
5189 if ( properties.contains( u"alpha"_s ) )
5190 {
5191 alpha = properties[u"alpha"_s].toDouble();
5192 }
5193 if ( properties.contains( u"offset"_s ) )
5194 {
5195 offset = QgsSymbolLayerUtils::decodePoint( properties[u"offset"_s].toString() );
5196 }
5197 if ( properties.contains( u"angle"_s ) )
5198 {
5199 angle = properties[u"angle"_s].toDouble();
5200 }
5201 if ( properties.contains( u"width"_s ) )
5202 {
5203 width = properties[u"width"_s].toDouble();
5204 }
5205 auto symbolLayer = std::make_unique< QgsRasterFillSymbolLayer >( imagePath );
5206 symbolLayer->setCoordinateMode( mode );
5207 symbolLayer->setOpacity( alpha );
5208 symbolLayer->setOffset( offset );
5209 symbolLayer->setAngle( angle );
5210 symbolLayer->setWidth( width );
5211 if ( properties.contains( u"offset_unit"_s ) )
5212 {
5213 symbolLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[u"offset_unit"_s].toString() ) );
5214 }
5215 if ( properties.contains( u"offset_map_unit_scale"_s ) )
5216 {
5217 symbolLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"offset_map_unit_scale"_s].toString() ) );
5218 }
5219 if ( properties.contains( u"width_unit"_s ) )
5220 {
5221 symbolLayer->setSizeUnit( QgsUnitTypes::decodeRenderUnit( properties[u"width_unit"_s].toString() ) );
5222 }
5223 if ( properties.contains( u"width_map_unit_scale"_s ) )
5224 {
5225 symbolLayer->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"width_map_unit_scale"_s].toString() ) );
5226 }
5227
5228 if ( properties.contains( u"height"_s ) )
5229 {
5230 symbolLayer->setHeight( properties[u"height"_s].toDouble() );
5231 }
5232
5233 symbolLayer->restoreOldDataDefinedProperties( properties );
5234
5235 return symbolLayer.release();
5236}
5237
5239{
5240 QDomElement fillElem = element.firstChildElement( u"Fill"_s );
5241 if ( fillElem.isNull() )
5242 return nullptr;
5243
5244 QDomElement graphicFillElem = fillElem.firstChildElement( u"GraphicFill"_s );
5245 if ( graphicFillElem.isNull() )
5246 return nullptr;
5247
5248 QDomElement graphicElem = graphicFillElem.firstChildElement( u"Graphic"_s );
5249 if ( graphicElem.isNull() )
5250 return nullptr;
5251
5252 QString path, mimeType;
5253 double size;
5254 QColor fillColor;
5255
5256 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
5257 return nullptr;
5258
5259 // Try to correct the path, this is a wild guess but we have not access to the SLD path here.
5260 if ( ! QFile::exists( path ) )
5261 {
5262 path = QgsProject::instance()->pathResolver().readPath( path ); // skip-keyword-check
5263 }
5264
5265 auto sl = std::make_unique< QgsRasterFillSymbolLayer>( path );
5266
5267 return sl.release();
5268}
5269
5270void QgsRasterFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
5271{
5272 QVariantMap::iterator it = properties.find( u"imageFile"_s );
5273 if ( it != properties.end() )
5274 {
5275 if ( saving )
5276 it.value() = pathResolver.writePath( it.value().toString() );
5277 else
5278 it.value() = pathResolver.readPath( it.value().toString() );
5279 }
5280}
5281
5283{
5284 Q_UNUSED( symbol )
5285 return true;
5286}
5287
5289{
5290 return u"RasterFill"_s;
5291}
5292
5297
5298void QgsRasterFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5299{
5300 QPainter *p = context.renderContext().painter();
5301 if ( !p )
5302 {
5303 return;
5304 }
5305
5306 QPointF offset = mOffset;
5308 {
5310 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::Property::Offset, context.renderContext().expressionContext(), QString() );
5311 bool ok = false;
5312 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
5313 if ( ok )
5314 offset = res;
5315 }
5316 if ( !offset.isNull() )
5317 {
5318 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
5319 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
5320 p->translate( offset );
5321 }
5322 if ( mCoordinateMode == Qgis::SymbolCoordinateReference::Feature )
5323 {
5324 QRectF boundingRect = points.boundingRect();
5325 mBrush.setTransform( mBrush.transform().translate( boundingRect.left() - mBrush.transform().dx(),
5326 boundingRect.top() - mBrush.transform().dy() ) );
5327 }
5328
5329 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
5330 if ( !offset.isNull() )
5331 {
5332 p->translate( -offset );
5333 }
5334}
5335
5337{
5338 applyPattern( mBrush, mImageFilePath, mWidth, mHeight, mOpacity * context.opacity(), context );
5339}
5340
5342{
5343 Q_UNUSED( context )
5344}
5345
5347{
5348 QVariantMap map;
5349 map[u"imageFile"_s] = mImageFilePath;
5350 map[u"coordinate_mode"_s] = QString::number( static_cast< int >( mCoordinateMode ) );
5351 map[u"alpha"_s] = QString::number( mOpacity );
5352 map[u"offset"_s] = QgsSymbolLayerUtils::encodePoint( mOffset );
5353 map[u"offset_unit"_s] = QgsUnitTypes::encodeUnit( mOffsetUnit );
5354 map[u"offset_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
5355 map[u"angle"_s] = QString::number( mAngle );
5356
5357 map[u"width"_s] = QString::number( mWidth );
5358 map[u"height"_s] = QString::number( mHeight );
5359 map[u"width_unit"_s] = QgsUnitTypes::encodeUnit( mSizeUnit );
5360 map[u"width_map_unit_scale"_s] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
5361
5362 return map;
5363}
5364
5366{
5367 auto sl = std::make_unique< QgsRasterFillSymbolLayer >( mImageFilePath );
5368 sl->setCoordinateMode( mCoordinateMode );
5369 sl->setOpacity( mOpacity );
5370 sl->setOffset( mOffset );
5371 sl->setOffsetUnit( mOffsetUnit );
5372 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
5373 sl->setAngle( mAngle );
5374 sl->setWidth( mWidth );
5375 sl->setHeight( mHeight );
5376 sl->setSizeUnit( mSizeUnit );
5377 sl->setSizeMapUnitScale( mSizeMapUnitScale );
5378
5379 copyDataDefinedProperties( sl.get() );
5380 copyPaintEffect( sl.get() );
5381 return sl.release();
5382}
5383
5385{
5386 return context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
5387}
5388
5390{
5391 return mSizeUnit == Qgis::RenderUnit::MapUnits || mSizeUnit == Qgis::RenderUnit::MetersInMapUnits
5392 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
5393}
5394
5396{
5397 return QColor();
5398}
5399
5401{
5403 mOffsetUnit = unit;
5404 mSizeUnit = unit;
5405}
5406
5407void QgsRasterFillSymbolLayer::setImageFilePath( const QString &imagePath )
5408{
5409 mImageFilePath = imagePath;
5410}
5411
5413{
5414 mCoordinateMode = mode;
5415}
5416
5418{
5419 mOpacity = opacity;
5420}
5421
5423{
5424 if ( !dataDefinedProperties().hasActiveProperties() )
5425 return; // shortcut
5426
5427 const bool hasWidthExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Width );
5428 const bool hasHeightExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Height );
5429 const bool hasFileExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::File );
5430 const bool hasOpacityExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Opacity );
5431 const bool hasAngleExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Angle );
5432
5433 if ( !hasWidthExpression && !hasHeightExpression && !hasAngleExpression && !hasOpacityExpression && !hasFileExpression )
5434 {
5435 return; //no data defined settings
5436 }
5437
5438 bool ok;
5439 if ( hasAngleExpression )
5440 {
5442 double nextAngle = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::Angle, context.renderContext().expressionContext(), 0, &ok );
5443 if ( ok )
5444 mNextAngle = nextAngle;
5445 }
5446
5447 if ( !hasWidthExpression && !hasHeightExpression && !hasOpacityExpression && !hasFileExpression )
5448 {
5449 return; //nothing further to do
5450 }
5451
5452 double width = mWidth;
5453 if ( hasWidthExpression )
5454 {
5455 context.setOriginalValueVariable( mWidth );
5457 }
5458 double height = mHeight;
5459 if ( hasHeightExpression )
5460 {
5461 context.setOriginalValueVariable( mHeight );
5463 }
5464 double opacity = mOpacity;
5465 if ( hasOpacityExpression )
5466 {
5467 context.setOriginalValueVariable( mOpacity );
5469 }
5470 QString file = mImageFilePath;
5471 if ( hasFileExpression )
5472 {
5473 context.setOriginalValueVariable( mImageFilePath );
5475 }
5476 applyPattern( mBrush, file, width, height, opacity, context );
5477}
5478
5483
5484void QgsRasterFillSymbolLayer::applyPattern( QBrush &brush, const QString &imageFilePath, const double width, const double height, const double alpha, const QgsSymbolRenderContext &context )
5485{
5486 double imageWidth = 0;
5487 double imageHeight = 0;
5488
5489 // defer retrieval of original size till we actually NEED it
5490 QSize originalSize;
5491
5492 if ( width > 0 )
5493 {
5494 if ( mSizeUnit != Qgis::RenderUnit::Percentage )
5495 {
5496 imageWidth = context.renderContext().convertToPainterUnits( width, mSizeUnit, mSizeMapUnitScale );
5497 }
5498 else
5499 {
5500 // RenderPercentage Unit Type takes original image size
5502 if ( originalSize.isEmpty() )
5503 return;
5504
5505 imageWidth = ( width * originalSize.width() ) / 100.0;
5506
5507 // don't render symbols with size below one or above 10,000 pixels
5508 if ( static_cast< int >( imageWidth ) < 1 || 10000.0 < imageWidth )
5509 return;
5510 }
5511 }
5512 if ( height > 0 )
5513 {
5514 if ( mSizeUnit != Qgis::RenderUnit::Percentage )
5515 {
5516 imageHeight = context.renderContext().convertToPainterUnits( height, mSizeUnit, mSizeMapUnitScale );
5517 }
5518 else
5519 {
5520 // RenderPercentage Unit Type takes original image size
5521 if ( !originalSize.isValid() )
5523
5524 if ( originalSize.isEmpty() )
5525 return;
5526
5527 imageHeight = ( height * originalSize.height() ) / 100.0;
5528
5529 // don't render symbols with size below one or above 10,000 pixels
5530 if ( static_cast< int >( imageHeight ) < 1 || 10000.0 < imageHeight )
5531 return;
5532 }
5533 }
5534
5535 if ( width == 0 && imageHeight > 0 )
5536 {
5537 if ( !originalSize.isValid() )
5539
5540 imageWidth = imageHeight * originalSize.width() / originalSize.height();
5541 }
5542 else if ( height == 0 && imageWidth > 0 )
5543 {
5544 if ( !originalSize.isValid() )
5546
5547 imageHeight = imageWidth * originalSize.height() / originalSize.width();
5548 }
5549 if ( imageWidth == 0 || imageHeight == 0 )
5550 {
5551 if ( !originalSize.isValid() )
5553
5554 imageWidth = originalSize.width();
5555 imageHeight = originalSize.height();
5556 }
5557
5558 bool cached;
5559 QImage img = QgsApplication::imageCache()->pathAsImage( imageFilePath, QSize( std::round< int >( imageWidth ), std::round< int >( imageHeight ) ), false, alpha, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
5560 if ( img.isNull() )
5561 return;
5562
5563 brush.setTextureImage( img );
5564}
5565
5566
5567//
5568// QgsRandomMarkerFillSymbolLayer
5569//
5570
5572 : mCountMethod( method )
5573 , mPointCount( pointCount )
5574 , mDensityArea( densityArea )
5575 , mSeed( seed )
5576{
5578}
5579
5581
5583{
5584 const Qgis::PointCountMethod countMethod = static_cast< Qgis::PointCountMethod >( properties.value( u"count_method"_s, u"0"_s ).toInt() );
5585 const int pointCount = properties.value( u"point_count"_s, u"10"_s ).toInt();
5586 const double densityArea = properties.value( u"density_area"_s, u"250.0"_s ).toDouble();
5587
5588 unsigned long seed = 0;
5589 if ( properties.contains( u"seed"_s ) )
5590 seed = properties.value( u"seed"_s ).toUInt();
5591 else
5592 {
5593 // if we a creating a new random marker fill from scratch, we default to a random seed
5594 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
5595 std::random_device rd;
5596 std::mt19937 mt( static_cast< int >( rd() ) );
5597 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
5598 seed = uniformDist( mt );
5599 }
5600
5601 auto sl = std::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, countMethod, densityArea, seed );
5602
5603 if ( properties.contains( u"density_area_unit"_s ) )
5604 sl->setDensityAreaUnit( QgsUnitTypes::decodeRenderUnit( properties[u"density_area_unit"_s].toString() ) );
5605 if ( properties.contains( u"density_area_unit_scale"_s ) )
5606 sl->setDensityAreaUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[u"density_area_unit_scale"_s].toString() ) );
5607
5608 if ( properties.contains( u"clip_points"_s ) )
5609 {
5610 sl->setClipPoints( properties[u"clip_points"_s].toInt() );
5611 }
5612
5613 return sl.release();
5614}
5615
5617{
5618 return u"RandomMarkerFill"_s;
5619}
5620
5622{
5623 mMarker->setColor( color );
5624 mColor = color;
5625}
5626
5628{
5629 return mMarker ? mMarker->color() : mColor;
5630}
5631
5633{
5634 mMarker->setRenderHints( mMarker->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
5635 mMarker->startRender( context.renderContext(), context.fields() );
5636}
5637
5639{
5640 mMarker->stopRender( context.renderContext() );
5641}
5642
5643void QgsRandomMarkerFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5644{
5645 Part part;
5646 part.exterior = points;
5647 if ( rings )
5648 part.rings = *rings;
5649
5650 if ( mRenderingFeature )
5651 {
5652 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
5653 // until after we've received the final part
5654 mFeatureSymbolOpacity = context.opacity();
5655 mCurrentParts << part;
5656 }
5657 else
5658 {
5659 // not rendering a feature, so we can just render the polygon immediately
5660 const double prevOpacity = mMarker->opacity();
5661 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
5662 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
5663 render( context.renderContext(), QVector< Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
5664 mMarker->setOpacity( prevOpacity );
5665 }
5666}
5667
5668void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsRandomMarkerFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
5669{
5670 bool clipPoints = mClipPoints;
5672 {
5675 }
5676
5677 QVector< QgsGeometry > geometryParts;
5678 geometryParts.reserve( parts.size() );
5679 QPainterPath path;
5680
5681 for ( const Part &part : parts )
5682 {
5683 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
5684 if ( !geom.isNull() && !part.rings.empty() )
5685 {
5686 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
5687 for ( const QPolygonF &ring : part.rings )
5688 {
5689 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
5690 poly->addInteriorRing( fromRing.release() );
5691 }
5692 }
5693 if ( !geom.isGeosValid() )
5694 {
5695 geom = geom.buffer( 0, 0 );
5696 }
5697 geometryParts << geom;
5698
5699 if ( clipPoints )
5700 {
5701 path.addPolygon( part.exterior );
5702 for ( const QPolygonF &ring : part.rings )
5703 {
5704 path.addPolygon( ring );
5705 }
5706 }
5707 }
5708
5709 const QgsGeometry geom = geometryParts.count() != 1 ? QgsGeometry::unaryUnion( geometryParts ) : geometryParts.at( 0 );
5710
5711 if ( clipPoints )
5712 {
5713 context.painter()->save();
5714 context.painter()->setClipPath( path );
5715 }
5716
5717
5718 int count = mPointCount;
5720 {
5721 context.expressionContext().setOriginalValueVariable( count );
5722 count = mDataDefinedProperties.valueAsInt( QgsSymbolLayer::Property::PointCount, context.expressionContext(), count );
5723 }
5724
5725 switch ( mCountMethod )
5726 {
5728 {
5729 double densityArea = mDensityArea;
5731 {
5734 }
5735 densityArea = context.convertToPainterUnits( std::sqrt( densityArea ), mDensityAreaUnit, mDensityAreaUnitScale );
5736 densityArea = std::pow( densityArea, 2 );
5737 count = std::max( 0.0, std::ceil( count * ( geom.area() / densityArea ) ) );
5738 break;
5739 }
5741 break;
5742 }
5743
5744 unsigned long seed = mSeed;
5746 {
5747 context.expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
5748 seed = mDataDefinedProperties.valueAsInt( QgsSymbolLayer::Property::RandomSeed, context.expressionContext(), seed );
5749 }
5750
5751 QVector< QgsPointXY > randomPoints = geom.randomPointsInPolygon( count, seed );
5752#if 0
5753 // in some cases rendering from top to bottom is nice (e.g. randomised tree markers), but in other cases it's not wanted..
5754 // TODO consider exposing this as an option
5755 std::sort( randomPoints.begin(), randomPoints.end(), []( const QgsPointXY & a, const QgsPointXY & b )->bool
5756 {
5757 return a.y() < b.y();
5758 } );
5759#endif
5760 QgsExpressionContextScope *scope = new QgsExpressionContextScope();
5761 QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scope );
5762 int pointNum = 0;
5763 const bool needsExpressionContext = mMarker->hasDataDefinedProperties();
5764
5765 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
5767
5768 for ( const QgsPointXY &p : std::as_const( randomPoints ) )
5769 {
5770 if ( needsExpressionContext )
5771 scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
5772 mMarker->renderPoint( QPointF( p.x(), p.y() ), feature.isValid() ? &feature : nullptr, context, -1, selected );
5773 }
5774
5775 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
5776
5777 if ( clipPoints )
5778 {
5779 context.painter()->restore();
5780 }
5781}
5782
5784{
5785 QVariantMap map;
5786 map.insert( u"count_method"_s, QString::number( static_cast< int >( mCountMethod ) ) );
5787 map.insert( u"point_count"_s, QString::number( mPointCount ) );
5788 map.insert( u"density_area"_s, QString::number( mDensityArea ) );
5789 map.insert( u"density_area_unit"_s, QgsUnitTypes::encodeUnit( mDensityAreaUnit ) );
5790 map.insert( u"density_area_unit_scale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mDensityAreaUnitScale ) );
5791 map.insert( u"seed"_s, QString::number( mSeed ) );
5792 map.insert( u"clip_points"_s, QString::number( mClipPoints ) );
5793 return map;
5794}
5795
5797{
5798 auto res = std::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mCountMethod, mDensityArea, mSeed );
5799 res->mAngle = mAngle;
5800 res->mColor = mColor;
5801 res->setDensityAreaUnit( mDensityAreaUnit );
5802 res->setDensityAreaUnitScale( mDensityAreaUnitScale );
5803 res->mClipPoints = mClipPoints;
5804 res->setSubSymbol( mMarker->clone() );
5805 copyDataDefinedProperties( res.get() );
5806 copyPaintEffect( res.get() );
5807 return res.release();
5808}
5809
5814
5816{
5818 return false;
5819
5820 // special case -- two QgsRandomMarkerFillSymbolLayer will render differently if they have
5821 // no seed value set.
5822 if ( mSeed == 0 )
5823 return false;
5824
5825 return true;
5826}
5827
5829{
5830 return mMarker.get();
5831}
5832
5834{
5835 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5836 {
5837 delete symbol;
5838 return false;
5839 }
5840
5841 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5842 mColor = mMarker->color();
5843 return true;
5844}
5845
5847{
5848 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5849
5850 if ( mMarker )
5851 attributes.unite( mMarker->usedAttributes( context ) );
5852
5853 return attributes;
5854}
5855
5857{
5859 return true;
5860 if ( mMarker && mMarker->hasDataDefinedProperties() )
5861 return true;
5862 return false;
5863}
5864
5866{
5867 return mPointCount;
5868}
5869
5871{
5872 mPointCount = pointCount;
5873}
5874
5876{
5877 return mSeed;
5878}
5879
5881{
5882 mSeed = seed;
5883}
5884
5886{
5887 return mClipPoints;
5888}
5889
5891{
5892 mClipPoints = clipPoints;
5893}
5894
5896{
5897 return mCountMethod;
5898}
5899
5901{
5902 mCountMethod = method;
5903}
5904
5906{
5907 return mDensityArea;
5908}
5909
5911{
5912 mDensityArea = area;
5913}
5914
5916{
5917 installMasks( context, true );
5918
5919 mRenderingFeature = true;
5920 mCurrentParts.clear();
5921}
5922
5924{
5925 mRenderingFeature = false;
5926
5927 const double prevOpacity = mMarker->opacity();
5928 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
5929
5930 render( context, mCurrentParts, feature, false );
5931
5932 mFeatureSymbolOpacity = 1;
5933 mMarker->setOpacity( prevOpacity );
5934
5935 removeMasks( context, true );
5936}
5937
5938
5940{
5941 mDensityAreaUnit = unit;
5942 if ( mMarker )
5943 {
5944 mMarker->setOutputUnit( unit );
5945 }
5946}
5947
5949{
5950 if ( mMarker )
5951 {
5952 return mMarker->outputUnit();
5953 }
5954 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5955}
5956
5958{
5959 if ( mMarker )
5960 {
5961 return mMarker->usesMapUnits();
5962 }
5963 return false;
5964}
5965
5967{
5968 if ( mMarker )
5969 {
5970 mMarker->setMapUnitScale( scale );
5971 }
5972}
5973
5975{
5976 if ( mMarker )
5977 {
5978 return mMarker->mapUnitScale();
5979 }
5980 return QgsMapUnitScale();
5981}
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2763
MarkerClipMode
Marker clipping modes.
Definition qgis.h:3308
@ CompletelyWithin
Render complete markers wherever the completely fall within the polygon shape.
Definition qgis.h:3312
@ NoClipping
No clipping, render complete markers.
Definition qgis.h:3309
@ Shape
Clip to polygon shape.
Definition qgis.h:3310
@ CentroidWithin
Render complete markers wherever their centroid falls within the polygon shape.
Definition qgis.h:3311
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
Definition qgis.h:790
LineClipMode
Line clipping modes.
Definition qgis.h:3322
@ NoClipping
Lines are not clipped, will extend to shape's bounding box.
Definition qgis.h:3325
@ ClipPainterOnly
Applying clipping on the painter only (i.e. line endpoints will coincide with polygon bounding box,...
Definition qgis.h:3323
@ ClipToIntersection
Clip lines to intersection with polygon shape (slower) (i.e. line endpoints will coincide with polygo...
Definition qgis.h:3324
GradientColorSource
Gradient color sources.
Definition qgis.h:3237
@ ColorRamp
Gradient color ramp.
Definition qgis.h:3239
@ SimpleTwoColor
Simple two color gradient.
Definition qgis.h:3238
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
Definition qgis.h:901
GradientSpread
Gradient spread options, which control how gradients are rendered outside of their start and end poin...
Definition qgis.h:3281
@ Repeat
Repeat gradient.
Definition qgis.h:3284
@ Reflect
Reflect gradient.
Definition qgis.h:3283
@ Pad
Pad out gradient using colors at endpoint of gradient.
Definition qgis.h:3282
@ Png
Export complex styles to separate PNG files for better compatibility with OGC servers.
Definition qgis.h:718
PointCountMethod
Methods which define the number of points randomly filling a polygon.
Definition qgis.h:3296
@ Absolute
The point count is used as an absolute count of markers.
Definition qgis.h:3297
@ DensityBased
The point count is part of a marker density count.
Definition qgis.h:3298
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:906
RenderUnit
Rendering size units.
Definition qgis.h:5255
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5259
@ Unknown
Mixed or unknown units.
Definition qgis.h:5262
@ MapUnits
Map units.
Definition qgis.h:5257
@ Pixels
Pixels.
Definition qgis.h:5258
@ MetersInMapUnits
Meters value as Map units.
Definition qgis.h:5263
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
Definition qgis.h:2823
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
Definition qgis.h:2818
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
Definition qgis.h:2813
@ RenderLayerTree
The render is for a layer tree display where map based properties are not available and where avoidan...
Definition qgis.h:2829
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
Definition qgis.h:2824
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
Definition qgis.h:2817
GradientType
Gradient types.
Definition qgis.h:3251
@ Linear
Linear gradient.
Definition qgis.h:3252
@ Conical
Conical (polar) gradient.
Definition qgis.h:3254
@ Radial
Radial (circular) gradient.
Definition qgis.h:3253
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:796
@ Marker
Marker symbol.
Definition qgis.h:630
@ Line
Line symbol.
Definition qgis.h:631
SymbolCoordinateReference
Symbol coordinate reference modes.
Definition qgis.h:3266
@ Feature
Relative to feature/shape being rendered.
Definition qgis.h:3267
@ Viewport
Relative to the whole viewport/output device.
Definition qgis.h:3268
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())
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)
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.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol 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.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another 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:6817
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
QMap< QString, QString > QgsStringMap
Definition qgis.h:7413
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.