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