QGIS API Documentation 3.34.0-Prizren (ffbdd678812)
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 "qgsfileutils.h"
17#include "qgsfillsymbollayer.h"
18#include "qgslinesymbollayer.h"
19#include "qgssldexportcontext.h"
20#include "qgssymbollayerutils.h"
21#include "qgsdxfexport.h"
22#include "qgsgeometry.h"
23#include "qgsimagecache.h"
24#include "qgsrendercontext.h"
25#include "qgsproject.h"
26#include "qgssvgcache.h"
27#include "qgscolorramp.h"
28#include "qgscolorrampimpl.h"
29#include "qgsunittypes.h"
30#include "qgsmessagelog.h"
31#include "qgsapplication.h"
32#include "qgsimageoperation.h"
33#include "qgspolygon.h"
34#include "qgslinestring.h"
36#include "qgssymbol.h"
37#include "qgsmarkersymbol.h"
38#include "qgslinesymbol.h"
39#include "qgsfeedback.h"
40#include "qgsgeometryengine.h"
41
42#include <QPainter>
43#include <QFile>
44#include <QSvgRenderer>
45#include <QDomDocument>
46#include <QDomElement>
47#include <QtMath>
48#include <random>
49
50#ifndef QT_NO_PRINTER
51#include <QPrinter>
52#endif
53
54QgsSimpleFillSymbolLayer::QgsSimpleFillSymbolLayer( const QColor &color, Qt::BrushStyle style, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth,
55 Qt::PenJoinStyle penJoinStyle )
56 : mBrushStyle( style )
57 , mStrokeColor( strokeColor )
58 , mStrokeStyle( strokeStyle )
59 , mStrokeWidth( strokeWidth )
60 , mPenJoinStyle( penJoinStyle )
61{
62 mColor = color;
63}
64
66
72
74{
76 if ( mOffsetUnit != unit )
77 {
79 }
80 return unit;
81}
82
88
94
103
104void QgsSimpleFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QBrush &brush, QPen &pen, QPen &selPen )
105{
106 if ( !dataDefinedProperties().hasActiveProperties() )
107 return; // shortcut
108
109 bool ok;
110
112 {
115 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
116 brush.setColor( fillColor );
117 }
119 {
122 if ( !QgsVariantUtils::isNull( exprVal ) )
123 brush.setStyle( QgsSymbolLayerUtils::decodeBrushStyle( exprVal.toString() ) );
124 }
126 {
129 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
130 pen.setColor( penColor );
131 }
133 {
136 if ( !QgsVariantUtils::isNull( exprVal ) )
137 {
138 double width = exprVal.toDouble( &ok );
139 if ( ok )
140 {
142 pen.setWidthF( width );
143 selPen.setWidthF( width );
144 }
145 }
146 }
148 {
151 if ( ok )
152 {
153 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
154 selPen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
155 }
156 }
158 {
161 if ( ok )
162 {
163 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
164 selPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
165 }
166 }
167}
168
169
171{
173 Qt::BrushStyle style = DEFAULT_SIMPLEFILL_STYLE;
178 QPointF offset;
179
180 if ( props.contains( QStringLiteral( "color" ) ) )
181 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
182 if ( props.contains( QStringLiteral( "style" ) ) )
183 style = QgsSymbolLayerUtils::decodeBrushStyle( props[QStringLiteral( "style" )].toString() );
184 if ( props.contains( QStringLiteral( "color_border" ) ) )
185 {
186 //pre 2.5 projects used "color_border"
187 strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color_border" )].toString() );
188 }
189 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
190 {
191 strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
192 }
193 else if ( props.contains( QStringLiteral( "line_color" ) ) )
194 {
195 strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
196 }
197
198 if ( props.contains( QStringLiteral( "style_border" ) ) )
199 {
200 //pre 2.5 projects used "style_border"
201 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "style_border" )].toString() );
202 }
203 else if ( props.contains( QStringLiteral( "outline_style" ) ) )
204 {
205 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
206 }
207 else if ( props.contains( QStringLiteral( "line_style" ) ) )
208 {
209 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
210 }
211 if ( props.contains( QStringLiteral( "width_border" ) ) )
212 {
213 //pre 2.5 projects used "width_border"
214 strokeWidth = props[QStringLiteral( "width_border" )].toDouble();
215 }
216 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
217 {
218 strokeWidth = props[QStringLiteral( "outline_width" )].toDouble();
219 }
220 else if ( props.contains( QStringLiteral( "line_width" ) ) )
221 {
222 strokeWidth = props[QStringLiteral( "line_width" )].toDouble();
223 }
224 if ( props.contains( QStringLiteral( "offset" ) ) )
225 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
226 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
227 penJoinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() );
228
229 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, style, strokeColor, strokeStyle, strokeWidth, penJoinStyle );
230 sl->setOffset( offset );
231 if ( props.contains( QStringLiteral( "border_width_unit" ) ) )
232 {
233 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "border_width_unit" )].toString() ) );
234 }
235 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
236 {
237 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
238 }
239 else if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
240 {
241 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
242 }
243 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
244 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
245
246 if ( props.contains( QStringLiteral( "border_width_map_unit_scale" ) ) )
247 sl->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "border_width_map_unit_scale" )].toString() ) );
248 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
249 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
250
251 sl->restoreOldDataDefinedProperties( props );
252
253 return sl.release();
254}
255
256
258{
259 return QStringLiteral( "SimpleFill" );
260}
261
263{
264 QColor fillColor = mColor;
265 fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
266 mBrush = QBrush( fillColor, mBrushStyle );
267
268 QColor selColor = context.renderContext().selectionColor();
269 QColor selPenColor = selColor == mColor ? selColor : mStrokeColor;
270 if ( ! SELECTION_IS_OPAQUE )
271 selColor.setAlphaF( context.opacity() );
272 mSelBrush = QBrush( selColor );
273 // N.B. unless a "selection line color" is implemented in addition to the "selection color" option
274 // this would mean symbols with "no fill" look the same whether or not they are selected
275 if ( SELECT_FILL_STYLE )
276 mSelBrush.setStyle( mBrushStyle );
277
278 QColor strokeColor = mStrokeColor;
279 strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
280 mPen = QPen( strokeColor );
281 mSelPen = QPen( selPenColor );
282 mPen.setStyle( mStrokeStyle );
284 mPen.setJoinStyle( mPenJoinStyle );
285}
286
288{
289 Q_UNUSED( context )
290}
291
292void QgsSimpleFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
293{
294 QPainter *p = context.renderContext().painter();
295 if ( !p )
296 {
297 return;
298 }
299
300 QColor fillColor = mColor;
301 fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
302 mBrush.setColor( fillColor );
303 QColor strokeColor = mStrokeColor;
304 strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
305 mPen.setColor( strokeColor );
306
307 applyDataDefinedSymbology( context, mBrush, mPen, mSelPen );
308
309 QPointF offset = mOffset;
310
312 {
314 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
315 bool ok = false;
316 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
317 if ( ok )
318 offset = res;
319 }
320
321 if ( !offset.isNull() )
322 {
325 p->translate( offset );
326 }
327
328 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
329
330#ifndef QT_NO_PRINTER
331 if ( mBrush.style() == Qt::SolidPattern || mBrush.style() == Qt::NoBrush || !dynamic_cast<QPrinter *>( p->device() ) )
332#endif
333 {
334 p->setPen( useSelectedColor ? mSelPen : mPen );
335 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
336 _renderPolygon( p, points, rings, context );
337 }
338#ifndef QT_NO_PRINTER
339 else
340 {
341 // workaround upstream issue https://github.com/qgis/QGIS/issues/36580
342 // when a non-solid brush is set with opacity, the opacity incorrectly applies to the pen
343 // when exporting to PDF/print devices
344 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
345 p->setPen( Qt::NoPen );
346 _renderPolygon( p, points, rings, context );
347
348 p->setPen( useSelectedColor ? mSelPen : mPen );
349 p->setBrush( Qt::NoBrush );
350 _renderPolygon( p, points, rings, context );
351 }
352#endif
353
354 if ( !offset.isNull() )
355 {
356 p->translate( -offset );
357 }
358}
359
361{
362 QVariantMap map;
363 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
364 map[QStringLiteral( "style" )] = QgsSymbolLayerUtils::encodeBrushStyle( mBrushStyle );
365 map[QStringLiteral( "outline_color" )] = QgsSymbolLayerUtils::encodeColor( mStrokeColor );
366 map[QStringLiteral( "outline_style" )] = QgsSymbolLayerUtils::encodePenStyle( mStrokeStyle );
367 map[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
368 map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
369 map[QStringLiteral( "border_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
370 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
371 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
372 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
373 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
374 return map;
375}
376
378{
379 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( mColor, mBrushStyle, mStrokeColor, mStrokeStyle, mStrokeWidth, mPenJoinStyle );
380 sl->setOffset( mOffset );
381 sl->setOffsetUnit( mOffsetUnit );
382 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
383 sl->setStrokeWidthUnit( mStrokeWidthUnit );
384 sl->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
385 copyDataDefinedProperties( sl.get() );
386 copyPaintEffect( sl.get() );
387 return sl.release();
388}
389
390void QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
391{
392 if ( mBrushStyle == Qt::NoBrush && mStrokeStyle == Qt::NoPen )
393 return;
394
395 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
396 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
397 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
398 element.appendChild( symbolizerElem );
399
400 // <Geometry>
401 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
402
403 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
404
405
406 // Export to PNG
407 bool exportOk { false };
408 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) && mBrush.style() != Qt::NoBrush )
409 {
410 const QImage image { toTiledPatternImage( ) };
411 if ( ! image.isNull() )
412 {
413 // <Fill>
414 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
415 symbolizerElem.appendChild( fillElem );
416 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
417 fillElem.appendChild( graphicFillElem );
418 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
419 graphicFillElem.appendChild( graphicElem );
420 QgsRenderContext renderContext;
421 const QFileInfo info { context.exportFilePath() };
422 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
423 pngPath = QgsFileUtils::uniquePath( pngPath );
424 image.save( pngPath );
425 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
426 exportOk = true;
427 }
428 }
429
430 if ( ! exportOk )
431 {
432 if ( mBrushStyle != Qt::NoBrush )
433 {
434
435 QColor color { mColor };
436
437 // Apply alpha from symbol
438 bool ok;
439 const double alpha { props.value( QStringLiteral( "alpha" ), QVariant() ).toDouble( &ok ) };
440 if ( ok )
441 {
442 color.setAlphaF( color.alphaF() * alpha );
443 }
444 // <Fill>
445 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
446 symbolizerElem.appendChild( fillElem );
448 }
449
450 if ( mStrokeStyle != Qt::NoPen )
451 {
452 // <Stroke>
453 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
454 symbolizerElem.appendChild( strokeElem );
456 // Apply alpha from symbol
457 bool ok;
458 const double alpha { props.value( QStringLiteral( "alpha" ), QVariant() ).toDouble( &ok ) };
459 QColor strokeColor { mStrokeColor };
460 if ( ok )
461 {
462 strokeColor.setAlphaF( strokeColor.alphaF() * alpha );
463 }
465 }
466 }
467
468 // <se:Displacement>
471}
472
473QString QgsSimpleFillSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
474{
475 //brush
476 QString symbolStyle;
477 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStyleBrush( mColor ) );
478 symbolStyle.append( ';' );
479 //pen
480 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStylePen( mStrokeWidth, mmScaleFactor, mapUnitScaleFactor, mStrokeColor, mPenJoinStyle ) );
481 return symbolStyle;
482}
483
485{
486 QColor color, strokeColor;
487 Qt::BrushStyle fillStyle;
488 Qt::PenStyle strokeStyle;
489 double strokeWidth;
490
491 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
492 QgsSymbolLayerUtils::fillFromSld( fillElem, fillStyle, color );
493
494 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
496
497 QPointF offset;
499
500 double scaleFactor = 1.0;
501 const QString uom = element.attribute( QStringLiteral( "uom" ) );
502 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
503 offset.setX( offset.x() * scaleFactor );
504 offset.setY( offset.y() * scaleFactor );
505 strokeWidth = strokeWidth * scaleFactor;
506
507 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, fillStyle, strokeColor, strokeStyle, strokeWidth );
508 sl->setOutputUnit( sldUnitSize );
509 sl->setOffset( offset );
510 return sl.release();
511}
512
514{
515 double penBleed = context.convertToPainterUnits( mStrokeStyle == Qt::NoPen ? 0 : ( mStrokeWidth / 2.0 ), mStrokeWidthUnit, mStrokeWidthMapUnitScale );
516 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
517 return penBleed + offsetBleed;
518}
519
530
541
552
554{
555 return mStrokeStyle;
556}
557
567
569{
570 return mBrushStyle;
571}
572
574{
575 QPixmap pixmap( QSize( 32, 32 ) );
576 pixmap.fill( Qt::transparent );
577 QPainter painter;
578 painter.begin( &pixmap );
579 painter.setRenderHint( QPainter::Antialiasing );
580 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
584 renderContext.setForceVectorOutput( true );
585 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
586
587 std::unique_ptr< QgsSimpleFillSymbolLayer > layerClone( clone() );
588 layerClone->setStrokeStyle( Qt::PenStyle::NoPen );
589 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
590 painter.end();
591 return pixmap.toImage();
592}
593
594//QgsGradientFillSymbolLayer
595
596QgsGradientFillSymbolLayer::QgsGradientFillSymbolLayer( const QColor &color, const QColor &color2,
597 Qgis::GradientColorSource colorType, Qgis::GradientType gradientType,
599 : mGradientColorType( colorType )
600 , mGradientType( gradientType )
601 , mCoordinateMode( coordinateMode )
602 , mGradientSpread( spread )
603 , mReferencePoint1( QPointF( 0.5, 0 ) )
604 , mReferencePoint2( QPointF( 0.5, 1 ) )
605{
606 mColor = color;
607 mColor2 = color2;
608}
609
614
616{
617 //default to a two-color, linear gradient with feature mode and pad spreading
622 //default to gradient from the default fill color to white
623 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
624 QPointF referencePoint1 = QPointF( 0.5, 0 );
625 bool refPoint1IsCentroid = false;
626 QPointF referencePoint2 = QPointF( 0.5, 1 );
627 bool refPoint2IsCentroid = false;
628 double angle = 0;
629 QPointF offset;
630
631 //update gradient properties from props
632 if ( props.contains( QStringLiteral( "type" ) ) )
633 type = static_cast< Qgis::GradientType >( props[QStringLiteral( "type" )].toInt() );
634 if ( props.contains( QStringLiteral( "coordinate_mode" ) ) )
635 coordinateMode = static_cast< Qgis::SymbolCoordinateReference >( props[QStringLiteral( "coordinate_mode" )].toInt() );
636 if ( props.contains( QStringLiteral( "spread" ) ) )
637 gradientSpread = static_cast< Qgis::GradientSpread >( props[QStringLiteral( "spread" )].toInt() );
638 if ( props.contains( QStringLiteral( "color_type" ) ) )
639 colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
640 if ( props.contains( QStringLiteral( "gradient_color" ) ) )
641 {
642 //pre 2.5 projects used "gradient_color"
643 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color" )].toString() );
644 }
645 else if ( props.contains( QStringLiteral( "color" ) ) )
646 {
647 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
648 }
649 if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
650 {
651 color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
652 }
653
654 if ( props.contains( QStringLiteral( "reference_point1" ) ) )
655 referencePoint1 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point1" )].toString() );
656 if ( props.contains( QStringLiteral( "reference_point1_iscentroid" ) ) )
657 refPoint1IsCentroid = props[QStringLiteral( "reference_point1_iscentroid" )].toInt();
658 if ( props.contains( QStringLiteral( "reference_point2" ) ) )
659 referencePoint2 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point2" )].toString() );
660 if ( props.contains( QStringLiteral( "reference_point2_iscentroid" ) ) )
661 refPoint2IsCentroid = props[QStringLiteral( "reference_point2_iscentroid" )].toInt();
662 if ( props.contains( QStringLiteral( "angle" ) ) )
663 angle = props[QStringLiteral( "angle" )].toDouble();
664
665 if ( props.contains( QStringLiteral( "offset" ) ) )
666 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
667
668 //attempt to create color ramp from props
669 QgsColorRamp *gradientRamp = nullptr;
670 if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
671 {
672 gradientRamp = QgsCptCityColorRamp::create( props );
673 }
674 else
675 {
676 gradientRamp = QgsGradientColorRamp::create( props );
677 }
678
679 //create a new gradient fill layer with desired properties
680 std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( color, color2, colorType, type, coordinateMode, gradientSpread );
681 sl->setOffset( offset );
682 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
683 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
684 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
685 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
686 sl->setReferencePoint1( referencePoint1 );
687 sl->setReferencePoint1IsCentroid( refPoint1IsCentroid );
688 sl->setReferencePoint2( referencePoint2 );
689 sl->setReferencePoint2IsCentroid( refPoint2IsCentroid );
690 sl->setAngle( angle );
691 if ( gradientRamp )
692 sl->setColorRamp( gradientRamp );
693
694 sl->restoreOldDataDefinedProperties( props );
695
696 return sl.release();
697}
698
704
706{
707 return QStringLiteral( "GradientFill" );
708}
709
710void QgsGradientFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, const QPolygonF &points )
711{
713 {
714 //shortcut
717 return;
718 }
719
720 bool ok;
721
722 //first gradient color
723 QColor color = mColor;
725 {
728 color.setAlphaF( context.opacity() * color.alphaF() );
729 }
730
731 //second gradient color
732 QColor color2 = mColor2;
734 {
737 color2.setAlphaF( context.opacity() * color2.alphaF() );
738 }
739
740 //gradient rotation angle
741 double angle = mAngle;
743 {
746 }
747
748 //gradient type
751 {
753 if ( ok )
754 {
755 if ( currentType == QObject::tr( "linear" ) )
756 {
758 }
759 else if ( currentType == QObject::tr( "radial" ) )
760 {
762 }
763 else if ( currentType == QObject::tr( "conical" ) )
764 {
766 }
767 }
768 }
769
770 //coordinate mode
773 {
774 QString currentCoordMode = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyCoordinateMode, context.renderContext().expressionContext(), QString(), &ok );
775 if ( ok )
776 {
777 if ( currentCoordMode == QObject::tr( "feature" ) )
778 {
780 }
781 else if ( currentCoordMode == QObject::tr( "viewport" ) )
782 {
784 }
785 }
786 }
787
788 //gradient spread
791 {
793 if ( ok )
794 {
795 if ( currentSpread == QObject::tr( "pad" ) )
796 {
798 }
799 else if ( currentSpread == QObject::tr( "repeat" ) )
800 {
802 }
803 else if ( currentSpread == QObject::tr( "reflect" ) )
804 {
806 }
807 }
808 }
809
810 //reference point 1 x & y
811 double refPoint1X = mReferencePoint1.x();
813 {
814 context.setOriginalValueVariable( refPoint1X );
816 }
817 double refPoint1Y = mReferencePoint1.y();
819 {
820 context.setOriginalValueVariable( refPoint1Y );
822 }
823 bool refPoint1IsCentroid = mReferencePoint1IsCentroid;
825 {
826 context.setOriginalValueVariable( refPoint1IsCentroid );
828 }
829
830 //reference point 2 x & y
831 double refPoint2X = mReferencePoint2.x();
833 {
834 context.setOriginalValueVariable( refPoint2X );
836 }
837 double refPoint2Y = mReferencePoint2.y();
839 {
840 context.setOriginalValueVariable( refPoint2Y );
842 }
843 bool refPoint2IsCentroid = mReferencePoint2IsCentroid;
845 {
846 context.setOriginalValueVariable( refPoint2IsCentroid );
848 }
849
850 if ( refPoint1IsCentroid || refPoint2IsCentroid )
851 {
852 //either the gradient is starting or ending at a centroid, so calculate it
853 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
854 //centroid coordinates need to be scaled to a range [0, 1] relative to polygon bounds
855 QRectF bbox = points.boundingRect();
856 double centroidX = ( centroid.x() - bbox.left() ) / bbox.width();
857 double centroidY = ( centroid.y() - bbox.top() ) / bbox.height();
858
859 if ( refPoint1IsCentroid )
860 {
861 refPoint1X = centroidX;
862 refPoint1Y = centroidY;
863 }
864 if ( refPoint2IsCentroid )
865 {
866 refPoint2X = centroidX;
867 refPoint2Y = centroidY;
868 }
869 }
870
871 //update gradient with data defined values
873 spread, QPointF( refPoint1X, refPoint1Y ), QPointF( refPoint2X, refPoint2Y ), angle );
874}
875
876QPointF QgsGradientFillSymbolLayer::rotateReferencePoint( QPointF refPoint, double angle )
877{
878 //rotate a reference point by a specified angle around the point (0.5, 0.5)
879
880 //create a line from the centrepoint of a rectangle bounded by (0, 0) and (1, 1) to the reference point
881 QLineF refLine = QLineF( QPointF( 0.5, 0.5 ), refPoint );
882 //rotate this line by the current rotation angle
883 refLine.setAngle( refLine.angle() + angle );
884 //get new end point of line
885 QPointF rotatedReferencePoint = refLine.p2();
886 //make sure coords of new end point is within [0, 1]
887 if ( rotatedReferencePoint.x() > 1 )
888 rotatedReferencePoint.setX( 1 );
889 if ( rotatedReferencePoint.x() < 0 )
890 rotatedReferencePoint.setX( 0 );
891 if ( rotatedReferencePoint.y() > 1 )
892 rotatedReferencePoint.setY( 1 );
893 if ( rotatedReferencePoint.y() < 0 )
894 rotatedReferencePoint.setY( 0 );
895
896 return rotatedReferencePoint;
897}
898
899void QgsGradientFillSymbolLayer::applyGradient( const QgsSymbolRenderContext &context, QBrush &brush,
900 const QColor &color, const QColor &color2, Qgis::GradientColorSource gradientColorType,
901 QgsColorRamp *gradientRamp, Qgis::GradientType gradientType,
902 Qgis::SymbolCoordinateReference coordinateMode, Qgis::GradientSpread gradientSpread,
903 QPointF referencePoint1, QPointF referencePoint2, const double angle )
904{
905 //update alpha of gradient colors
906 QColor fillColor = color;
907 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
908 QColor fillColor2 = color2;
909 fillColor2.setAlphaF( context.opacity() * fillColor2.alphaF() );
910
911 //rotate reference points
912 QPointF rotatedReferencePoint1 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint1, angle ) : referencePoint1;
913 QPointF rotatedReferencePoint2 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint2, angle ) : referencePoint2;
914
915 //create a QGradient with the desired properties
916 QGradient gradient;
917 switch ( gradientType )
918 {
920 gradient = QLinearGradient( rotatedReferencePoint1, rotatedReferencePoint2 );
921 break;
923 gradient = QRadialGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).length() );
924 break;
926 gradient = QConicalGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).angle() );
927 break;
928 }
929 switch ( coordinateMode )
930 {
932 gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
933 break;
935 gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
936 break;
937 }
938 switch ( gradientSpread )
939 {
941 gradient.setSpread( QGradient::PadSpread );
942 break;
944 gradient.setSpread( QGradient::ReflectSpread );
945 break;
947 gradient.setSpread( QGradient::RepeatSpread );
948 break;
949 }
950
951 //add stops to gradient
953 ( gradientRamp->type() == QgsGradientColorRamp::typeString() || gradientRamp->type() == QgsCptCityColorRamp::typeString() ) )
954 {
955 //color ramp gradient
956 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( gradientRamp );
957 gradRamp->addStopsToGradient( &gradient, context.opacity() );
958 }
959 else
960 {
961 //two color gradient
962 gradient.setColorAt( 0.0, fillColor );
963 gradient.setColorAt( 1.0, fillColor2 );
964 }
965
966 //update QBrush use gradient
967 brush = QBrush( gradient );
968}
969
971{
972 QColor selColor = context.renderContext().selectionColor();
973 if ( ! SELECTION_IS_OPAQUE )
974 selColor.setAlphaF( context.opacity() );
975 mSelBrush = QBrush( selColor );
976}
977
979{
980 Q_UNUSED( context )
981}
982
983void QgsGradientFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
984{
985 QPainter *p = context.renderContext().painter();
986 if ( !p )
987 {
988 return;
989 }
990
991 applyDataDefinedSymbology( context, points );
992
993 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
994 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
995 p->setPen( Qt::NoPen );
996
997 QPointF offset = mOffset;
999 {
1001 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1002 bool ok = false;
1003 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1004 if ( ok )
1005 offset = res;
1006 }
1007
1008 if ( !offset.isNull() )
1009 {
1012 p->translate( offset );
1013 }
1014
1015 _renderPolygon( p, points, rings, context );
1016
1017 if ( !offset.isNull() )
1018 {
1019 p->translate( -offset );
1020 }
1021}
1022
1024{
1025 QVariantMap map;
1026 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
1027 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
1028 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
1029 map[QStringLiteral( "type" )] = QString::number( static_cast<int>( mGradientType ) );
1030 map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
1031 map[QStringLiteral( "spread" )] = QString::number( static_cast< int >( mGradientSpread ) );
1032 map[QStringLiteral( "reference_point1" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint1 );
1033 map[QStringLiteral( "reference_point1_iscentroid" )] = QString::number( mReferencePoint1IsCentroid );
1034 map[QStringLiteral( "reference_point2" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint2 );
1035 map[QStringLiteral( "reference_point2_iscentroid" )] = QString::number( mReferencePoint2IsCentroid );
1036 map[QStringLiteral( "angle" )] = QString::number( mAngle );
1037 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1038 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1039 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1040 if ( mGradientRamp )
1041 {
1042#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1043 map.unite( mGradientRamp->properties() );
1044#else
1045 map.insert( mGradientRamp->properties() );
1046#endif
1047 }
1048 return map;
1049}
1050
1052{
1053 std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( mColor, mColor2, mGradientColorType, mGradientType, mCoordinateMode, mGradientSpread );
1054 if ( mGradientRamp )
1055 sl->setColorRamp( mGradientRamp->clone() );
1056 sl->setReferencePoint1( mReferencePoint1 );
1057 sl->setReferencePoint1IsCentroid( mReferencePoint1IsCentroid );
1058 sl->setReferencePoint2( mReferencePoint2 );
1059 sl->setReferencePoint2IsCentroid( mReferencePoint2IsCentroid );
1060 sl->setAngle( mAngle );
1061 sl->setOffset( mOffset );
1062 sl->setOffsetUnit( mOffsetUnit );
1063 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1064 copyDataDefinedProperties( sl.get() );
1065 copyPaintEffect( sl.get() );
1066 return sl.release();
1067}
1068
1070{
1071 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1072 return offsetBleed;
1073}
1074
1079
1084
1089
1094
1099
1104
1105//QgsShapeburstFillSymbolLayer
1106
1108 int blurRadius, bool useWholeShape, double maxDistance )
1109 : mBlurRadius( blurRadius )
1110 , mUseWholeShape( useWholeShape )
1111 , mMaxDistance( maxDistance )
1112 , mColorType( colorType )
1113 , mColor2( color2 )
1114{
1115 mColor = color;
1116}
1117
1119
1121{
1122 //default to a two-color gradient
1124 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
1125 int blurRadius = 0;
1126 bool useWholeShape = true;
1127 double maxDistance = 5;
1128 QPointF offset;
1129
1130 //update fill properties from props
1131 if ( props.contains( QStringLiteral( "color_type" ) ) )
1132 {
1133 colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
1134 }
1135 if ( props.contains( QStringLiteral( "shapeburst_color" ) ) )
1136 {
1137 //pre 2.5 projects used "shapeburst_color"
1138 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color" )].toString() );
1139 }
1140 else if ( props.contains( QStringLiteral( "color" ) ) )
1141 {
1142 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
1143 }
1144
1145 if ( props.contains( QStringLiteral( "shapeburst_color2" ) ) )
1146 {
1147 //pre 2.5 projects used "shapeburst_color2"
1148 color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color2" )].toString() );
1149 }
1150 else if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
1151 {
1152 color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
1153 }
1154 if ( props.contains( QStringLiteral( "blur_radius" ) ) )
1155 {
1156 blurRadius = props[QStringLiteral( "blur_radius" )].toInt();
1157 }
1158 if ( props.contains( QStringLiteral( "use_whole_shape" ) ) )
1159 {
1160 useWholeShape = props[QStringLiteral( "use_whole_shape" )].toInt();
1161 }
1162 if ( props.contains( QStringLiteral( "max_distance" ) ) )
1163 {
1164 maxDistance = props[QStringLiteral( "max_distance" )].toDouble();
1165 }
1166 if ( props.contains( QStringLiteral( "offset" ) ) )
1167 {
1168 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
1169 }
1170
1171 //attempt to create color ramp from props
1172 QgsColorRamp *gradientRamp = nullptr;
1173 if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
1174 {
1175 gradientRamp = QgsCptCityColorRamp::create( props );
1176 }
1177 else
1178 {
1179 gradientRamp = QgsGradientColorRamp::create( props );
1180 }
1181
1182 //create a new shapeburst fill layer with desired properties
1183 std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( color, color2, colorType, blurRadius, useWholeShape, maxDistance );
1184 sl->setOffset( offset );
1185 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1186 {
1187 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1188 }
1189 if ( props.contains( QStringLiteral( "distance_unit" ) ) )
1190 {
1191 sl->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "distance_unit" )].toString() ) );
1192 }
1193 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1194 {
1195 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1196 }
1197 if ( props.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
1198 {
1199 sl->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
1200 }
1201 if ( props.contains( QStringLiteral( "ignore_rings" ) ) )
1202 {
1203 sl->setIgnoreRings( props[QStringLiteral( "ignore_rings" )].toInt() );
1204 }
1205 if ( gradientRamp )
1206 {
1207 sl->setColorRamp( gradientRamp );
1208 }
1209
1210 sl->restoreOldDataDefinedProperties( props );
1211
1212 return sl.release();
1213}
1214
1216{
1217 return QStringLiteral( "ShapeburstFill" );
1218}
1219
1221{
1222 if ( mGradientRamp.get() == ramp )
1223 return;
1224
1225 mGradientRamp.reset( ramp );
1226}
1227
1228void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
1229 double &maxDistance, bool &ignoreRings )
1230{
1231 //first gradient color
1232 color = mColor;
1234 {
1237 }
1238
1239 //second gradient color
1240 color2 = mColor2;
1242 {
1245 }
1246
1247 //blur radius
1248 blurRadius = mBlurRadius;
1250 {
1251 context.setOriginalValueVariable( mBlurRadius );
1253 }
1254
1255 //use whole shape
1256 useWholeShape = mUseWholeShape;
1258 {
1259 context.setOriginalValueVariable( mUseWholeShape );
1261 }
1262
1263 //max distance
1264 maxDistance = mMaxDistance;
1266 {
1267 context.setOriginalValueVariable( mMaxDistance );
1269 }
1270
1271 //ignore rings
1272 ignoreRings = mIgnoreRings;
1274 {
1275 context.setOriginalValueVariable( mIgnoreRings );
1277 }
1278
1279}
1280
1282{
1283 //TODO - check this
1284 QColor selColor = context.renderContext().selectionColor();
1285 if ( ! SELECTION_IS_OPAQUE )
1286 selColor.setAlphaF( context.opacity() );
1287 mSelBrush = QBrush( selColor );
1288}
1289
1291{
1292 Q_UNUSED( context )
1293}
1294
1295void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1296{
1297 QPainter *p = context.renderContext().painter();
1298 if ( !p )
1299 {
1300 return;
1301 }
1302
1303 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1304 if ( useSelectedColor )
1305 {
1306 //feature is selected, draw using selection style
1307 p->setBrush( mSelBrush );
1308 QPointF offset = mOffset;
1309
1311 {
1313 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1314 bool ok = false;
1315 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1316 if ( ok )
1317 offset = res;
1318 }
1319
1320 if ( !offset.isNull() )
1321 {
1322 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1323 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1324 p->translate( offset );
1325 }
1326 _renderPolygon( p, points, rings, context );
1327 if ( !offset.isNull() )
1328 {
1329 p->translate( -offset );
1330 }
1331 return;
1332 }
1333
1334 QColor color1, color2;
1335 int blurRadius;
1336 bool useWholeShape;
1337 double maxDistance;
1338 bool ignoreRings;
1339 //calculate data defined symbology
1340 applyDataDefinedSymbology( context, color1, color2, blurRadius, useWholeShape, maxDistance, ignoreRings );
1341
1342 //calculate max distance for shapeburst fill to extend from polygon boundary, in pixels
1343 int outputPixelMaxDist = 0;
1344 if ( !useWholeShape && !qgsDoubleNear( maxDistance, 0.0 ) )
1345 {
1346 //convert max distance to pixels
1347 outputPixelMaxDist = static_cast< int >( std::round( context.renderContext().convertToPainterUnits( maxDistance, mDistanceUnit, mDistanceMapUnitScale ) ) );
1348 }
1349
1350 //if we are using the two color mode, create a gradient ramp
1351 std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
1353 {
1354 twoColorGradientRamp = std::make_unique< QgsGradientColorRamp >( color1, color2 );
1355 }
1356
1357 //no stroke for shapeburst fills
1358 p->setPen( QPen( Qt::NoPen ) );
1359
1360 //calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
1361 int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
1362 //create a QImage to draw shapeburst in
1363 int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1364 int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1365 int imWidth = pointsWidth + ( sideBuffer * 2 );
1366 int imHeight = pointsHeight + ( sideBuffer * 2 );
1367
1368 // these are all potentially very expensive operations, so check regularly if the job is canceled and abort responsively
1369 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1370 return;
1371
1372 std::unique_ptr< QImage > fillImage = std::make_unique< QImage >( imWidth,
1373 imHeight, QImage::Format_ARGB32_Premultiplied );
1374 if ( fillImage->isNull() )
1375 {
1376 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1377 return;
1378 }
1379
1380 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1381 return;
1382
1383 //also create an image to store the alpha channel
1384 std::unique_ptr< QImage > alphaImage = std::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1385 if ( alphaImage->isNull() )
1386 {
1387 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1388 return;
1389 }
1390
1391 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1392 return;
1393
1394 //Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
1395 //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
1396 //polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
1397 fillImage->fill( Qt::black );
1398
1399 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1400 return;
1401
1402 //initially fill the alpha channel image with a transparent color
1403 alphaImage->fill( Qt::transparent );
1404
1405 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1406 return;
1407
1408 //now, draw the polygon in the alpha channel image
1409 QPainter imgPainter;
1410 imgPainter.begin( alphaImage.get() );
1411 imgPainter.setRenderHint( QPainter::Antialiasing, true );
1412 imgPainter.setBrush( QBrush( Qt::white ) );
1413 imgPainter.setPen( QPen( Qt::black ) );
1414 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1415 _renderPolygon( &imgPainter, points, rings, context );
1416 imgPainter.end();
1417
1418 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1419 return;
1420
1421 //now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
1422 //(this avoids calling _renderPolygon twice, since that can be slow)
1423 imgPainter.begin( fillImage.get() );
1424 if ( !ignoreRings )
1425 {
1426 imgPainter.drawImage( 0, 0, *alphaImage );
1427 }
1428 else
1429 {
1430 //using ignore rings mode, so the alpha image can't be used
1431 //directly as the alpha channel contains polygon rings and we need
1432 //to draw now without any rings
1433 imgPainter.setBrush( QBrush( Qt::white ) );
1434 imgPainter.setPen( QPen( Qt::black ) );
1435 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1436 _renderPolygon( &imgPainter, points, nullptr, context );
1437 }
1438 imgPainter.end();
1439
1440 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1441 return;
1442
1443 //apply distance transform to image, uses the current color ramp to calculate final pixel colors
1444 double *dtArray = distanceTransform( fillImage.get(), context.renderContext() );
1445
1446 //copy distance transform values back to QImage, shading by appropriate color ramp
1447 dtArrayToQImage( dtArray, fillImage.get(), mColorType == Qgis::GradientColorSource::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
1448 context.renderContext(), useWholeShape, outputPixelMaxDist );
1449 if ( context.opacity() < 1 )
1450 {
1451 QgsImageOperation::multiplyOpacity( *fillImage, context.opacity(), context.renderContext().feedback() );
1452 }
1453
1454 //clean up some variables
1455 delete [] dtArray;
1456
1457 //apply blur if desired
1458 if ( blurRadius > 0 )
1459 {
1460 QgsImageOperation::stackBlur( *fillImage, blurRadius, false, context.renderContext().feedback() );
1461 }
1462
1463 //apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1464 imgPainter.begin( fillImage.get() );
1465 imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
1466 imgPainter.drawImage( 0, 0, *alphaImage );
1467 imgPainter.end();
1468 //we're finished with the alpha channel image now
1469 alphaImage.reset();
1470
1471 //draw shapeburst image in correct place in the destination painter
1472
1473 QgsScopedQPainterState painterState( p );
1474 QPointF offset = mOffset;
1476 {
1478 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1479 bool ok = false;
1480 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1481 if ( ok )
1482 offset = res;
1483 }
1484 if ( !offset.isNull() )
1485 {
1486 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1487 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1488 p->translate( offset );
1489 }
1490
1491 p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
1492
1493 if ( !offset.isNull() )
1494 {
1495 p->translate( -offset );
1496 }
1497}
1498
1499//fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
1500
1501/* distance transform of a 1d function using squared distance */
1502void QgsShapeburstFillSymbolLayer::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
1503{
1504 int k = 0;
1505 v[0] = 0;
1506 z[0] = -INF;
1507 z[1] = + INF;
1508 for ( int q = 1; q <= n - 1; q++ )
1509 {
1510 double s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1511 while ( s <= z[k] )
1512 {
1513 k--;
1514 s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1515 }
1516 k++;
1517 v[k] = q;
1518 z[k] = s;
1519 z[k + 1] = + INF;
1520 }
1521
1522 k = 0;
1523 for ( int q = 0; q <= n - 1; q++ )
1524 {
1525 while ( z[k + 1] < q )
1526 k++;
1527 d[q] = static_cast< double >( q - v[k] ) * ( q - v[k] ) + f[v[k]];
1528 }
1529}
1530
1531/* distance transform of 2d function using squared distance */
1532void QgsShapeburstFillSymbolLayer::distanceTransform2d( double *im, int width, int height, QgsRenderContext &context )
1533{
1534 int maxDimension = std::max( width, height );
1535 double *f = new double[ maxDimension ];
1536 int *v = new int[ maxDimension ];
1537 double *z = new double[ maxDimension + 1 ];
1538 double *d = new double[ maxDimension ];
1539
1540 // transform along columns
1541 for ( int x = 0; x < width; x++ )
1542 {
1543 if ( context.renderingStopped() )
1544 break;
1545
1546 for ( int y = 0; y < height; y++ )
1547 {
1548 f[y] = im[ x + static_cast< std::size_t>( y ) * width ];
1549 }
1550 distanceTransform1d( f, height, v, z, d );
1551 for ( int y = 0; y < height; y++ )
1552 {
1553 im[ x + static_cast< std::size_t>( y ) * width ] = d[y];
1554 }
1555 }
1556
1557 // transform along rows
1558 for ( int y = 0; y < height; y++ )
1559 {
1560 if ( context.renderingStopped() )
1561 break;
1562
1563 for ( int x = 0; x < width; x++ )
1564 {
1565 f[x] = im[ x + static_cast< std::size_t>( y ) * width ];
1566 }
1567 distanceTransform1d( f, width, v, z, d );
1568 for ( int x = 0; x < width; x++ )
1569 {
1570 im[ x + static_cast< std::size_t>( y ) * width ] = d[x];
1571 }
1572 }
1573
1574 delete [] d;
1575 delete [] f;
1576 delete [] v;
1577 delete [] z;
1578}
1579
1580/* distance transform of a binary QImage */
1581double *QgsShapeburstFillSymbolLayer::distanceTransform( QImage *im, QgsRenderContext &context )
1582{
1583 int width = im->width();
1584 int height = im->height();
1585
1586 double *dtArray = new double[static_cast< std::size_t>( width ) * height];
1587
1588 //load qImage to array
1589 QRgb tmpRgb;
1590 std::size_t idx = 0;
1591 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1592 {
1593 if ( context.renderingStopped() )
1594 break;
1595
1596 const QRgb *scanLine = reinterpret_cast< const QRgb * >( im->constScanLine( heightIndex ) );
1597 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1598 {
1599 tmpRgb = scanLine[widthIndex];
1600 if ( qRed( tmpRgb ) == 0 )
1601 {
1602 //black pixel, so zero distance
1603 dtArray[ idx ] = 0;
1604 }
1605 else
1606 {
1607 //white pixel, so initially set distance as infinite
1608 dtArray[ idx ] = INF;
1609 }
1610 idx++;
1611 }
1612 }
1613
1614 //calculate squared distance transform
1615 distanceTransform2d( dtArray, width, height, context );
1616
1617 return dtArray;
1618}
1619
1620void QgsShapeburstFillSymbolLayer::dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, QgsRenderContext &context, bool useWholeShape, int maxPixelDistance )
1621{
1622 int width = im->width();
1623 int height = im->height();
1624
1625 //find maximum distance value
1626 double maxDistanceValue;
1627
1628 if ( useWholeShape )
1629 {
1630 //no max distance specified in symbol properties, so calculate from maximum value in distance transform results
1631 double dtMaxValue = array[0];
1632 for ( std::size_t i = 1; i < static_cast< std::size_t >( width ) * height; ++i )
1633 {
1634 if ( array[i] > dtMaxValue )
1635 {
1636 dtMaxValue = array[i];
1637 }
1638 }
1639
1640 //values in distance transform are squared
1641 maxDistanceValue = std::sqrt( dtMaxValue );
1642 }
1643 else
1644 {
1645 //use max distance set in symbol properties
1646 maxDistanceValue = maxPixelDistance;
1647 }
1648
1649 //update the pixels in the provided QImage
1650 std::size_t idx = 0;
1651 double squaredVal = 0;
1652 double pixVal = 0;
1653
1654 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1655 {
1656 if ( context.renderingStopped() )
1657 break;
1658
1659 QRgb *scanLine = reinterpret_cast< QRgb * >( im->scanLine( heightIndex ) );
1660 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1661 {
1662 //result of distance transform
1663 squaredVal = array[idx];
1664
1665 //scale result to fit in the range [0, 1]
1666 if ( maxDistanceValue > 0 )
1667 {
1668 pixVal = squaredVal > 0 ? std::min( ( std::sqrt( squaredVal ) / maxDistanceValue ), 1.0 ) : 0;
1669 }
1670 else
1671 {
1672 pixVal = 1.0;
1673 }
1674
1675 //convert value to color from ramp
1676 //premultiply ramp color since we are storing this in a ARGB32_Premultiplied QImage
1677 scanLine[widthIndex] = qPremultiply( ramp->color( pixVal ).rgba() );
1678 idx++;
1679 }
1680 }
1681}
1682
1684{
1685 QVariantMap map;
1686 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
1687 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
1688 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mColorType ) );
1689 map[QStringLiteral( "blur_radius" )] = QString::number( mBlurRadius );
1690 map[QStringLiteral( "use_whole_shape" )] = QString::number( mUseWholeShape );
1691 map[QStringLiteral( "max_distance" )] = QString::number( mMaxDistance );
1692 map[QStringLiteral( "distance_unit" )] = QgsUnitTypes::encodeUnit( mDistanceUnit );
1693 map[QStringLiteral( "distance_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale );
1694 map[QStringLiteral( "ignore_rings" )] = QString::number( mIgnoreRings );
1695 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1696 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1697 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1698 if ( mGradientRamp )
1699 {
1700#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1701 map.unite( mGradientRamp->properties() );
1702#else
1703 map.insert( mGradientRamp->properties() );
1704#endif
1705 }
1706
1707 return map;
1708}
1709
1711{
1712 std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( mColor, mColor2, mColorType, mBlurRadius, mUseWholeShape, mMaxDistance );
1713 if ( mGradientRamp )
1714 {
1715 sl->setColorRamp( mGradientRamp->clone() );
1716 }
1717 sl->setDistanceUnit( mDistanceUnit );
1718 sl->setDistanceMapUnitScale( mDistanceMapUnitScale );
1719 sl->setIgnoreRings( mIgnoreRings );
1720 sl->setOffset( mOffset );
1721 sl->setOffsetUnit( mOffsetUnit );
1722 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1723 copyDataDefinedProperties( sl.get() );
1724 copyPaintEffect( sl.get() );
1725 return sl.release();
1726}
1727
1729{
1730 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1731 return offsetBleed;
1732}
1733
1738
1740{
1741 mDistanceUnit = unit;
1742 mOffsetUnit = unit;
1743}
1744
1746{
1747 if ( mDistanceUnit == mOffsetUnit )
1748 {
1749 return mDistanceUnit;
1750 }
1752}
1753
1755{
1756 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
1757 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
1758}
1759
1761{
1762 mDistanceMapUnitScale = scale;
1763 mOffsetMapUnitScale = scale;
1764}
1765
1767{
1768 if ( mDistanceMapUnitScale == mOffsetMapUnitScale )
1769 {
1770 return mDistanceMapUnitScale;
1771 }
1772 return QgsMapUnitScale();
1773}
1774
1775
1776//QgsImageFillSymbolLayer
1777
1781
1783
1784void QgsImageFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1785{
1786 QPainter *p = context.renderContext().painter();
1787 if ( !p )
1788 {
1789 return;
1790 }
1791
1793 applyDataDefinedSettings( context );
1794
1795 p->setPen( QPen( Qt::NoPen ) );
1796
1797 QTransform bkTransform = mBrush.transform();
1798 if ( applyBrushTransformFromContext( &context ) && !context.renderContext().textureOrigin().isNull() )
1799 {
1800 QPointF leftCorner = context.renderContext().textureOrigin();
1801 QTransform t = mBrush.transform();
1802 t.translate( leftCorner.x(), leftCorner.y() );
1803 mBrush.setTransform( t );
1804 }
1805 else
1806 {
1807 QTransform t = mBrush.transform();
1808 t.translate( 0, 0 );
1809 mBrush.setTransform( t );
1810 }
1811
1812 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1813 if ( useSelectedColor )
1814 {
1815 QColor selColor = context.renderContext().selectionColor();
1816 p->setBrush( QBrush( selColor ) );
1817 _renderPolygon( p, points, rings, context );
1818 }
1819
1820 if ( !qgsDoubleNear( mNextAngle, 0.0 ) )
1821 {
1822 QTransform t = mBrush.transform();
1823 t.rotate( mNextAngle );
1824 mBrush.setTransform( t );
1825 }
1826 p->setBrush( mBrush );
1827 _renderPolygon( p, points, rings, context );
1828
1829 mBrush.setTransform( bkTransform );
1830}
1831
1836
1841
1846
1851
1862
1864{
1865 return Qt::SolidLine;
1866#if 0
1867 if ( !mStroke )
1868 {
1869 return Qt::SolidLine;
1870 }
1871 else
1872 {
1873 return mStroke->dxfPenStyle();
1874 }
1875#endif //0
1876}
1877
1879{
1880 QVariantMap map;
1881 map.insert( QStringLiteral( "coordinate_reference" ), QgsSymbolLayerUtils::encodeCoordinateReference( mCoordinateReference ) );
1882 return map;
1883}
1884
1903
1904
1905//QgsSVGFillSymbolLayer
1906
1907QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString &svgFilePath, double width, double angle )
1909 , mPatternWidth( width )
1910{
1911 mStrokeWidth = 0.3;
1912 mAngle = angle;
1913 mColor = QColor( 255, 255, 255 );
1915}
1916
1917QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray &svgData, double width, double angle )
1919 , mPatternWidth( width )
1920 , mSvgData( svgData )
1921{
1922 storeViewBox();
1923 mStrokeWidth = 0.3;
1924 mAngle = angle;
1925 mColor = QColor( 255, 255, 255 );
1926 setDefaultSvgParams();
1927}
1928
1930
1932{
1934 mPatternWidthUnit = unit;
1935 mSvgStrokeWidthUnit = unit;
1936 mStrokeWidthUnit = unit;
1937 if ( mStroke )
1938 mStroke->setOutputUnit( unit );
1939}
1940
1942{
1944 if ( mPatternWidthUnit != unit || mSvgStrokeWidthUnit != unit || mStrokeWidthUnit != unit )
1945 {
1947 }
1948 return unit;
1949}
1950
1952{
1954 mPatternWidthMapUnitScale = scale;
1955 mSvgStrokeWidthMapUnitScale = scale;
1956}
1957
1959{
1960 if ( QgsImageFillSymbolLayer::mapUnitScale() == mPatternWidthMapUnitScale &&
1961 mPatternWidthMapUnitScale == mSvgStrokeWidthMapUnitScale &&
1962 mSvgStrokeWidthMapUnitScale == mStrokeWidthMapUnitScale )
1963 {
1964 return mPatternWidthMapUnitScale;
1965 }
1966 return QgsMapUnitScale();
1967}
1968
1969void QgsSVGFillSymbolLayer::setSvgFilePath( const QString &svgPath )
1970{
1971 mSvgData = QgsApplication::svgCache()->getImageData( svgPath );
1972 storeViewBox();
1973
1974 mSvgFilePath = svgPath;
1975 setDefaultSvgParams();
1976}
1977
1978QgsSymbolLayer *QgsSVGFillSymbolLayer::create( const QVariantMap &properties )
1979{
1980 QByteArray data;
1981 double width = 20;
1982 QString svgFilePath;
1983 double angle = 0.0;
1984
1985 if ( properties.contains( QStringLiteral( "width" ) ) )
1986 {
1987 width = properties[QStringLiteral( "width" )].toDouble();
1988 }
1989 if ( properties.contains( QStringLiteral( "svgFile" ) ) )
1990 {
1991 svgFilePath = properties[QStringLiteral( "svgFile" )].toString();
1992 }
1993 if ( properties.contains( QStringLiteral( "angle" ) ) )
1994 {
1995 angle = properties[QStringLiteral( "angle" )].toDouble();
1996 }
1997
1998 std::unique_ptr< QgsSVGFillSymbolLayer > symbolLayer;
1999 if ( !svgFilePath.isEmpty() )
2000 {
2001 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( svgFilePath, width, angle );
2002 }
2003 else
2004 {
2005 if ( properties.contains( QStringLiteral( "data" ) ) )
2006 {
2007 data = QByteArray::fromHex( properties[QStringLiteral( "data" )].toString().toLocal8Bit() );
2008 }
2009 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( data, width, angle );
2010 }
2011
2012 //svg parameters
2013 if ( properties.contains( QStringLiteral( "svgFillColor" ) ) )
2014 {
2015 //pre 2.5 projects used "svgFillColor"
2016 symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgFillColor" )].toString() ) );
2017 }
2018 else if ( properties.contains( QStringLiteral( "color" ) ) )
2019 {
2020 symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
2021 }
2022 if ( properties.contains( QStringLiteral( "svgOutlineColor" ) ) )
2023 {
2024 //pre 2.5 projects used "svgOutlineColor"
2025 symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgOutlineColor" )].toString() ) );
2026 }
2027 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2028 {
2029 symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() ) );
2030 }
2031 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2032 {
2033 symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() ) );
2034 }
2035 if ( properties.contains( QStringLiteral( "svgOutlineWidth" ) ) )
2036 {
2037 //pre 2.5 projects used "svgOutlineWidth"
2038 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "svgOutlineWidth" )].toDouble() );
2039 }
2040 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2041 {
2042 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "outline_width" )].toDouble() );
2043 }
2044 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2045 {
2046 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "line_width" )].toDouble() );
2047 }
2048
2049 //units
2050 if ( properties.contains( QStringLiteral( "pattern_width_unit" ) ) )
2051 {
2052 symbolLayer->setPatternWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "pattern_width_unit" )].toString() ) );
2053 }
2054 if ( properties.contains( QStringLiteral( "pattern_width_map_unit_scale" ) ) )
2055 {
2056 symbolLayer->setPatternWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "pattern_width_map_unit_scale" )].toString() ) );
2057 }
2058 if ( properties.contains( QStringLiteral( "svg_outline_width_unit" ) ) )
2059 {
2060 symbolLayer->setSvgStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "svg_outline_width_unit" )].toString() ) );
2061 }
2062 if ( properties.contains( QStringLiteral( "svg_outline_width_map_unit_scale" ) ) )
2063 {
2064 symbolLayer->setSvgStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "svg_outline_width_map_unit_scale" )].toString() ) );
2065 }
2066 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2067 {
2068 symbolLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2069 }
2070 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2071 {
2072 symbolLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2073 }
2074
2075 if ( properties.contains( QStringLiteral( "parameters" ) ) )
2076 {
2077 const QVariantMap parameters = properties[QStringLiteral( "parameters" )].toMap();
2078 symbolLayer->setParameters( QgsProperty::variantMapToPropertyMap( parameters ) );
2079 }
2080
2081 symbolLayer->restoreOldDataDefinedProperties( properties );
2082
2083 return symbolLayer.release();
2084}
2085
2086void QgsSVGFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2087{
2088 QVariantMap::iterator it = properties.find( QStringLiteral( "svgFile" ) );
2089 if ( it != properties.end() )
2090 {
2091 if ( saving )
2092 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2093 else
2094 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2095 }
2096}
2097
2099{
2100 return QStringLiteral( "SVGFill" );
2101}
2102
2103void QgsSVGFillSymbolLayer::applyPattern( QBrush &brush, const QString &svgFilePath, double patternWidth, Qgis::RenderUnit patternWidthUnit,
2104 const QColor &svgFillColor, const QColor &svgStrokeColor, double svgStrokeWidth,
2105 Qgis::RenderUnit svgStrokeWidthUnit, const QgsSymbolRenderContext &context,
2106 const QgsMapUnitScale &patternWidthMapUnitScale, const QgsMapUnitScale &svgStrokeWidthMapUnitScale, const QgsStringMap svgParameters )
2107{
2108 if ( mSvgViewBox.isNull() )
2109 {
2110 return;
2111 }
2112
2114
2115 if ( static_cast< int >( size ) < 1.0 || 10000.0 < size )
2116 {
2117 brush.setTextureImage( QImage() );
2118 }
2119 else
2120 {
2121 bool fitsInCache = true;
2123 QImage patternImage = QgsApplication::svgCache()->svgAsImage( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2124 context.renderContext().scaleFactor(), fitsInCache, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), svgParameters );
2125 if ( !fitsInCache )
2126 {
2127 QPicture patternPict = QgsApplication::svgCache()->svgAsPicture( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2128 context.renderContext().scaleFactor(), false, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
2129 double hwRatio = 1.0;
2130 if ( patternPict.width() > 0 )
2131 {
2132 hwRatio = static_cast< double >( patternPict.height() ) / static_cast< double >( patternPict.width() );
2133 }
2134 patternImage = QImage( static_cast< int >( size ), static_cast< int >( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
2135 patternImage.fill( 0 ); // transparent background
2136
2137 QPainter p( &patternImage );
2138 p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
2139 }
2140
2141 QTransform brushTransform;
2142 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2143 {
2144 QImage transparentImage = patternImage.copy();
2145 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2146 brush.setTextureImage( transparentImage );
2147 }
2148 else
2149 {
2150 brush.setTextureImage( patternImage );
2151 }
2152 brush.setTransform( brushTransform );
2153 }
2154}
2155
2157{
2158 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2159
2160 applyPattern( mBrush, mSvgFilePath, mPatternWidth, mPatternWidthUnit, mColor, mSvgStrokeColor, mSvgStrokeWidth, mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2161
2162 if ( mStroke )
2163 {
2164 mStroke->startRender( context.renderContext(), context.fields() );
2165 }
2166}
2167
2169{
2170 if ( mStroke )
2171 {
2172 mStroke->stopRender( context.renderContext() );
2173 }
2174}
2175
2176void QgsSVGFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
2177{
2178 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
2179
2180 if ( mStroke )
2181 {
2182 const bool useSelectedColor = SELECT_FILL_BORDER && shouldRenderUsingSelectionColor( context );
2183 mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, useSelectedColor );
2184 if ( rings )
2185 {
2186 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
2187 {
2188 mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, useSelectedColor );
2189 }
2190 }
2191 }
2192}
2193
2195{
2196 QVariantMap map;
2197 if ( !mSvgFilePath.isEmpty() )
2198 {
2199 map.insert( QStringLiteral( "svgFile" ), mSvgFilePath );
2200 }
2201 else
2202 {
2203 map.insert( QStringLiteral( "data" ), QString( mSvgData.toHex() ) );
2204 }
2205
2206 map.insert( QStringLiteral( "width" ), QString::number( mPatternWidth ) );
2207 map.insert( QStringLiteral( "angle" ), QString::number( mAngle ) );
2208
2209 //svg parameters
2210 map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
2211 map.insert( QStringLiteral( "outline_color" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2212 map.insert( QStringLiteral( "outline_width" ), QString::number( mSvgStrokeWidth ) );
2213
2214 //units
2215 map.insert( QStringLiteral( "pattern_width_unit" ), QgsUnitTypes::encodeUnit( mPatternWidthUnit ) );
2216 map.insert( QStringLiteral( "pattern_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mPatternWidthMapUnitScale ) );
2217 map.insert( QStringLiteral( "svg_outline_width_unit" ), QgsUnitTypes::encodeUnit( mSvgStrokeWidthUnit ) );
2218 map.insert( QStringLiteral( "svg_outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mSvgStrokeWidthMapUnitScale ) );
2219 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
2220 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
2221
2222 map[QStringLiteral( "parameters" )] = QgsProperty::propertyMapToVariantMap( mParameters );
2223
2224 return map;
2225}
2226
2228{
2229 std::unique_ptr< QgsSVGFillSymbolLayer > clonedLayer;
2230 if ( !mSvgFilePath.isEmpty() )
2231 {
2232 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgFilePath, mPatternWidth, mAngle );
2233 clonedLayer->setSvgFillColor( mColor );
2234 clonedLayer->setSvgStrokeColor( mSvgStrokeColor );
2235 clonedLayer->setSvgStrokeWidth( mSvgStrokeWidth );
2236 }
2237 else
2238 {
2239 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgData, mPatternWidth, mAngle );
2240 }
2241
2242 clonedLayer->setPatternWidthUnit( mPatternWidthUnit );
2243 clonedLayer->setPatternWidthMapUnitScale( mPatternWidthMapUnitScale );
2244 clonedLayer->setSvgStrokeWidthUnit( mSvgStrokeWidthUnit );
2245 clonedLayer->setSvgStrokeWidthMapUnitScale( mSvgStrokeWidthMapUnitScale );
2246 clonedLayer->setStrokeWidthUnit( mStrokeWidthUnit );
2247 clonedLayer->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
2248
2249 clonedLayer->setParameters( mParameters );
2250
2251 if ( mStroke )
2252 {
2253 clonedLayer->setSubSymbol( mStroke->clone() );
2254 }
2255 copyDataDefinedProperties( clonedLayer.get() );
2256 copyPaintEffect( clonedLayer.get() );
2257 return clonedLayer.release();
2258}
2259
2260void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2261{
2262 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
2263 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2264 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2265 element.appendChild( symbolizerElem );
2266
2267 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2268
2269 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
2270 symbolizerElem.appendChild( fillElem );
2271
2272 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
2273 fillElem.appendChild( graphicFillElem );
2274
2275 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2276 graphicFillElem.appendChild( graphicElem );
2277
2278 if ( !mSvgFilePath.isEmpty() )
2279 {
2280 // encode a parametric SVG reference
2281 double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
2282 double strokeWidth = QgsSymbolLayerUtils::rescaleUom( mSvgStrokeWidth, mSvgStrokeWidthUnit, props );
2283 QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgStrokeColor, strokeWidth );
2284 }
2285 else
2286 {
2287 // TODO: create svg from data
2288 // <se:InlineContent>
2289 symbolizerElem.appendChild( doc.createComment( QStringLiteral( "SVG from data not implemented yet" ) ) );
2290 }
2291
2292 // <Rotation>
2293 QString angleFunc;
2294 bool ok;
2295 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
2296 if ( !ok )
2297 {
2298 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
2299 }
2300 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2301 {
2302 angleFunc = QString::number( angle + mAngle );
2303 }
2304 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
2305
2306 if ( mStroke )
2307 {
2308 // the stroke sub symbol should be stored within the Stroke element,
2309 // but it will be stored in a separated LineSymbolizer because it could
2310 // have more than one layer
2311 mStroke->toSld( doc, element, props );
2312 }
2313}
2314
2316{
2317 return mPatternWidthUnit == Qgis::RenderUnit::MapUnits || mPatternWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2318 || mSvgStrokeWidthUnit == Qgis::RenderUnit::MapUnits || mSvgStrokeWidthUnit == Qgis::RenderUnit::MetersInMapUnits;
2319}
2320
2322{
2323 return mStroke.get();
2324}
2325
2327{
2328 if ( !symbol ) //unset current stroke
2329 {
2330 mStroke.reset( nullptr );
2331 return true;
2332 }
2333
2334 if ( symbol->type() != Qgis::SymbolType::Line )
2335 {
2336 delete symbol;
2337 return false;
2338 }
2339
2340 QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
2341 if ( lineSymbol )
2342 {
2343 mStroke.reset( lineSymbol );
2344 return true;
2345 }
2346
2347 delete symbol;
2348 return false;
2349}
2350
2352{
2353 if ( mStroke && mStroke->symbolLayer( 0 ) )
2354 {
2355 double subLayerBleed = mStroke->symbolLayer( 0 )->estimateMaxBleed( context );
2356 return subLayerBleed;
2357 }
2358 return 0;
2359}
2360
2362{
2363 Q_UNUSED( context )
2364 if ( !mStroke )
2365 {
2366 return QColor( Qt::black );
2367 }
2368 return mStroke->color();
2369}
2370
2371QSet<QString> QgsSVGFillSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2372{
2373 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2374 if ( mStroke )
2375 attr.unite( mStroke->usedAttributes( context ) );
2376 return attr;
2377}
2378
2380{
2382 return true;
2383 if ( mStroke && mStroke->hasDataDefinedProperties() )
2384 return true;
2385 return false;
2386}
2387
2389{
2390 QString path, mimeType;
2391 QColor fillColor, strokeColor;
2392 Qt::PenStyle penStyle;
2393 double size, strokeWidth;
2394
2395 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
2396 if ( fillElem.isNull() )
2397 return nullptr;
2398
2399 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
2400 if ( graphicFillElem.isNull() )
2401 return nullptr;
2402
2403 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
2404 if ( graphicElem.isNull() )
2405 return nullptr;
2406
2407 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
2408 return nullptr;
2409
2410 if ( mimeType != QLatin1String( "image/svg+xml" ) )
2411 return nullptr;
2412
2413 QgsSymbolLayerUtils::lineFromSld( graphicElem, penStyle, strokeColor, strokeWidth );
2414
2415 double scaleFactor = 1.0;
2416 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2417 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2418 size = size * scaleFactor;
2419 strokeWidth = strokeWidth * scaleFactor;
2420
2421 double angle = 0.0;
2422 QString angleFunc;
2423 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2424 {
2425 bool ok;
2426 double d = angleFunc.toDouble( &ok );
2427 if ( ok )
2428 angle = d;
2429 }
2430
2431 std::unique_ptr< QgsSVGFillSymbolLayer > sl = std::make_unique< QgsSVGFillSymbolLayer >( path, size, angle );
2432 sl->setOutputUnit( sldUnitSize );
2433 sl->setSvgFillColor( fillColor );
2434 sl->setSvgStrokeColor( strokeColor );
2435 sl->setSvgStrokeWidth( strokeWidth );
2436
2437 // try to get the stroke
2438 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2439 if ( !strokeElem.isNull() )
2440 {
2442 if ( l )
2443 {
2444 QgsSymbolLayerList layers;
2445 layers.append( l );
2446 sl->setSubSymbol( new QgsLineSymbol( layers ) );
2447 }
2448 }
2449
2450 return sl.release();
2451}
2452
2454{
2458 {
2459 return; //no data defined settings
2460 }
2461
2463 {
2466 }
2467
2468 double width = mPatternWidth;
2470 {
2471 context.setOriginalValueVariable( mPatternWidth );
2473 }
2474 QString svgFile = mSvgFilePath;
2476 {
2477 context.setOriginalValueVariable( mSvgFilePath );
2479 context.renderContext().pathResolver() );
2480 }
2481 QColor svgFillColor = mColor;
2483 {
2486 }
2487 QColor svgStrokeColor = mSvgStrokeColor;
2489 {
2490 context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2492 }
2493 double strokeWidth = mSvgStrokeWidth;
2495 {
2496 context.setOriginalValueVariable( mSvgStrokeWidth );
2498 }
2499 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2500
2501 applyPattern( mBrush, svgFile, width, mPatternWidthUnit, svgFillColor, svgStrokeColor, strokeWidth,
2502 mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2503
2504}
2505
2506void QgsSVGFillSymbolLayer::storeViewBox()
2507{
2508 if ( !mSvgData.isEmpty() )
2509 {
2510 QSvgRenderer r( mSvgData );
2511 if ( r.isValid() )
2512 {
2513 mSvgViewBox = r.viewBoxF();
2514 return;
2515 }
2516 }
2517
2518 mSvgViewBox = QRectF();
2519}
2520
2521void QgsSVGFillSymbolLayer::setDefaultSvgParams()
2522{
2523 if ( mSvgFilePath.isEmpty() )
2524 {
2525 return;
2526 }
2527
2528 bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
2529 bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
2530 QColor defaultFillColor, defaultStrokeColor;
2531 double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
2532 QgsApplication::svgCache()->containsParams( mSvgFilePath, hasFillParam, hasDefaultFillColor, defaultFillColor,
2533 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
2534 hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2535 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
2536 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
2537
2538 double newFillOpacity = hasFillOpacityParam ? mColor.alphaF() : 1.0;
2539 double newStrokeOpacity = hasStrokeOpacityParam ? mSvgStrokeColor.alphaF() : 1.0;
2540
2541 if ( hasDefaultFillColor )
2542 {
2543 mColor = defaultFillColor;
2544 mColor.setAlphaF( newFillOpacity );
2545 }
2546 if ( hasDefaultFillOpacity )
2547 {
2548 mColor.setAlphaF( defaultFillOpacity );
2549 }
2550 if ( hasDefaultStrokeColor )
2551 {
2552 mSvgStrokeColor = defaultStrokeColor;
2553 mSvgStrokeColor.setAlphaF( newStrokeOpacity );
2554 }
2555 if ( hasDefaultStrokeOpacity )
2556 {
2557 mSvgStrokeColor.setAlphaF( defaultStrokeOpacity );
2558 }
2559 if ( hasDefaultStrokeWidth )
2560 {
2561 mSvgStrokeWidth = defaultStrokeWidth;
2562 }
2563}
2564
2565void QgsSVGFillSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2566{
2567 mParameters = parameters;
2568}
2569
2570
2573{
2574 mFillLineSymbol = std::make_unique<QgsLineSymbol>( );
2575 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
2576}
2577
2579
2581{
2582 mFillLineSymbol->setWidth( w );
2583 mLineWidth = w;
2584}
2585
2587{
2588 mFillLineSymbol->setColor( c );
2589 mColor = c;
2590}
2591
2593{
2594 return mFillLineSymbol ? mFillLineSymbol->color() : mColor;
2595}
2596
2598{
2599 if ( !symbol )
2600 {
2601 return false;
2602 }
2603
2604 if ( symbol->type() == Qgis::SymbolType::Line )
2605 {
2606 mFillLineSymbol.reset( qgis::down_cast<QgsLineSymbol *>( symbol ) );
2607 return true;
2608 }
2609 delete symbol;
2610 return false;
2611}
2612
2614{
2615 return mFillLineSymbol.get();
2616}
2617
2619{
2620 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2621 if ( mFillLineSymbol )
2622 attr.unite( mFillLineSymbol->usedAttributes( context ) );
2623 return attr;
2624}
2625
2627{
2629 return true;
2630 if ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
2631 return true;
2632 return false;
2633}
2634
2636{
2637 installMasks( context, true );
2638
2639 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2640}
2641
2643{
2644 removeMasks( context, true );
2645
2646 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2647}
2648
2650{
2651
2652 double lineAngleRads { qDegreesToRadians( mLineAngle ) };
2653 double distancePx { QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, {} ) };
2654
2655 QSize size { static_cast<int>( distancePx ), static_cast<int>( distancePx ) };
2656
2657 if ( static_cast<int>( mLineAngle ) % 90 != 0 )
2658 {
2659 size = QSize( static_cast<int>( distancePx / std::sin( lineAngleRads ) ), static_cast<int>( distancePx / std::cos( lineAngleRads ) ) );
2660 }
2661
2662 QPixmap pixmap( size );
2663 pixmap.fill( Qt::transparent );
2664 QPainter painter;
2665 painter.begin( &pixmap );
2666 painter.setRenderHint( QPainter::Antialiasing );
2667 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
2671 renderContext.setForceVectorOutput( true );
2672 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
2673
2674 std::unique_ptr< QgsLinePatternFillSymbolLayer > layerClone( clone() );
2675 layerClone->setOffset( 0 );
2676 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
2677 painter.end();
2678 return pixmap.toImage();
2679 return QImage();
2680}
2681
2683{
2684 return 0;
2685}
2686
2688{
2690 mDistanceUnit = unit;
2691 mLineWidthUnit = unit;
2692 mOffsetUnit = unit;
2693
2694 if ( mFillLineSymbol )
2695 mFillLineSymbol->setOutputUnit( unit );
2696}
2697
2699{
2701 if ( mDistanceUnit != unit || mLineWidthUnit != unit || ( mOffsetUnit != unit && mOffsetUnit != Qgis::RenderUnit::Percentage ) )
2702 {
2704 }
2705 return unit;
2706}
2707
2709{
2710 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
2711 || mLineWidthUnit == Qgis::RenderUnit::MapUnits || mLineWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2712 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
2713}
2714
2716{
2718 mDistanceMapUnitScale = scale;
2719 mLineWidthMapUnitScale = scale;
2720 mOffsetMapUnitScale = scale;
2721}
2722
2724{
2725 if ( QgsImageFillSymbolLayer::mapUnitScale() == mDistanceMapUnitScale &&
2726 mDistanceMapUnitScale == mLineWidthMapUnitScale &&
2727 mLineWidthMapUnitScale == mOffsetMapUnitScale )
2728 {
2729 return mDistanceMapUnitScale;
2730 }
2731 return QgsMapUnitScale();
2732}
2733
2735{
2736 std::unique_ptr< QgsLinePatternFillSymbolLayer > patternLayer = std::make_unique< QgsLinePatternFillSymbolLayer >();
2737
2738 //default values
2739 double lineAngle = 45;
2740 double distance = 5;
2741 double lineWidth = 0.5;
2742 QColor color( Qt::black );
2743 double offset = 0.0;
2744
2745 if ( properties.contains( QStringLiteral( "lineangle" ) ) )
2746 {
2747 //pre 2.5 projects used "lineangle"
2748 lineAngle = properties[QStringLiteral( "lineangle" )].toDouble();
2749 }
2750 else if ( properties.contains( QStringLiteral( "angle" ) ) )
2751 {
2752 lineAngle = properties[QStringLiteral( "angle" )].toDouble();
2753 }
2754 patternLayer->setLineAngle( lineAngle );
2755
2756 if ( properties.contains( QStringLiteral( "distance" ) ) )
2757 {
2758 distance = properties[QStringLiteral( "distance" )].toDouble();
2759 }
2760 patternLayer->setDistance( distance );
2761
2762 if ( properties.contains( QStringLiteral( "linewidth" ) ) )
2763 {
2764 //pre 2.5 projects used "linewidth"
2765 lineWidth = properties[QStringLiteral( "linewidth" )].toDouble();
2766 }
2767 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2768 {
2769 lineWidth = properties[QStringLiteral( "outline_width" )].toDouble();
2770 }
2771 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2772 {
2773 lineWidth = properties[QStringLiteral( "line_width" )].toDouble();
2774 }
2775 patternLayer->setLineWidth( lineWidth );
2776
2777 if ( properties.contains( QStringLiteral( "color" ) ) )
2778 {
2779 color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() );
2780 }
2781 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2782 {
2783 color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() );
2784 }
2785 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2786 {
2787 color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() );
2788 }
2789 patternLayer->setColor( color );
2790
2791 if ( properties.contains( QStringLiteral( "offset" ) ) )
2792 {
2793 offset = properties[QStringLiteral( "offset" )].toDouble();
2794 }
2795 patternLayer->setOffset( offset );
2796
2797
2798 if ( properties.contains( QStringLiteral( "distance_unit" ) ) )
2799 {
2800 patternLayer->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_unit" )].toString() ) );
2801 }
2802 if ( properties.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
2803 {
2804 patternLayer->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
2805 }
2806 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
2807 {
2808 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
2809 }
2810 else if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2811 {
2812 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2813 }
2814 if ( properties.contains( QStringLiteral( "line_width_map_unit_scale" ) ) )
2815 {
2816 patternLayer->setLineWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "line_width_map_unit_scale" )].toString() ) );
2817 }
2818 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
2819 {
2820 patternLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
2821 }
2822 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
2823 {
2824 patternLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
2825 }
2826 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2827 {
2828 patternLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2829 }
2830 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2831 {
2832 patternLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2833 }
2834 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
2835 {
2836 patternLayer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
2837 }
2838 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
2839 {
2840 patternLayer->setClipMode( QgsSymbolLayerUtils::decodeLineClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
2841 }
2842
2843 patternLayer->restoreOldDataDefinedProperties( properties );
2844
2845 return patternLayer.release();
2846}
2847
2849{
2850 return QStringLiteral( "LinePatternFill" );
2851}
2852
2853bool QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double lineAngle, double distance )
2854{
2855 mBrush.setTextureImage( QImage() ); // set empty in case we have to return
2856
2857 if ( !mFillLineSymbol )
2858 {
2859 return true;
2860 }
2861 // We have to make a copy because marker intervals will have to be adjusted
2862 std::unique_ptr< QgsLineSymbol > fillLineSymbol( mFillLineSymbol->clone() );
2863 if ( !fillLineSymbol )
2864 {
2865 return true;
2866 }
2867
2868 const QgsRenderContext &ctx = context.renderContext();
2869 //double strokePixelWidth = lineWidth * QgsSymbolLayerUtils::pixelSizeScaleFactor( ctx, mLineWidthUnit, mLineWidthMapUnitScale );
2870 double outputPixelDist = ctx.convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
2871 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDist * mOffset / 100
2872 : ctx.convertToPainterUnits( mOffset, mOffsetUnit, mOffsetMapUnitScale );
2873
2874 // NOTE: this may need to be modified if we ever change from a forced rasterized/brush approach,
2875 // because potentially we may want to allow vector based line pattern fills where the first line
2876 // is offset by a large distance
2877
2878 // fix truncated pattern with larger offsets
2879 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDist );
2880 if ( outputPixelOffset > outputPixelDist / 2.0 )
2881 outputPixelOffset -= outputPixelDist;
2882
2883 // To get all patterns into image, we have to consider symbols size (estimateMaxBleed()).
2884 // For marker lines we have to get markers interval.
2885 double outputPixelBleed = 0;
2886 double outputPixelInterval = 0; // maximum interval
2887 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2888 {
2889 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2890 double outputPixelLayerBleed = layer->estimateMaxBleed( context.renderContext() );
2891 outputPixelBleed = std::max( outputPixelBleed, outputPixelLayerBleed );
2892
2893 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2894 if ( markerLineLayer )
2895 {
2896 double outputPixelLayerInterval = ctx.convertToPainterUnits( markerLineLayer->interval(), markerLineLayer->intervalUnit(), markerLineLayer->intervalMapUnitScale() );
2897
2898 // There may be multiple marker lines with different intervals.
2899 // In theory we should find the least common multiple, but that could be too
2900 // big (multiplication of intervals in the worst case).
2901 // Because patterns without small common interval would look strange, we
2902 // believe that the longest interval should usually be sufficient.
2903 outputPixelInterval = std::max( outputPixelInterval, outputPixelLayerInterval );
2904 }
2905 }
2906
2907 if ( outputPixelInterval > 0 )
2908 {
2909 // We have to adjust marker intervals to integer pixel size to get
2910 // repeatable pattern.
2911 double intervalScale = std::round( outputPixelInterval ) / outputPixelInterval;
2912 outputPixelInterval = std::round( outputPixelInterval );
2913
2914 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2915 {
2916 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2917
2918 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2919 if ( markerLineLayer )
2920 {
2921 markerLineLayer->setInterval( intervalScale * markerLineLayer->interval() );
2922 }
2923 }
2924 }
2925
2926 //create image
2927 int height, width;
2928 lineAngle = std::fmod( lineAngle, 360 );
2929 if ( lineAngle < 0 )
2930 lineAngle += 360;
2931 if ( qgsDoubleNear( lineAngle, 0 ) || qgsDoubleNear( lineAngle, 360 ) || qgsDoubleNear( lineAngle, 180 ) )
2932 {
2933 height = outputPixelDist;
2934 width = outputPixelInterval > 0 ? outputPixelInterval : height;
2935 }
2936 else if ( qgsDoubleNear( lineAngle, 90 ) || qgsDoubleNear( lineAngle, 270 ) )
2937 {
2938 width = outputPixelDist;
2939 height = outputPixelInterval > 0 ? outputPixelInterval : width;
2940 }
2941 else
2942 {
2943 height = outputPixelDist / std::cos( lineAngle * M_PI / 180 ); //keep perpendicular distance between lines constant
2944 width = outputPixelDist / std::sin( lineAngle * M_PI / 180 );
2945
2946 // recalculate real angle and distance after rounding to pixels
2947 lineAngle = 180 * std::atan2( static_cast< double >( height ), static_cast< double >( width ) ) / M_PI;
2948 if ( lineAngle < 0 )
2949 {
2950 lineAngle += 360.;
2951 }
2952
2953 height = std::abs( height );
2954 width = std::abs( width );
2955
2956 outputPixelDist = std::abs( height * std::cos( lineAngle * M_PI / 180 ) );
2957
2958 // Round offset to correspond to one pixel height, otherwise lines may
2959 // be shifted on tile border if offset falls close to pixel center
2960 int offsetHeight = static_cast< int >( std::round( outputPixelOffset / std::cos( lineAngle * M_PI / 180 ) ) );
2961 outputPixelOffset = offsetHeight * std::cos( lineAngle * M_PI / 180 );
2962 }
2963
2964 //depending on the angle, we might need to render into a larger image and use a subset of it
2965 double dx = 0;
2966 double dy = 0;
2967
2968 // Add buffer based on bleed but keep precisely the height/width ratio (angle)
2969 // thus we add integer multiplications of width and height covering the bleed
2970 int bufferMulti = static_cast< int >( std::max( std::ceil( outputPixelBleed / width ), std::ceil( outputPixelBleed / width ) ) );
2971
2972 // Always buffer at least once so that center of line marker in upper right corner
2973 // does not fall outside due to representation error
2974 bufferMulti = std::max( bufferMulti, 1 );
2975
2976 int xBuffer = width * bufferMulti;
2977 int yBuffer = height * bufferMulti;
2978 int innerWidth = width;
2979 int innerHeight = height;
2980 width += 2 * xBuffer;
2981 height += 2 * yBuffer;
2982
2983 //protect from zero width/height image and symbol layer from eating too much memory
2984 if ( width > 2000 || height > 2000 || width == 0 || height == 0 )
2985 {
2986 return false;
2987 }
2988
2989 QImage patternImage( width, height, QImage::Format_ARGB32 );
2990 patternImage.fill( 0 );
2991
2992 QPointF p1, p2, p3, p4, p5, p6;
2993 if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
2994 {
2995 p1 = QPointF( 0, yBuffer );
2996 p2 = QPointF( width, yBuffer );
2997 p3 = QPointF( 0, yBuffer + innerHeight );
2998 p4 = QPointF( width, yBuffer + innerHeight );
2999 }
3000 else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
3001 {
3002 p1 = QPointF( xBuffer, height );
3003 p2 = QPointF( xBuffer, 0 );
3004 p3 = QPointF( xBuffer + innerWidth, height );
3005 p4 = QPointF( xBuffer + innerWidth, 0 );
3006 }
3007 else if ( lineAngle > 0 && lineAngle < 90 )
3008 {
3009 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3010 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3011 p1 = QPointF( 0, height );
3012 p2 = QPointF( width, 0 );
3013 p3 = QPointF( -dx, height - dy );
3014 p4 = QPointF( width - dx, -dy );
3015 p5 = QPointF( dx, height + dy );
3016 p6 = QPointF( width + dx, dy );
3017 }
3018 else if ( lineAngle > 180 && lineAngle < 270 )
3019 {
3020 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3021 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3022 p1 = QPointF( width, 0 );
3023 p2 = QPointF( 0, height );
3024 p3 = QPointF( width - dx, -dy );
3025 p4 = QPointF( -dx, height - dy );
3026 p5 = QPointF( width + dx, dy );
3027 p6 = QPointF( dx, height + dy );
3028 }
3029 else if ( lineAngle > 90 && lineAngle < 180 )
3030 {
3031 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3032 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3033 p1 = QPointF( 0, 0 );
3034 p2 = QPointF( width, height );
3035 p5 = QPointF( dx, -dy );
3036 p6 = QPointF( width + dx, height - dy );
3037 p3 = QPointF( -dx, dy );
3038 p4 = QPointF( width - dx, height + dy );
3039 }
3040 else if ( lineAngle > 270 && lineAngle < 360 )
3041 {
3042 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3043 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3044 p1 = QPointF( width, height );
3045 p2 = QPointF( 0, 0 );
3046 p5 = QPointF( width + dx, height - dy );
3047 p6 = QPointF( dx, -dy );
3048 p3 = QPointF( width - dx, height + dy );
3049 p4 = QPointF( -dx, dy );
3050 }
3051
3052 if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
3053 {
3054 QPointF tempPt;
3055 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
3056 p3 = QPointF( tempPt.x(), tempPt.y() );
3057 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
3058 p4 = QPointF( tempPt.x(), tempPt.y() );
3059 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
3060 p5 = QPointF( tempPt.x(), tempPt.y() );
3061 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
3062 p6 = QPointF( tempPt.x(), tempPt.y() );
3063
3064 //update p1, p2 last
3065 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
3066 p1 = QPointF( tempPt.x(), tempPt.y() );
3067 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
3068 p2 = QPointF( tempPt.x(), tempPt.y() );
3069 }
3070
3071 QPainter p( &patternImage );
3072
3073#if 0
3074 // DEBUG: Draw rectangle
3075 p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
3076 QPen pen( QColor( Qt::black ) );
3077 pen.setWidthF( 0.1 );
3078 pen.setCapStyle( Qt::FlatCap );
3079 p.setPen( pen );
3080
3081 // To see this rectangle, comment buffer cut below.
3082 // Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
3083 QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 );
3084 p.drawPolygon( polygon );
3085
3086 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 );
3087 p.drawPolygon( polygon );
3088#endif
3089
3090 // Use antialiasing because without antialiasing lines are rendered to the
3091 // right and below the mathematically defined points (not symmetrical)
3092 // and such tiles become useless for are filling
3093 p.setRenderHint( QPainter::Antialiasing, true );
3094
3095 // line rendering needs context for drawing on patternImage
3096 QgsRenderContext lineRenderContext;
3097 lineRenderContext.setPainter( &p );
3098 lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3100 lineRenderContext.setMapToPixel( mtp );
3101 lineRenderContext.setForceVectorOutput( false );
3102 lineRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3104 lineRenderContext.setDisabledSymbolLayersV2( context.renderContext().disabledSymbolLayersV2() );
3105
3106 fillLineSymbol->startRender( lineRenderContext, context.fields() );
3107
3108 QVector<QPolygonF> polygons;
3109 polygons.append( QPolygonF() << p1 << p2 );
3110 polygons.append( QPolygonF() << p3 << p4 );
3111 if ( !qgsDoubleNear( lineAngle, 0 ) && !qgsDoubleNear( lineAngle, 360 ) && !qgsDoubleNear( lineAngle, 90 ) && !qgsDoubleNear( lineAngle, 180 ) && !qgsDoubleNear( lineAngle, 270 ) )
3112 {
3113 polygons.append( QPolygonF() << p5 << p6 );
3114 }
3115
3116 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3117 for ( const QPolygonF &polygon : std::as_const( polygons ) )
3118 {
3119 fillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, useSelectedColor );
3120 }
3121
3122 fillLineSymbol->stopRender( lineRenderContext );
3123 p.end();
3124
3125 // Cut off the buffer
3126 patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
3127
3128 //set image to mBrush
3129 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3130 {
3131 QImage transparentImage = patternImage.copy();
3132 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3133 brush.setTextureImage( transparentImage );
3134 }
3135 else
3136 {
3137 brush.setTextureImage( patternImage );
3138 }
3139
3140 QTransform brushTransform;
3141 brush.setTransform( brushTransform );
3142
3143 return true;
3144}
3145
3147{
3148 // if we are using a vector based output, we need to render points as vectors
3149 // (OR if the line has data defined symbology, in which case we need to evaluate this line-by-line)
3150 mRenderUsingLines = context.renderContext().forceVectorOutput()
3151 || mFillLineSymbol->hasDataDefinedProperties()
3154
3155 if ( !mRenderUsingLines )
3156 {
3157 // optimised render for screen only, use image based brush
3158 // (fallback to line rendering when pattern image will result in too large a memory footprint)
3159 mRenderUsingLines = !applyPattern( context, mBrush, mLineAngle, mDistance );
3160 }
3161
3162 if ( mRenderUsingLines )
3163 {
3164 if ( mFillLineSymbol )
3165 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3166 }
3167}
3168
3170{
3171 if ( mRenderUsingLines && mFillLineSymbol )
3172 {
3173 mFillLineSymbol->stopRender( context.renderContext() );
3174 }
3175}
3176
3177void QgsLinePatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3178{
3179 if ( !mRenderUsingLines )
3180 {
3181 // use image based brush for speed
3182 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3183 return;
3184 }
3185
3186 // vector based output - so draw line by line!
3187 QPainter *p = context.renderContext().painter();
3188 if ( !p )
3189 {
3190 return;
3191 }
3192
3193 double lineAngle = mLineAngle;
3195 {
3196 context.setOriginalValueVariable( mLineAngle );
3198 }
3199
3200 double distance = mDistance;
3202 {
3203 context.setOriginalValueVariable( mDistance );
3205 }
3206 const double outputPixelDistance = context.renderContext().convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
3207
3208 double offset = mOffset;
3209 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDistance * offset / 100
3210 : context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3211
3212 // fix truncated pattern with larger offsets
3213 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDistance );
3214 if ( outputPixelOffset > outputPixelDistance / 2.0 )
3215 outputPixelOffset -= outputPixelDistance;
3216
3217 p->setPen( QPen( Qt::NoPen ) );
3218
3219 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3220 if ( useSelectedColor )
3221 {
3222 QColor selColor = context.renderContext().selectionColor();
3223 p->setBrush( QBrush( selColor ) );
3224 _renderPolygon( p, points, rings, context );
3225 }
3226
3227 // if invalid parameters, skip out
3228 if ( qgsDoubleNear( distance, 0 ) )
3229 return;
3230
3231 p->save();
3232
3233 Qgis::LineClipMode clipMode = mClipMode;
3235 {
3237 bool ok = false;
3238 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyLineClipping, context.renderContext().expressionContext(), QString(), &ok );
3239 if ( ok )
3240 {
3241 Qgis::LineClipMode decodedMode = QgsSymbolLayerUtils::decodeLineClipMode( valueString, &ok );
3242 if ( ok )
3243 clipMode = decodedMode;
3244 }
3245 }
3246
3247 std::unique_ptr< QgsPolygon > shapePolygon;
3248 std::unique_ptr< QgsGeometryEngine > shapeEngine;
3249 switch ( clipMode )
3250 {
3252 break;
3253
3255 {
3256 shapePolygon = std::make_unique< QgsPolygon >();
3257 shapePolygon->setExteriorRing( QgsLineString::fromQPolygonF( points ) );
3258 if ( rings )
3259 {
3260 for ( const QPolygonF &ring : *rings )
3261 {
3262 shapePolygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
3263 }
3264 }
3265 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3266 shapeEngine->prepareGeometry();
3267 break;
3268 }
3269
3271 {
3272 QPainterPath path;
3273 path.addPolygon( points );
3274 if ( rings )
3275 {
3276 for ( const QPolygonF &ring : *rings )
3277 {
3278 path.addPolygon( ring );
3279 }
3280 }
3281 p->setClipPath( path, Qt::IntersectClip );
3282 break;
3283 }
3284 }
3285
3286 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3287 const QRectF boundingRect = points.boundingRect();
3288
3289 QTransform invertedRotateTransform;
3290 double left;
3291 double top;
3292 double right;
3293 double bottom;
3294
3295 QTransform transform;
3296 if ( applyBrushTransform )
3297 {
3298 // rotation applies around center of feature
3299 transform.translate( -boundingRect.center().x(),
3300 -boundingRect.center().y() );
3301 transform.rotate( lineAngle );
3302 transform.translate( boundingRect.center().x(),
3303 boundingRect.center().y() );
3304 }
3305 else
3306 {
3307 // rotation applies around top of viewport
3308 transform.rotate( lineAngle );
3309 }
3310
3311 const QRectF transformedBounds = transform.map( points ).boundingRect();
3312
3313 // bounds are expanded out a bit to account for maximum line width
3314 const double buffer = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFillLineSymbol.get(), context.renderContext() );
3315 left = transformedBounds.left() - buffer * 2;
3316 top = transformedBounds.top() - buffer * 2;
3317 right = transformedBounds.right() + buffer * 2;
3318 bottom = transformedBounds.bottom() + buffer * 2;
3319 invertedRotateTransform = transform.inverted();
3320
3321 if ( !applyBrushTransform )
3322 {
3323 top -= transformedBounds.top() - ( outputPixelDistance * std::floor( transformedBounds.top() / outputPixelDistance ) );
3324 }
3325
3327 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
3328 const bool needsExpressionContext = mFillLineSymbol->hasDataDefinedProperties();
3329
3330 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3332
3333 int currentLine = 0;
3334 for ( double currentY = top; currentY <= bottom; currentY += outputPixelDistance )
3335 {
3336 if ( context.renderContext().renderingStopped() )
3337 break;
3338
3339 if ( needsExpressionContext )
3340 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_line_number" ), ++currentLine, true ) );
3341
3342 double x1 = left;
3343 double y1 = currentY;
3344 double x2 = left;
3345 double y2 = currentY;
3346 invertedRotateTransform.map( left, currentY - outputPixelOffset, &x1, &y1 );
3347 invertedRotateTransform.map( right, currentY - outputPixelOffset, &x2, &y2 );
3348
3349 if ( shapeEngine )
3350 {
3351 QgsLineString ls( QgsPoint( x1, y1 ), QgsPoint( x2, y2 ) );
3352 std::unique_ptr< QgsAbstractGeometry > intersection( shapeEngine->intersection( &ls ) );
3353 for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
3354 {
3355 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( *it ) )
3356 {
3357 mFillLineSymbol->renderPolyline( ls->asQPolygonF(), context.feature(), context.renderContext() );
3358 }
3359 }
3360 }
3361 else
3362 {
3363 mFillLineSymbol->renderPolyline( QPolygonF() << QPointF( x1, y1 ) << QPointF( x2, y2 ), context.feature(), context.renderContext() );
3364 }
3365 }
3366
3367 p->restore();
3368
3370}
3371
3373{
3374 QVariantMap map = QgsImageFillSymbolLayer::properties();
3375 map.insert( QStringLiteral( "angle" ), QString::number( mLineAngle ) );
3376 map.insert( QStringLiteral( "distance" ), QString::number( mDistance ) );
3377 map.insert( QStringLiteral( "line_width" ), QString::number( mLineWidth ) );
3378 map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
3379 map.insert( QStringLiteral( "offset" ), QString::number( mOffset ) );
3380 map.insert( QStringLiteral( "distance_unit" ), QgsUnitTypes::encodeUnit( mDistanceUnit ) );
3381 map.insert( QStringLiteral( "line_width_unit" ), QgsUnitTypes::encodeUnit( mLineWidthUnit ) );
3382 map.insert( QStringLiteral( "offset_unit" ), QgsUnitTypes::encodeUnit( mOffsetUnit ) );
3383 map.insert( QStringLiteral( "distance_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale ) );
3384 map.insert( QStringLiteral( "line_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineWidthMapUnitScale ) );
3385 map.insert( QStringLiteral( "offset_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ) );
3386 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3387 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3388 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeLineClipMode( mClipMode ) );
3389 return map;
3390}
3391
3393{
3395 if ( mFillLineSymbol )
3396 {
3397 clonedLayer->setSubSymbol( mFillLineSymbol->clone() );
3398 }
3399 copyPaintEffect( clonedLayer );
3400 copyDataDefinedProperties( clonedLayer );
3401 return clonedLayer;
3402}
3403
3404void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3405{
3406 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
3407 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
3408 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
3409 element.appendChild( symbolizerElem );
3410
3411 // <Geometry>
3412 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
3413
3414 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
3415 symbolizerElem.appendChild( fillElem );
3416
3417 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
3418 fillElem.appendChild( graphicFillElem );
3419
3420 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
3421 graphicFillElem.appendChild( graphicElem );
3422
3423 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
3424
3425 // Export to PNG (TODO: SVG)
3426 bool exportOk { false };
3427 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
3428 {
3429 const QImage image { toTiledPatternImage() };
3430 if ( ! image.isNull() )
3431 {
3432 const QFileInfo info { context.exportFilePath() };
3433 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
3434 pngPath = QgsFileUtils::uniquePath( pngPath );
3435 image.save( pngPath );
3436 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
3437 exportOk = true;
3438 }
3439 }
3440
3441 if ( ! exportOk )
3442 {
3443 //line properties must be inside the graphic definition
3444 QColor lineColor = mFillLineSymbol ? mFillLineSymbol->color() : QColor();
3445 double lineWidth = mFillLineSymbol ? mFillLineSymbol->width() : 0.0;
3446 lineWidth = QgsSymbolLayerUtils::rescaleUom( lineWidth, mLineWidthUnit, props );
3447 double distance = QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, props );
3448 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "horline" ), QColor(), lineColor, Qt::SolidLine, lineWidth, distance );
3449
3450 // <Rotation>
3451 QString angleFunc;
3452 bool ok;
3453 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
3454 if ( !ok )
3455 {
3456 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mLineAngle );
3457 }
3458 else if ( !qgsDoubleNear( angle + mLineAngle, 0.0 ) )
3459 {
3460 angleFunc = QString::number( angle + mLineAngle );
3461 }
3462 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
3463
3464 // <se:Displacement>
3465 QPointF lineOffset( std::sin( mLineAngle ) * mOffset, std::cos( mLineAngle ) * mOffset );
3466 lineOffset = QgsSymbolLayerUtils::rescaleUom( lineOffset, mOffsetUnit, props );
3467 QgsSymbolLayerUtils::createDisplacementElement( doc, graphicElem, lineOffset );
3468 }
3469}
3470
3471QString QgsLinePatternFillSymbolLayer::ogrFeatureStyleWidth( double widthScaleFactor ) const
3472{
3473 QString featureStyle;
3474 featureStyle.append( "Brush(" );
3475 featureStyle.append( QStringLiteral( "fc:%1" ).arg( mColor.name() ) );
3476 featureStyle.append( QStringLiteral( ",bc:%1" ).arg( QLatin1String( "#00000000" ) ) ); //transparent background
3477 featureStyle.append( ",id:\"ogr-brush-2\"" );
3478 featureStyle.append( QStringLiteral( ",a:%1" ).arg( mLineAngle ) );
3479 featureStyle.append( QStringLiteral( ",s:%1" ).arg( mLineWidth * widthScaleFactor ) );
3480 featureStyle.append( ",dx:0mm" );
3481 featureStyle.append( QStringLiteral( ",dy:%1mm" ).arg( mDistance * widthScaleFactor ) );
3482 featureStyle.append( ')' );
3483 return featureStyle;
3484}
3485
3487{
3489 && ( !mFillLineSymbol || !mFillLineSymbol->hasDataDefinedProperties() ) )
3490 {
3491 return; //no data defined settings
3492 }
3493
3494 double lineAngle = mLineAngle;
3496 {
3497 context.setOriginalValueVariable( mLineAngle );
3499 }
3500 double distance = mDistance;
3502 {
3503 context.setOriginalValueVariable( mDistance );
3505 }
3506 applyPattern( context, mBrush, lineAngle, distance );
3507}
3508
3510{
3511 QString name;
3512 QColor fillColor, lineColor;
3513 double size, lineWidth;
3514 Qt::PenStyle lineStyle;
3515
3516 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
3517 if ( fillElem.isNull() )
3518 return nullptr;
3519
3520 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
3521 if ( graphicFillElem.isNull() )
3522 return nullptr;
3523
3524 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
3525 if ( graphicElem.isNull() )
3526 return nullptr;
3527
3528 if ( !QgsSymbolLayerUtils::wellKnownMarkerFromSld( graphicElem, name, fillColor, lineColor, lineStyle, lineWidth, size ) )
3529 return nullptr;
3530
3531 if ( name != QLatin1String( "horline" ) )
3532 return nullptr;
3533
3534 double angle = 0.0;
3535 QString angleFunc;
3536 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3537 {
3538 bool ok;
3539 double d = angleFunc.toDouble( &ok );
3540 if ( ok )
3541 angle = d;
3542 }
3543
3544 double offset = 0.0;
3545 QPointF vectOffset;
3546 if ( QgsSymbolLayerUtils::displacementFromSldElement( graphicElem, vectOffset ) )
3547 {
3548 offset = std::sqrt( std::pow( vectOffset.x(), 2 ) + std::pow( vectOffset.y(), 2 ) );
3549 }
3550
3551 double scaleFactor = 1.0;
3552 const QString uom = element.attribute( QStringLiteral( "uom" ) );
3553 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
3554 size = size * scaleFactor;
3555 lineWidth = lineWidth * scaleFactor;
3556
3557 std::unique_ptr< QgsLinePatternFillSymbolLayer > sl = std::make_unique< QgsLinePatternFillSymbolLayer >();
3558 sl->setOutputUnit( sldUnitSize );
3559 sl->setColor( lineColor );
3560 sl->setLineWidth( lineWidth );
3561 sl->setLineAngle( angle );
3562 sl->setOffset( offset );
3563 sl->setDistance( size );
3564
3565 // try to get the stroke
3566 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
3567 if ( !strokeElem.isNull() )
3568 {
3570 if ( l )
3571 {
3572 QgsSymbolLayerList layers;
3573 layers.append( l );
3574 sl->setSubSymbol( new QgsLineSymbol( layers ) );
3575 }
3576 }
3577
3578 return sl.release();
3579}
3580
3581
3583
3586{
3587 mMarkerSymbol = std::make_unique<QgsMarkerSymbol>();
3588 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
3589}
3590
3592
3594{
3596 mDistanceXUnit = unit;
3597 mDistanceYUnit = unit;
3598 // don't change "percentage" units -- since they adapt directly to whatever other unit is set
3600 mDisplacementXUnit = unit;
3602 mDisplacementYUnit = unit;
3604 mOffsetXUnit = unit;
3606 mOffsetYUnit = unit;
3608 mRandomDeviationXUnit = unit;
3610 mRandomDeviationYUnit = unit;
3611
3612 if ( mMarkerSymbol )
3613 {
3614 mMarkerSymbol->setOutputUnit( unit );
3615 }
3616}
3617
3634
3646
3659
3675
3677{
3678 std::unique_ptr< QgsPointPatternFillSymbolLayer > layer = std::make_unique< QgsPointPatternFillSymbolLayer >();
3679 if ( properties.contains( QStringLiteral( "distance_x" ) ) )
3680 {
3681 layer->setDistanceX( properties[QStringLiteral( "distance_x" )].toDouble() );
3682 }
3683 if ( properties.contains( QStringLiteral( "distance_y" ) ) )
3684 {
3685 layer->setDistanceY( properties[QStringLiteral( "distance_y" )].toDouble() );
3686 }
3687 if ( properties.contains( QStringLiteral( "displacement_x" ) ) )
3688 {
3689 layer->setDisplacementX( properties[QStringLiteral( "displacement_x" )].toDouble() );
3690 }
3691 if ( properties.contains( QStringLiteral( "displacement_y" ) ) )
3692 {
3693 layer->setDisplacementY( properties[QStringLiteral( "displacement_y" )].toDouble() );
3694 }
3695 if ( properties.contains( QStringLiteral( "offset_x" ) ) )
3696 {
3697 layer->setOffsetX( properties[QStringLiteral( "offset_x" )].toDouble() );
3698 }
3699 if ( properties.contains( QStringLiteral( "offset_y" ) ) )
3700 {
3701 layer->setOffsetY( properties[QStringLiteral( "offset_y" )].toDouble() );
3702 }
3703
3704 if ( properties.contains( QStringLiteral( "distance_x_unit" ) ) )
3705 {
3706 layer->setDistanceXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_x_unit" )].toString() ) );
3707 }
3708 if ( properties.contains( QStringLiteral( "distance_x_map_unit_scale" ) ) )
3709 {
3710 layer->setDistanceXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_x_map_unit_scale" )].toString() ) );
3711 }
3712 if ( properties.contains( QStringLiteral( "distance_y_unit" ) ) )
3713 {
3714 layer->setDistanceYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_y_unit" )].toString() ) );
3715 }
3716 if ( properties.contains( QStringLiteral( "distance_y_map_unit_scale" ) ) )
3717 {
3718 layer->setDistanceYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_y_map_unit_scale" )].toString() ) );
3719 }
3720 if ( properties.contains( QStringLiteral( "displacement_x_unit" ) ) )
3721 {
3722 layer->setDisplacementXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_x_unit" )].toString() ) );
3723 }
3724 if ( properties.contains( QStringLiteral( "displacement_x_map_unit_scale" ) ) )
3725 {
3726 layer->setDisplacementXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_x_map_unit_scale" )].toString() ) );
3727 }
3728 if ( properties.contains( QStringLiteral( "displacement_y_unit" ) ) )
3729 {
3730 layer->setDisplacementYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_y_unit" )].toString() ) );
3731 }
3732 if ( properties.contains( QStringLiteral( "displacement_y_map_unit_scale" ) ) )
3733 {
3734 layer->setDisplacementYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_y_map_unit_scale" )].toString() ) );
3735 }
3736 if ( properties.contains( QStringLiteral( "offset_x_unit" ) ) )
3737 {
3738 layer->setOffsetXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_x_unit" )].toString() ) );
3739 }
3740 if ( properties.contains( QStringLiteral( "offset_x_map_unit_scale" ) ) )
3741 {
3742 layer->setOffsetXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_x_map_unit_scale" )].toString() ) );
3743 }
3744 if ( properties.contains( QStringLiteral( "offset_y_unit" ) ) )
3745 {
3746 layer->setOffsetYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_y_unit" )].toString() ) );
3747 }
3748 if ( properties.contains( QStringLiteral( "offset_y_map_unit_scale" ) ) )
3749 {
3750 layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_y_map_unit_scale" )].toString() ) );
3751 }
3752
3753 if ( properties.contains( QStringLiteral( "random_deviation_x" ) ) )
3754 {
3755 layer->setMaximumRandomDeviationX( properties[QStringLiteral( "random_deviation_x" )].toDouble() );
3756 }
3757 if ( properties.contains( QStringLiteral( "random_deviation_y" ) ) )
3758 {
3759 layer->setMaximumRandomDeviationY( properties[QStringLiteral( "random_deviation_y" )].toDouble() );
3760 }
3761 if ( properties.contains( QStringLiteral( "random_deviation_x_unit" ) ) )
3762 {
3763 layer->setRandomDeviationXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_x_unit" )].toString() ) );
3764 }
3765 if ( properties.contains( QStringLiteral( "random_deviation_x_map_unit_scale" ) ) )
3766 {
3767 layer->setRandomDeviationXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_x_map_unit_scale" )].toString() ) );
3768 }
3769 if ( properties.contains( QStringLiteral( "random_deviation_y_unit" ) ) )
3770 {
3771 layer->setRandomDeviationYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_y_unit" )].toString() ) );
3772 }
3773 if ( properties.contains( QStringLiteral( "random_deviation_y_map_unit_scale" ) ) )
3774 {
3775 layer->setRandomDeviationYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_y_map_unit_scale" )].toString() ) );
3776 }
3777 unsigned long seed = 0;
3778 if ( properties.contains( QStringLiteral( "seed" ) ) )
3779 seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
3780 else
3781 {
3782 // if we a creating a new point pattern fill from scratch, we default to a random seed
3783 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
3784 std::random_device rd;
3785 std::mt19937 mt( seed == 0 ? rd() : seed );
3786 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
3787 seed = uniformDist( mt );
3788 }
3789 layer->setSeed( seed );
3790
3791 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
3792 {
3793 layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
3794 }
3795 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
3796 {
3797 layer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
3798 }
3799 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
3800 {
3801 layer->setClipMode( QgsSymbolLayerUtils::decodeMarkerClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
3802 }
3803 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
3804 {
3805 layer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
3806 }
3807
3808 if ( properties.contains( QStringLiteral( "angle" ) ) )
3809 {
3810 layer->setAngle( properties[QStringLiteral( "angle" )].toDouble() );
3811 }
3812
3813 layer->restoreOldDataDefinedProperties( properties );
3814
3815 return layer.release();
3816}
3817
3819{
3820 return QStringLiteral( "PointPatternFill" );
3821}
3822
3823bool QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double distanceX, double distanceY,
3824 double displacementX, double displacementY, double offsetX, double offsetY )
3825{
3826 //render 3 rows and columns in one go to easily incorporate displacement
3827 const QgsRenderContext &ctx = context.renderContext();
3830
3831 double widthOffset = std::fmod(
3832 mOffsetXUnit == Qgis::RenderUnit::Percentage ? ( width * offsetX / 200 ) : ctx.convertToPainterUnits( offsetX, mOffsetXUnit, mOffsetXMapUnitScale ),
3833 width );
3834 double heightOffset = std::fmod(
3835 mOffsetYUnit == Qgis::RenderUnit::Percentage ? ( height * offsetY / 200 ) : ctx.convertToPainterUnits( offsetY, mOffsetYUnit, mOffsetYMapUnitScale ),
3836 height );
3837
3838 if ( width > 2000 || height > 2000 ) //protect symbol layer from eating too much memory
3839 {
3840 brush.setTextureImage( QImage() );
3841 return false;
3842 }
3843
3844 QImage patternImage( width, height, QImage::Format_ARGB32 );
3845 patternImage.fill( 0 );
3846 if ( patternImage.isNull() )
3847 {
3848 brush.setTextureImage( QImage() );
3849 return false;
3850 }
3851 if ( mMarkerSymbol )
3852 {
3853 QPainter p( &patternImage );
3854
3855 //marker rendering needs context for drawing on patternImage
3856 QgsRenderContext pointRenderContext;
3857 pointRenderContext.setRendererScale( context.renderContext().rendererScale() );
3858 pointRenderContext.setPainter( &p );
3859 pointRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3860
3863 pointRenderContext.setMapToPixel( mtp );
3864 pointRenderContext.setForceVectorOutput( false );
3865 pointRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3867
3868 mMarkerSymbol->startRender( pointRenderContext, context.fields() );
3869
3870 //render points on distance grid
3871 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3872 {
3873 for ( double currentY = -height; currentY <= height * 2.0; currentY += height )
3874 {
3875 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset, currentY + heightOffset ), context.feature(), pointRenderContext );
3876 }
3877 }
3878
3879 //render displaced points
3880 double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
3881 ? ( width * displacementX / 200 )
3882 : ctx.convertToPainterUnits( displacementX, mDisplacementXUnit, mDisplacementXMapUnitScale );
3883 double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
3884 ? ( height * displacementY / 200 )
3885 : ctx.convertToPainterUnits( displacementY, mDisplacementYUnit, mDisplacementYMapUnitScale );
3886 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3887 {
3888 for ( double currentY = -height / 2.0; currentY <= height * 2.0; currentY += height )
3889 {
3890 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + displacementPixelX, currentY + heightOffset ), context.feature(), pointRenderContext );
3891 }
3892 }
3893
3894 for ( double currentX = -width / 2.0; currentX <= width * 2.0; currentX += width )
3895 {
3896 for ( double currentY = -height; currentY <= height * 2.0; currentY += height / 2.0 )
3897 {
3898 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + ( std::fmod( currentY, height ) != 0 ? displacementPixelX : 0 ), currentY + heightOffset - displacementPixelY ), context.feature(), pointRenderContext );
3899 }
3900 }
3901
3902 mMarkerSymbol->stopRender( pointRenderContext );
3903 }
3904
3905 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3906 {
3907 QImage transparentImage = patternImage.copy();
3908 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3909 brush.setTextureImage( transparentImage );
3910 }
3911 else
3912 {
3913 brush.setTextureImage( patternImage );
3914 }
3915 QTransform brushTransform;
3916 brush.setTransform( brushTransform );
3917
3918 return true;
3919}
3920
3922{
3923 // if we are using a vector based output, we need to render points as vectors
3924 // (OR if the marker has data defined symbology, in which case we need to evaluate this point-by-point)
3925 mRenderUsingMarkers = context.renderContext().forceVectorOutput()
3926 || mMarkerSymbol->hasDataDefinedProperties()
3930 || mClipMode != Qgis::MarkerClipMode::Shape
3933 || !qgsDoubleNear( mAngle, 0 )
3935
3936 if ( !mRenderUsingMarkers )
3937 {
3938 // optimised render for screen only, use image based brush
3939 // (fallback to line rendering when pattern image will result in too large a memory footprint)
3940 mRenderUsingMarkers = !applyPattern( context, mBrush, mDistanceX, mDistanceY, mDisplacementX, mDisplacementY, mOffsetX, mOffsetY );
3941 }
3942
3943 if ( mRenderUsingMarkers )
3944 {
3945 mMarkerSymbol->startRender( context.renderContext() );
3946 }
3947}
3948
3950{
3951 if ( mRenderUsingMarkers )
3952 {
3953 mMarkerSymbol->stopRender( context.renderContext() );
3954 }
3955}
3956
3958{
3959 installMasks( context, true );
3960
3961 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3962 // Otherwise generators used in the subsymbol will only render a single point per feature (they
3963 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
3964}
3965
3967{
3968 removeMasks( context, true );
3969
3970 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3971 // Otherwise generators used in the subsymbol will only render a single point per feature (they
3972 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
3973}
3974
3975void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3976{
3977 if ( !mRenderUsingMarkers )
3978 {
3979 // use image based brush for speed
3980 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3981 return;
3982 }
3983
3984 // vector based output - so draw dot by dot!
3985 QPainter *p = context.renderContext().painter();
3986 if ( !p )
3987 {
3988 return;
3989 }
3990
3991 double angle = mAngle;
3993 {
3996 }
3997
3998 double distanceX = mDistanceX;
4000 {
4003 }
4005
4006 double distanceY = mDistanceY;
4008 {
4011 }
4013
4014 double offsetX = mOffsetX;
4016 {
4019 }
4020 const double widthOffset = std::fmod(
4022 ? ( offsetX * width / 100 )
4024 width );
4025
4026 double offsetY = mOffsetY;
4028 {
4031 }
4032 const double heightOffset = std::fmod(
4034 ? ( offsetY * height / 100 )
4036 height );
4037
4040 {
4043 }
4044 const double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
4045 ? ( displacementX * width / 100 )
4047
4050 {
4053 }
4054 const double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
4055 ? ( displacementY * height / 100 )
4057
4058 p->setPen( QPen( Qt::NoPen ) );
4059
4060 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4061 if ( useSelectedColor )
4062 {
4063 QColor selColor = context.renderContext().selectionColor();
4064 p->setBrush( QBrush( selColor ) );
4065 _renderPolygon( p, points, rings, context );
4066 }
4067
4068 // if invalid parameters, skip out
4069 if ( qgsDoubleNear( width, 0 ) || qgsDoubleNear( height, 0 ) || width < 0 || height < 0 )
4070 return;
4071
4072 p->save();
4073
4074 Qgis::MarkerClipMode clipMode = mClipMode;
4076 {
4078 bool ok = false;
4079 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyMarkerClipping, context.renderContext().expressionContext(), QString(), &ok );
4080 if ( ok )
4081 {
4082 Qgis::MarkerClipMode decodedMode = QgsSymbolLayerUtils::decodeMarkerClipMode( valueString, &ok );
4083 if ( ok )
4084 clipMode = decodedMode;
4085 }
4086 }
4087
4088 std::unique_ptr< QgsPolygon > shapePolygon;
4089 std::unique_ptr< QgsGeometryEngine > shapeEngine;
4090 switch ( clipMode )
4091 {
4095 {
4096 shapePolygon = std::make_unique< QgsPolygon >();
4097 shapePolygon->setExteriorRing( QgsLineString::fromQPolygonF( points ) );
4098 if ( rings )
4099 {
4100 for ( const QPolygonF &ring : *rings )
4101 {
4102 shapePolygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
4103 }
4104 }
4105 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
4106 shapeEngine->prepareGeometry();
4107 break;
4108 }
4109
4111 {
4112 QPainterPath path;
4113 path.addPolygon( points );
4114 if ( rings )
4115 {
4116 for ( const QPolygonF &ring : *rings )
4117 {
4118 path.addPolygon( ring );
4119 }
4120 }
4121 p->setClipPath( path, Qt::IntersectClip );
4122 break;
4123 }
4124 }
4125
4126 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
4127 const QRectF boundingRect = points.boundingRect();
4128
4129 QTransform invertedRotateTransform;
4130 double left;
4131 double top;
4132 double right;
4133 double bottom;
4134
4135 if ( !qgsDoubleNear( angle, 0 ) )
4136 {
4137 QTransform transform;
4138 if ( applyBrushTransform )
4139 {
4140 // rotation applies around center of feature
4141 transform.translate( -boundingRect.center().x(),
4142 -boundingRect.center().y() );
4143 transform.rotate( -angle );
4144 transform.translate( boundingRect.center().x(),
4145 boundingRect.center().y() );
4146 }
4147 else
4148 {
4149 // rotation applies around top of viewport
4150 transform.rotate( -angle );
4151 }
4152
4153 const QRectF transformedBounds = transform.map( points ).boundingRect();
4154 left = transformedBounds.left() - 2 * width;
4155 top = transformedBounds.top() - 2 * height;
4156 right = transformedBounds.right() + 2 * width;
4157 bottom = transformedBounds.bottom() + 2 * height;
4158 invertedRotateTransform = transform.inverted();
4159
4160 if ( !applyBrushTransform )
4161 {
4162 left -= transformedBounds.left() - ( width * std::floor( transformedBounds.left() / width ) );
4163 top -= transformedBounds.top() - ( height * std::floor( transformedBounds.top() / height ) );
4164 }
4165 }
4166 else
4167 {
4168 left = boundingRect.left() - 2 * width;
4169 top = boundingRect.top() - 2 * height;
4170 right = boundingRect.right() + 2 * width;
4171 bottom = boundingRect.bottom() + 2 * height;
4172
4173 if ( !applyBrushTransform )
4174 {
4175 left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
4176 top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
4177 }
4178 }
4179
4180 unsigned long seed = mSeed;
4182 {
4183 context.renderContext().expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
4185 }
4186
4187 double maxRandomDeviationX = mRandomDeviationX;
4189 {
4190 context.setOriginalValueVariable( maxRandomDeviationX );
4191 maxRandomDeviationX = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetX, context.renderContext().expressionContext(), maxRandomDeviationX );
4192 }
4193 const double maxRandomDeviationPixelX = mRandomDeviationXUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationX * width / 100 )
4195
4196 double maxRandomDeviationY = mRandomDeviationY;
4198 {
4199 context.setOriginalValueVariable( maxRandomDeviationY );
4200 maxRandomDeviationY = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetY, context.renderContext().expressionContext(), maxRandomDeviationY );
4201 }
4202 const double maxRandomDeviationPixelY = mRandomDeviationYUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationY * height / 100 )
4204
4205 std::random_device rd;
4206 std::mt19937 mt( seed == 0 ? rd() : seed );
4207 std::uniform_real_distribution<> uniformDist( 0, 1 );
4208 const bool useRandomShift = !qgsDoubleNear( maxRandomDeviationPixelX, 0 ) || !qgsDoubleNear( maxRandomDeviationPixelY, 0 );
4209
4211 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
4212 int pointNum = 0;
4213 const bool needsExpressionContext = mMarkerSymbol->hasDataDefinedProperties();
4214
4215 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4217
4218 const double prevOpacity = mMarkerSymbol->opacity();
4219 mMarkerSymbol->setOpacity( mMarkerSymbol->opacity() * context.opacity() );
4220
4221 bool alternateColumn = false;
4222 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
4223 for ( double currentX = left; currentX <= right; currentX += width, alternateColumn = !alternateColumn )
4224 {
4225 if ( context.renderContext().renderingStopped() )
4226 break;
4227
4228 if ( needsExpressionContext )
4229 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_column" ), ++currentCol, true ) );
4230
4231 bool alternateRow = false;
4232 const double columnX = currentX + widthOffset;
4233 int currentRow = -3;
4234 for ( double currentY = top; currentY <= bottom; currentY += height, alternateRow = !alternateRow )
4235 {
4236 if ( context.renderContext().renderingStopped() )
4237 break;
4238
4239 double y = currentY + heightOffset;
4240 double x = columnX;
4241 if ( alternateRow )
4242 x += displacementPixelX;
4243
4244 if ( !alternateColumn )
4245 y -= displacementPixelY;
4246
4247 if ( !qgsDoubleNear( angle, 0 ) )
4248 {
4249 double xx = x;
4250 double yy = y;
4251 invertedRotateTransform.map( xx, yy, &x, &y );
4252 }
4253
4254 if ( useRandomShift )
4255 {
4256 x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
4257 y += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelY;
4258 }
4259
4260 if ( needsExpressionContext )
4261 {
4263 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_row" ), ++currentRow, true ) );
4264 }
4265
4266 if ( shapeEngine )
4267 {
4268 bool renderPoint = true;
4269 switch ( clipMode )
4270 {
4272 {
4273 // 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
4274 const QgsRectangle markerRect = QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) );
4275 QgsPoint p( markerRect.center() );
4276 renderPoint = shapeEngine->intersects( &p );
4277 break;
4278 }
4279
4282 {
4283 const QgsGeometry markerBounds = QgsGeometry::fromRect( QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) ) );
4284
4286 renderPoint = shapeEngine->contains( markerBounds.constGet() );
4287 else
4288 renderPoint = shapeEngine->intersects( markerBounds.constGet() );
4289 break;
4290 }
4291
4293 break;
4294 }
4295
4296 if ( !renderPoint )
4297 continue;
4298 }
4299
4300 mMarkerSymbol->renderPoint( QPointF( x, y ), context.feature(), context.renderContext() );
4301 }
4302 }
4303
4304 mMarkerSymbol->setOpacity( prevOpacity );
4305
4306 p->restore();
4307
4309}
4310
4312{
4313 QVariantMap map = QgsImageFillSymbolLayer::properties();
4314 map.insert( QStringLiteral( "distance_x" ), QString::number( mDistanceX ) );
4315 map.insert( QStringLiteral( "distance_y" ), QString::number( mDistanceY ) );
4316 map.insert( QStringLiteral( "displacement_x" ), QString::number( mDisplacementX ) );
4317 map.insert( QStringLiteral( "displacement_y" ), QString::number( mDisplacementY ) );
4318 map.insert( QStringLiteral( "offset_x" ), QString::number( mOffsetX ) );
4319 map.insert( QStringLiteral( "offset_y" ), QString::number( mOffsetY ) );
4320 map.insert( QStringLiteral( "distance_x_unit" ), QgsUnitTypes::encodeUnit( mDistanceXUnit ) );
4321 map.insert( QStringLiteral( "distance_y_unit" ), QgsUnitTypes::encodeUnit( mDistanceYUnit ) );
4322 map.insert( QStringLiteral( "displacement_x_unit" ), QgsUnitTypes::encodeUnit( mDisplacementXUnit ) );
4323 map.insert( QStringLiteral( "displacement_y_unit" ), QgsUnitTypes::encodeUnit( mDisplacementYUnit ) );
4324 map.insert( QStringLiteral( "offset_x_unit" ), QgsUnitTypes::encodeUnit( mOffsetXUnit ) );
4325 map.insert( QStringLiteral( "offset_y_unit" ), QgsUnitTypes::encodeUnit( mOffsetYUnit ) );
4326 map.insert( QStringLiteral( "distance_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceXMapUnitScale ) );
4327 map.insert( QStringLiteral( "distance_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceYMapUnitScale ) );
4328 map.insert( QStringLiteral( "displacement_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementXMapUnitScale ) );
4329 map.insert( QStringLiteral( "displacement_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementYMapUnitScale ) );
4330 map.insert( QStringLiteral( "offset_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetXMapUnitScale ) );
4331 map.insert( QStringLiteral( "offset_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetYMapUnitScale ) );
4332 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
4333 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
4334 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeMarkerClipMode( mClipMode ) );
4335 map.insert( QStringLiteral( "random_deviation_x" ), QString::number( mRandomDeviationX ) );
4336 map.insert( QStringLiteral( "random_deviation_y" ), QString::number( mRandomDeviationY ) );
4337 map.insert( QStringLiteral( "random_deviation_x_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationXUnit ) );
4338 map.insert( QStringLiteral( "random_deviation_y_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationYUnit ) );
4339 map.insert( QStringLiteral( "random_deviation_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationXMapUnitScale ) );
4340 map.insert( QStringLiteral( "random_deviation_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationYMapUnitScale ) );
4341 map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
4342 map.insert( QStringLiteral( "angle" ), mAngle );
4343 return map;
4344}
4345
4347{
4349 if ( mMarkerSymbol )
4350 {
4351 clonedLayer->setSubSymbol( mMarkerSymbol->clone() );
4352 }
4353 clonedLayer->setClipMode( mClipMode );
4354 copyDataDefinedProperties( clonedLayer );
4355 copyPaintEffect( clonedLayer );
4356 return clonedLayer;
4357}
4358
4359void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4360{
4361 for ( int symbolLayerIdx = 0; symbolLayerIdx < mMarkerSymbol->symbolLayerCount(); symbolLayerIdx++ )
4362 {
4363 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
4364 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
4365 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
4366 element.appendChild( symbolizerElem );
4367
4368 // <Geometry>
4369 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
4370
4371 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
4372 symbolizerElem.appendChild( fillElem );
4373
4374 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
4375 fillElem.appendChild( graphicFillElem );
4376
4377 QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( symbolLayerIdx );
4378
4379 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
4380
4381 // Export to PNG (TODO: SVG)
4382 bool exportOk { false };
4383 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
4384 {
4385 const QImage image { toTiledPatternImage( ) };
4386 if ( ! image.isNull() )
4387 {
4388 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
4389 graphicFillElem.appendChild( graphicElem );
4390 const QFileInfo info { context.exportFilePath() };
4391 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
4392 pngPath = QgsFileUtils::uniquePath( pngPath );
4393 image.save( pngPath );
4394 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
4395 exportOk = true;
4396 }
4397 }
4398
4399 if ( ! exportOk )
4400 {
4401 // Converts to GeoServer "graphic-margin": symbol size must be subtracted from distance and then divided by 2
4402 const double markerSize { mMarkerSymbol->size() };
4403
4404 // store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
4407 // From: https://docs.geoserver.org/stable/en/user/styling/sld/extensions/margins.html
4408 // top-bottom,right-left (two values, top and bottom sharing the same value)
4409 const QString marginSpec = QString( "%1 %2" ).arg( qgsDoubleToString( ( dy - markerSize ) / 2, 2 ), qgsDoubleToString( ( dx - markerSize ) / 2, 2 ) );
4410
4411 QDomElement graphicMarginElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), marginSpec );
4412 symbolizerElem.appendChild( graphicMarginElem );
4413
4414 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
4415 {
4416 markerLayer->writeSldMarker( doc, graphicFillElem, props );
4417 }
4418 else if ( layer )
4419 {
4420 QString errorMsg = QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() );
4421 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4422 }
4423 else
4424 {
4425 QString errorMsg = QStringLiteral( "Missing point pattern symbol layer. Skip it." );
4426 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4427 }
4428 }
4429 }
4430}
4431
4433{
4434
4435 double angleRads { qDegreesToRadians( mAngle ) };
4436
4437 int distanceXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, {} ) ) };
4438 int distanceYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, {} ) ) };
4439
4440 const int displacementXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementX, mDisplacementXUnit, {} ) ) };
4441 const int displacementYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementY, mDisplacementYUnit, {} ) ) };
4442
4443 // Consider displacement, double the distance.
4444 if ( displacementXPx != 0 )
4445 {
4446 distanceXPx *= 2;
4447 }
4448
4449 if ( displacementYPx != 0 )
4450 {
4451 distanceYPx *= 2;
4452 }
4453
4454 const QSize size { QgsSymbolLayerUtils::tileSize( distanceXPx, distanceYPx, angleRads ) };
4455
4456 QPixmap pixmap( size );
4457 pixmap.fill( Qt::transparent );
4458 QPainter painter;
4459 painter.begin( &pixmap );
4460 painter.setRenderHint( QPainter::Antialiasing );
4461 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
4465 renderContext.setForceVectorOutput( true );
4466 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
4467
4468 std::unique_ptr< QgsPointPatternFillSymbolLayer > layerClone( clone() );
4469
4470 layerClone->setAngle( qRadiansToDegrees( angleRads ) );
4471
4472 // No way we can export a random pattern, disable it.
4473 layerClone->setMaximumRandomDeviationX( 0 );
4474 layerClone->setMaximumRandomDeviationY( 0 );
4475
4476 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
4477 painter.end();
4478 return pixmap.toImage();
4479}
4480
4482{
4483
4484 // input element is PolygonSymbolizer
4485
4486 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
4487 if ( fillElem.isNull() )
4488 return nullptr;
4489
4490 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
4491 if ( graphicFillElem.isNull() )
4492 return nullptr;
4493
4494 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
4495 if ( graphicElem.isNull() )
4496 return nullptr;
4497
4498 QgsSymbolLayer *simpleMarkerSl = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicFillElem );
4499 if ( !simpleMarkerSl )
4500 return nullptr;
4501
4502
4503 QgsSymbolLayerList layers;
4504 layers.append( simpleMarkerSl );
4505
4506 std::unique_ptr< QgsMarkerSymbol > marker = std::make_unique< QgsMarkerSymbol >( layers );
4507
4508 // Converts from GeoServer "graphic-margin": symbol size must be added and margin doubled
4509 const double markerSize { marker->size() };
4510
4511 std::unique_ptr< QgsPointPatternFillSymbolLayer > pointPatternFillSl = std::make_unique< QgsPointPatternFillSymbolLayer >();
4512 pointPatternFillSl->setSubSymbol( marker.release() );
4513 // This may not be correct in all cases, TODO: check "uom"
4514 pointPatternFillSl->setDistanceXUnit( Qgis::RenderUnit::Pixels );
4515 pointPatternFillSl->setDistanceYUnit( Qgis::RenderUnit::Pixels );
4516
4517 auto distanceParser = [ & ]( const QStringList & values )
4518 {
4519 switch ( values.count( ) )
4520 {
4521 case 1: // top-right-bottom-left (single value for all four margins)
4522 {
4523 bool ok;
4524 const double v { values.at( 0 ).toDouble( &ok ) };
4525 if ( ok )
4526 {
4527 pointPatternFillSl->setDistanceX( v * 2 + markerSize );
4528 pointPatternFillSl->setDistanceY( v * 2 + markerSize );
4529 }
4530 break;
4531 }
4532 case 2: // top-bottom,right-left (two values, top and bottom sharing the same value)
4533 {
4534 bool ok;
4535 const double vX { values.at( 1 ).toDouble( &ok ) };
4536 if ( ok )
4537 {
4538 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4539 }
4540 const double vY { values.at( 0 ).toDouble( &ok ) };
4541 if ( ok )
4542 {
4543 pointPatternFillSl->setDistanceY( vY * 2 + markerSize );
4544 }
4545 break;
4546 }
4547 case 3: // top,right-left,bottom (three values, with right and left sharing the same value)
4548 {
4549 bool ok;
4550 const double vX { values.at( 1 ).toDouble( &ok ) };
4551 if ( ok )
4552 {
4553 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4554 }
4555 const double vYt { values.at( 0 ).toDouble( &ok ) };
4556 if ( ok )
4557 {
4558 const double vYb { values.at( 2 ).toDouble( &ok ) };
4559 if ( ok )
4560 {
4561 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4562 }
4563 }
4564 break;
4565 }
4566 case 4: // top,right,bottom,left (one explicit value per margin)
4567 {
4568 bool ok;
4569 const double vYt { values.at( 0 ).toDouble( &ok ) };
4570 if ( ok )
4571 {
4572 const double vYb { values.at( 2 ).toDouble( &ok ) };
4573 if ( ok )
4574 {
4575 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4576 }
4577 }
4578 const double vXr { values.at( 1 ).toDouble( &ok ) };
4579 if ( ok )
4580 {
4581 const double vXl { values.at( 3 ).toDouble( &ok ) };
4582 if ( ok )
4583 {
4584 pointPatternFillSl->setDistanceX( ( vXr + vXl ) + markerSize );
4585 }
4586 }
4587 break;
4588 }
4589 default:
4590 break;
4591 }
4592 };
4593
4594 // Set distance X and Y from vendor options, or from Size if no vendor options are set
4595 bool distanceFromVendorOption { false };
4596 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
4597 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
4598 {
4599 // Legacy
4600 if ( it.key() == QLatin1String( "distance" ) )
4601 {
4602 distanceParser( it.value().split( ',' ) );
4603 distanceFromVendorOption = true;
4604 }
4605 // GeoServer
4606 else if ( it.key() == QLatin1String( "graphic-margin" ) )
4607 {
4608 distanceParser( it.value().split( ' ' ) );
4609 distanceFromVendorOption = true;
4610 }
4611 }
4612
4613 // Get distances from size
4614 if ( ! distanceFromVendorOption && ! graphicFillElem.elementsByTagName( QStringLiteral( "Size" ) ).isEmpty() )
4615 {
4616 const QDomElement sizeElement { graphicFillElem.elementsByTagName( QStringLiteral( "Size" ) ).at( 0 ).toElement() };
4617 bool ok;
4618 const double size { sizeElement.text().toDouble( &ok ) };
4619 if ( ok )
4620 {
4621 pointPatternFillSl->setDistanceX( size );
4622 pointPatternFillSl->setDistanceY( size );
4623 }
4624 }
4625
4626 return pointPatternFillSl.release();
4627}
4628
4630{
4631 if ( !symbol )
4632 {
4633 return false;
4634 }
4635
4636 if ( symbol->type() == Qgis::SymbolType::Marker )
4637 {
4638 QgsMarkerSymbol *markerSymbol = static_cast<QgsMarkerSymbol *>( symbol );
4639 mMarkerSymbol.reset( markerSymbol );
4640 }
4641 return true;
4642}
4643
4648
4650{
4654 && ( !mMarkerSymbol || !mMarkerSymbol->hasDataDefinedProperties() ) )
4655 {
4656 return;
4657 }
4658
4659 double distanceX = mDistanceX;
4661 {
4664 }
4665 double distanceY = mDistanceY;
4667 {
4670 }
4673 {
4676 }
4679 {
4682 }
4683 double offsetX = mOffsetX;
4685 {
4688 }
4689 double offsetY = mOffsetY;
4691 {
4694 }
4695 applyPattern( context, mBrush, distanceX, distanceY, displacementX, displacementY, offsetX, offsetY );
4696}
4697
4699{
4700 return 0;
4701}
4702
4704{
4705 QSet<QString> attributes = QgsImageFillSymbolLayer::usedAttributes( context );
4706
4707 if ( mMarkerSymbol )
4708 attributes.unite( mMarkerSymbol->usedAttributes( context ) );
4709
4710 return attributes;
4711}
4712
4714{
4716 return true;
4717 if ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
4718 return true;
4719 return false;
4720}
4721
4723{
4724 mColor = c;
4725 if ( mMarkerSymbol )
4726 mMarkerSymbol->setColor( c );
4727}
4728
4730{
4731 return mMarkerSymbol ? mMarkerSymbol->color() : mColor;
4732}
4733
4735
4736
4741
4743
4745{
4746 std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4747
4748 if ( properties.contains( QStringLiteral( "point_on_surface" ) ) )
4749 sl->setPointOnSurface( properties[QStringLiteral( "point_on_surface" )].toInt() != 0 );
4750 if ( properties.contains( QStringLiteral( "point_on_all_parts" ) ) )
4751 sl->setPointOnAllParts( properties[QStringLiteral( "point_on_all_parts" )].toInt() != 0 );
4752 if ( properties.contains( QStringLiteral( "clip_points" ) ) )
4753 sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() != 0 );
4754 if ( properties.contains( QStringLiteral( "clip_on_current_part_only" ) ) )
4755 sl->setClipOnCurrentPartOnly( properties[QStringLiteral( "clip_on_current_part_only" )].toInt() != 0 );
4756
4757 sl->restoreOldDataDefinedProperties( properties );
4758
4759 return sl.release();
4760}
4761
4763{
4764 return QStringLiteral( "CentroidFill" );
4765}
4766
4767void QgsCentroidFillSymbolLayer::setColor( const QColor &color )
4768{
4769 mMarker->setColor( color );
4770 mColor = color;
4771}
4772
4774{
4775 return mMarker ? mMarker->color() : mColor;
4776}
4777
4779{
4780 mMarker->startRender( context.renderContext(), context.fields() );
4781}
4782
4784{
4785 mMarker->stopRender( context.renderContext() );
4786}
4787
4788void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4789{
4790 Part part;
4791 part.exterior = points;
4792 if ( rings )
4793 part.rings = *rings;
4794
4795 if ( mRenderingFeature )
4796 {
4797 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
4798 // until after we've received the final part
4799 mFeatureSymbolOpacity = context.opacity();
4800 mCurrentParts << part;
4801 }
4802 else
4803 {
4804 // not rendering a feature, so we can just render the polygon immediately
4805 const double prevOpacity = mMarker->opacity();
4806 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
4807 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4808 render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
4809 mMarker->setOpacity( prevOpacity );
4810 }
4811}
4812
4814{
4815 installMasks( context, true );
4816
4817 mRenderingFeature = true;
4818 mCurrentParts.clear();
4819}
4820
4822{
4823 mRenderingFeature = false;
4824
4825 const double prevOpacity = mMarker->opacity();
4826 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
4827
4828 render( context, mCurrentParts, feature, false );
4830 mMarker->setOpacity( prevOpacity );
4831
4832 removeMasks( context, true );
4833}
4834
4835void QgsCentroidFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsCentroidFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
4836{
4839 bool clipPoints = mClipPoints;
4841
4842 // TODO add expressions support
4843
4844 QVector< QgsGeometry > geometryParts;
4845 geometryParts.reserve( parts.size() );
4846 QPainterPath globalPath;
4847
4848 int maxArea = 0;
4849 int maxAreaPartIdx = 0;
4850
4851 for ( int i = 0; i < parts.size(); i++ )
4852 {
4853 const Part part = parts[i];
4854 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
4855
4856 if ( !geom.isNull() && !part.rings.empty() )
4857 {
4858 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
4859
4860 if ( !pointOnAllParts )
4861 {
4862 int area = poly->area();
4863
4864 if ( area > maxArea )
4865 {
4866 maxArea = area;
4867 maxAreaPartIdx = i;
4868 }
4869 }
4870 }
4871
4873 {
4874 globalPath.addPolygon( part.exterior );
4875 for ( const QPolygonF &ring : part.rings )
4876 {
4877 globalPath.addPolygon( ring );
4878 }
4879 }
4880 }
4881
4882 for ( int i = 0; i < parts.size(); i++ )
4883 {
4884 if ( !pointOnAllParts && i != maxAreaPartIdx )
4885 continue;
4886
4887 const Part part = parts[i];
4888
4889 if ( clipPoints )
4890 {
4891 QPainterPath path;
4892
4894 {
4895 path.addPolygon( part.exterior );
4896 for ( const QPolygonF &ring : part.rings )
4897 {
4898 path.addPolygon( ring );
4899 }
4900 }
4901 else
4902 {
4903 path = globalPath;
4904 }
4905
4906 context.painter()->save();
4907 context.painter()->setClipPath( path );
4908 }
4909
4910 QPointF centroid = pointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( part.exterior, &part.rings ) : QgsSymbolLayerUtils::polygonCentroid( part.exterior );
4911
4912 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4914 mMarker->renderPoint( centroid, feature.isValid() ? &feature : nullptr, context, -1, selected );
4915 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
4916
4917 if ( clipPoints )
4918 {
4919 context.painter()->restore();
4920 }
4921 }
4922}
4923
4925{
4926 QVariantMap map;
4927 map[QStringLiteral( "point_on_surface" )] = QString::number( mPointOnSurface );
4928 map[QStringLiteral( "point_on_all_parts" )] = QString::number( mPointOnAllParts );
4929 map[QStringLiteral( "clip_points" )] = QString::number( mClipPoints );
4930 map[QStringLiteral( "clip_on_current_part_only" )] = QString::number( mClipOnCurrentPartOnly );
4931 return map;
4932}
4933
4935{
4936 std::unique_ptr< QgsCentroidFillSymbolLayer > x = std::make_unique< QgsCentroidFillSymbolLayer >();
4937 x->mAngle = mAngle;
4938 x->mColor = mColor;
4939 x->setSubSymbol( mMarker->clone() );
4940 x->setPointOnSurface( mPointOnSurface );
4941 x->setPointOnAllParts( mPointOnAllParts );
4942 x->setClipPoints( mClipPoints );
4943 x->setClipOnCurrentPartOnly( mClipOnCurrentPartOnly );
4944 copyDataDefinedProperties( x.get() );
4945 copyPaintEffect( x.get() );
4946 return x.release();
4947}
4948
4949void QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4950{
4951 // SLD 1.0 specs says: "if a line, polygon, or raster geometry is
4952 // used with PointSymbolizer, then the semantic is to use the centroid
4953 // of the geometry, or any similar representative point.
4954 mMarker->toSld( doc, element, props );
4955}
4956
4958{
4960 if ( !l )
4961 return nullptr;
4962
4963 QgsSymbolLayerList layers;
4964 layers.append( l );
4965 std::unique_ptr< QgsMarkerSymbol > marker( new QgsMarkerSymbol( layers ) );
4966
4967 std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4968 sl->setSubSymbol( marker.release() );
4969 sl->setPointOnAllParts( false );
4970 return sl.release();
4971}
4972
4973
4978
4980{
4981 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
4982 {
4983 delete symbol;
4984 return false;
4985 }
4986
4987 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
4988 mColor = mMarker->color();
4989 return true;
4990}
4991
4993{
4994 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
4995
4996 if ( mMarker )
4997 attributes.unite( mMarker->usedAttributes( context ) );
4998
4999 return attributes;
5000}
5001
5003{
5005 return true;
5006 if ( mMarker && mMarker->hasDataDefinedProperties() )
5007 return true;
5008 return false;
5009}
5010
5015
5017{
5018 if ( mMarker )
5019 {
5020 mMarker->setOutputUnit( unit );
5021 }
5022}
5023
5025{
5026 if ( mMarker )
5027 {
5028 return mMarker->outputUnit();
5029 }
5030 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5031}
5032
5034{
5035 if ( mMarker )
5036 {
5037 return mMarker->usesMapUnits();
5038 }
5039 return false;
5040}
5041
5043{
5044 if ( mMarker )
5045 {
5046 mMarker->setMapUnitScale( scale );
5047 }
5048}
5049
5051{
5052 if ( mMarker )
5053 {
5054 return mMarker->mapUnitScale();
5055 }
5056 return QgsMapUnitScale();
5057}
5058
5059
5060
5061
5064 , mImageFilePath( imageFilePath )
5065{
5066 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //disable sub symbol
5068}
5069
5071
5072QgsSymbolLayer *QgsRasterFillSymbolLayer::create( const QVariantMap &properties )
5073{
5075 double alpha = 1.0;
5076 QPointF offset;
5077 double angle = 0.0;
5078 double width = 0.0;
5079
5080 QString imagePath;
5081 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
5082 {
5083 imagePath = properties[QStringLiteral( "imageFile" )].toString();
5084 }
5085 if ( properties.contains( QStringLiteral( "coordinate_mode" ) ) )
5086 {
5087 mode = static_cast< Qgis::SymbolCoordinateReference >( properties[QStringLiteral( "coordinate_mode" )].toInt() );
5088 }
5089 if ( properties.contains( QStringLiteral( "alpha" ) ) )
5090 {
5091 alpha = properties[QStringLiteral( "alpha" )].toDouble();
5092 }
5093 if ( properties.contains( QStringLiteral( "offset" ) ) )
5094 {
5095 offset = QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "offset" )].toString() );
5096 }
5097 if ( properties.contains( QStringLiteral( "angle" ) ) )
5098 {
5099 angle = properties[QStringLiteral( "angle" )].toDouble();
5100 }
5101 if ( properties.contains( QStringLiteral( "width" ) ) )
5102 {
5103 width = properties[QStringLiteral( "width" )].toDouble();
5104 }
5105 std::unique_ptr< QgsRasterFillSymbolLayer > symbolLayer = std::make_unique< QgsRasterFillSymbolLayer >( imagePath );
5106 symbolLayer->setCoordinateMode( mode );
5107 symbolLayer->setOpacity( alpha );
5108 symbolLayer->setOffset( offset );
5109 symbolLayer->setAngle( angle );
5110 symbolLayer->setWidth( width );
5111 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
5112 {
5113 symbolLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
5114 }
5115 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
5116 {
5117 symbolLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
5118 }
5119 if ( properties.contains( QStringLiteral( "width_unit" ) ) )
5120 {
5121 symbolLayer->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "width_unit" )].toString() ) );
5122 }
5123 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
5124 {
5125 symbolLayer->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
5126 }
5127
5128 symbolLayer->restoreOldDataDefinedProperties( properties );
5129
5130 return symbolLayer.release();
5131}
5132
5134{
5135 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
5136 if ( fillElem.isNull() )
5137 return nullptr;
5138
5139 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
5140 if ( graphicFillElem.isNull() )
5141 return nullptr;
5142
5143 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
5144 if ( graphicElem.isNull() )
5145 return nullptr;
5146
5147 QString path, mimeType;
5148 double size;
5149 QColor fillColor;
5150
5151 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
5152 return nullptr;
5153
5154 // Try to correct the path, this is a wild guess but we have not access to the SLD path here.
5155 if ( ! QFile::exists( path ) )
5156 {
5157 path = QgsProject::instance()->pathResolver().readPath( path );
5158 }
5159
5160 std::unique_ptr< QgsRasterFillSymbolLayer> sl = std::make_unique< QgsRasterFillSymbolLayer>( path );
5161
5162 return sl.release();
5163}
5164
5165void QgsRasterFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
5166{
5167 QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
5168 if ( it != properties.end() )
5169 {
5170 if ( saving )
5171 it.value() = pathResolver.writePath( it.value().toString() );
5172 else
5173 it.value() = pathResolver.readPath( it.value().toString() );
5174 }
5175}
5176
5178{
5179 Q_UNUSED( symbol )
5180 return true;
5181}
5182
5184{
5185 return QStringLiteral( "RasterFill" );
5186}
5187
5188void QgsRasterFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5189{
5190 QPainter *p = context.renderContext().painter();
5191 if ( !p )
5192 {
5193 return;
5194 }
5195
5196 QPointF offset = mOffset;
5198 {
5200 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
5201 bool ok = false;
5202 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
5203 if ( ok )
5204 offset = res;
5205 }
5206 if ( !offset.isNull() )
5207 {
5208 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
5209 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
5210 p->translate( offset );
5211 }
5212 if ( mCoordinateMode == Qgis::SymbolCoordinateReference::Feature )
5213 {
5214 QRectF boundingRect = points.boundingRect();
5215 mBrush.setTransform( mBrush.transform().translate( boundingRect.left() - mBrush.transform().dx(),
5216 boundingRect.top() - mBrush.transform().dy() ) );
5217 }
5218
5219 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
5220 if ( !offset.isNull() )
5221 {
5222 p->translate( -offset );
5223 }
5224}
5225
5227{
5228 applyPattern( mBrush, mImageFilePath, mWidth, mOpacity * context.opacity(), context );
5229}
5230
5232{
5233 Q_UNUSED( context )
5234}
5235
5237{
5238 QVariantMap map;
5239 map[QStringLiteral( "imageFile" )] = mImageFilePath;
5240 map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
5241 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
5242 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
5243 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
5244 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
5245 map[QStringLiteral( "angle" )] = QString::number( mAngle );
5246 map[QStringLiteral( "width" )] = QString::number( mWidth );
5247 map[QStringLiteral( "width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
5248 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
5249 return map;
5250}
5251
5253{
5254 std::unique_ptr< QgsRasterFillSymbolLayer > sl = std::make_unique< QgsRasterFillSymbolLayer >( mImageFilePath );
5255 sl->setCoordinateMode( mCoordinateMode );
5256 sl->setOpacity( mOpacity );
5257 sl->setOffset( mOffset );
5258 sl->setOffsetUnit( mOffsetUnit );
5259 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
5260 sl->setAngle( mAngle );
5261 sl->setWidth( mWidth );
5262 sl->setWidthUnit( mWidthUnit );
5263 sl->setWidthMapUnitScale( mWidthMapUnitScale );
5264 copyDataDefinedProperties( sl.get() );
5265 copyPaintEffect( sl.get() );
5266 return sl.release();
5267}
5268
5270{
5271 return context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
5272}
5273
5275{
5276 return mWidthUnit == Qgis::RenderUnit::MapUnits || mWidthUnit == Qgis::RenderUnit::MetersInMapUnits
5277 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
5278}
5279
5281{
5282 return QColor();
5283}
5284
5286{
5288 mOffsetUnit = unit;
5289 mWidthUnit = unit;
5290}
5291
5292void QgsRasterFillSymbolLayer::setImageFilePath( const QString &imagePath )
5293{
5294 mImageFilePath = imagePath;
5295}
5296
5298{
5299 mCoordinateMode = mode;
5300}
5301
5302void QgsRasterFillSymbolLayer::setOpacity( const double opacity )
5303{
5304 mOpacity = opacity;
5305}
5306
5308{
5309 if ( !dataDefinedProperties().hasActiveProperties() )
5310 return; // shortcut
5311
5316
5317 if ( !hasWidthExpression && !hasAngleExpression && !hasOpacityExpression && !hasFileExpression )
5318 {
5319 return; //no data defined settings
5320 }
5321
5322 bool ok;
5323 if ( hasAngleExpression )
5324 {
5327 if ( ok )
5328 mNextAngle = nextAngle;
5329 }
5330
5331 if ( !hasWidthExpression && !hasOpacityExpression && !hasFileExpression )
5332 {
5333 return; //nothing further to do
5334 }
5335
5336 double width = mWidth;
5337 if ( hasWidthExpression )
5338 {
5339 context.setOriginalValueVariable( mWidth );
5341 }
5342 double opacity = mOpacity;
5343 if ( hasOpacityExpression )
5344 {
5345 context.setOriginalValueVariable( mOpacity );
5347 }
5348 QString file = mImageFilePath;
5349 if ( hasFileExpression )
5350 {
5351 context.setOriginalValueVariable( mImageFilePath );
5353 }
5354 applyPattern( mBrush, file, width, opacity, context );
5355}
5356
5361
5362void QgsRasterFillSymbolLayer::applyPattern( QBrush &brush, const QString &imageFilePath, const double width, const double alpha, const QgsSymbolRenderContext &context )
5363{
5364 QSize size;
5365 if ( width > 0 )
5366 {
5367 if ( mWidthUnit != Qgis::RenderUnit::Percentage )
5368 {
5369 size.setWidth( context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale ) );
5370 }
5371 else
5372 {
5373 // RenderPercentage Unit Type takes original image size
5375 if ( size.isEmpty() )
5376 return;
5377
5378 size.setWidth( ( width * size.width() ) / 100.0 );
5379
5380 // don't render symbols with size below one or above 10,000 pixels
5381 if ( static_cast< int >( size.width() ) < 1 || 10000.0 < size.width() )
5382 return;
5383 }
5384
5385 size.setHeight( 0 );
5386 }
5387
5388 bool cached;
5389 QImage img = QgsApplication::imageCache()->pathAsImage( imageFilePath, size, true, alpha, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
5390 if ( img.isNull() )
5391 return;
5392
5393 brush.setTextureImage( img );
5394}
5395
5396
5397//
5398// QgsRandomMarkerFillSymbolLayer
5399//
5400
5401QgsRandomMarkerFillSymbolLayer::QgsRandomMarkerFillSymbolLayer( int pointCount, Qgis::PointCountMethod method, double densityArea, unsigned long seed )
5402 : mCountMethod( method )
5403 , mPointCount( pointCount )
5404 , mDensityArea( densityArea )
5405 , mSeed( seed )
5406{
5408}
5409
5411
5413{
5414 const Qgis::PointCountMethod countMethod = static_cast< Qgis::PointCountMethod >( properties.value( QStringLiteral( "count_method" ), QStringLiteral( "0" ) ).toInt() );
5415 const int pointCount = properties.value( QStringLiteral( "point_count" ), QStringLiteral( "10" ) ).toInt();
5416 const double densityArea = properties.value( QStringLiteral( "density_area" ), QStringLiteral( "250.0" ) ).toDouble();
5417
5418 unsigned long seed = 0;
5419 if ( properties.contains( QStringLiteral( "seed" ) ) )
5420 seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
5421 else
5422 {
5423 // if we a creating a new random marker fill from scratch, we default to a random seed
5424 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
5425 std::random_device rd;
5426 std::mt19937 mt( seed == 0 ? rd() : seed );
5427 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
5428 seed = uniformDist( mt );
5429 }
5430
5431 std::unique_ptr< QgsRandomMarkerFillSymbolLayer > sl = std::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, countMethod, densityArea, seed );
5432
5433 if ( properties.contains( QStringLiteral( "density_area_unit" ) ) )
5434 sl->setDensityAreaUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "density_area_unit" )].toString() ) );
5435 if ( properties.contains( QStringLiteral( "density_area_unit_scale" ) ) )
5436 sl->setDensityAreaUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "density_area_unit_scale" )].toString() ) );
5437
5438 if ( properties.contains( QStringLiteral( "clip_points" ) ) )
5439 {
5440 sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() );
5441 }
5442
5443 return sl.release();
5444}
5445
5447{
5448 return QStringLiteral( "RandomMarkerFill" );
5449}
5450
5452{
5453 mMarker->setColor( color );
5454 mColor = color;
5455}
5456
5458{
5459 return mMarker ? mMarker->color() : mColor;
5460}
5461
5463{
5464 mMarker->startRender( context.renderContext(), context.fields() );
5465}
5466
5468{
5469 mMarker->stopRender( context.renderContext() );
5470}
5471
5472void QgsRandomMarkerFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5473{
5474 Part part;
5475 part.exterior = points;
5476 if ( rings )
5477 part.rings = *rings;
5478
5479 if ( mRenderingFeature )
5480 {
5481 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
5482 // until after we've received the final part
5483 mFeatureSymbolOpacity = context.opacity();
5484 mCurrentParts << part;
5485 }
5486 else
5487 {
5488 // not rendering a feature, so we can just render the polygon immediately
5489 const double prevOpacity = mMarker->opacity();
5490 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
5491 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
5492 render( context.renderContext(), QVector< Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
5493 mMarker->setOpacity( prevOpacity );
5494 }
5495}
5496
5497void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsRandomMarkerFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
5498{
5499 bool clipPoints = mClipPoints;
5501 {
5504 }
5505
5506 QVector< QgsGeometry > geometryParts;
5507 geometryParts.reserve( parts.size() );
5508 QPainterPath path;
5509
5510 for ( const Part &part : parts )
5511 {
5512 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
5513 if ( !geom.isNull() && !part.rings.empty() )
5514 {
5515 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
5516 for ( const QPolygonF &ring : part.rings )
5517 {
5519 }
5520 }
5521 if ( !geom.isGeosValid() )
5522 {
5523 geom = geom.buffer( 0, 0 );
5524 }
5525 geometryParts << geom;
5526
5527 if ( clipPoints )
5528 {
5529 path.addPolygon( part.exterior );
5530 for ( const QPolygonF &ring : part.rings )
5531 {
5532 path.addPolygon( ring );
5533 }
5534 }
5535 }
5536
5537 const QgsGeometry geom = geometryParts.count() != 1 ? QgsGeometry::unaryUnion( geometryParts ) : geometryParts.at( 0 );
5538
5539 if ( clipPoints )
5540 {
5541 context.painter()->save();
5542 context.painter()->setClipPath( path );
5543 }
5544
5545
5546 int count = mPointCount;
5548 {
5551 }
5552
5553 switch ( mCountMethod )
5554 {
5556 {
5557 double densityArea = mDensityArea;
5559 {
5562 }
5563 densityArea = context.convertToPainterUnits( std::sqrt( densityArea ), mDensityAreaUnit, mDensityAreaUnitScale );
5564 densityArea = std::pow( densityArea, 2 );
5565 count = std::max( 0.0, std::ceil( count * ( geom.area() / densityArea ) ) );
5566 break;
5567 }
5569 break;
5570 }
5571
5572 unsigned long seed = mSeed;
5574 {
5575 context.expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
5577 }
5578
5579 QVector< QgsPointXY > randomPoints = geom.randomPointsInPolygon( count, seed );
5580#if 0
5581 // in some cases rendering from top to bottom is nice (e.g. randomised tree markers), but in other cases it's not wanted..
5582 // TODO consider exposing this as an option
5583 std::sort( randomPoints.begin(), randomPoints.end(), []( const QgsPointXY & a, const QgsPointXY & b )->bool
5584 {
5585 return a.y() < b.y();
5586 } );
5587#endif
5589 QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scope );
5590 int pointNum = 0;
5591 const bool needsExpressionContext = mMarker->hasDataDefinedProperties();
5592
5593 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
5595
5596 for ( const QgsPointXY &p : std::as_const( randomPoints ) )
5597 {
5598 if ( needsExpressionContext )
5600 mMarker->renderPoint( QPointF( p.x(), p.y() ), feature.isValid() ? &feature : nullptr, context, -1, selected );
5601 }
5602
5603 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
5604
5605 if ( clipPoints )
5606 {
5607 context.painter()->restore();
5608 }
5609}
5610
5612{
5613 QVariantMap map;
5614 map.insert( QStringLiteral( "count_method" ), QString::number( static_cast< int >( mCountMethod ) ) );
5615 map.insert( QStringLiteral( "point_count" ), QString::number( mPointCount ) );
5616 map.insert( QStringLiteral( "density_area" ), QString::number( mDensityArea ) );
5617 map.insert( QStringLiteral( "density_area_unit" ), QgsUnitTypes::encodeUnit( mDensityAreaUnit ) );
5618 map.insert( QStringLiteral( "density_area_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDensityAreaUnitScale ) );
5619 map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
5620 map.insert( QStringLiteral( "clip_points" ), QString::number( mClipPoints ) );
5621 return map;
5622}
5623
5625{
5626 std::unique_ptr< QgsRandomMarkerFillSymbolLayer > res = std::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mCountMethod, mDensityArea, mSeed );
5627 res->mAngle = mAngle;
5628 res->mColor = mColor;
5629 res->setDensityAreaUnit( mDensityAreaUnit );
5630 res->setDensityAreaUnitScale( mDensityAreaUnitScale );
5631 res->mClipPoints = mClipPoints;
5632 res->setSubSymbol( mMarker->clone() );
5633 copyDataDefinedProperties( res.get() );
5634 copyPaintEffect( res.get() );
5635 return res.release();
5636}
5637
5642
5644{
5645 return mMarker.get();
5646}
5647
5649{
5650 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5651 {
5652 delete symbol;
5653 return false;
5654 }
5655
5656 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5657 mColor = mMarker->color();
5658 return true;
5659}
5660
5662{
5663 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5664
5665 if ( mMarker )
5666 attributes.unite( mMarker->usedAttributes( context ) );
5667
5668 return attributes;
5669}
5670
5672{
5674 return true;
5675 if ( mMarker && mMarker->hasDataDefinedProperties() )
5676 return true;
5677 return false;
5678}
5679
5681{
5682 return mPointCount;
5683}
5684
5686{
5687 mPointCount = pointCount;
5688}
5689
5691{
5692 return mSeed;
5693}
5694
5696{
5697 mSeed = seed;
5698}
5699
5701{
5702 return mClipPoints;
5703}
5704
5706{
5707 mClipPoints = clipPoints;
5708}
5709
5711{
5712 return mCountMethod;
5713}
5714
5716{
5717 mCountMethod = method;
5718}
5719
5721{
5722 return mDensityArea;
5723}
5724
5726{
5727 mDensityArea = area;
5728}
5729
5731{
5732 installMasks( context, true );
5733
5734 mRenderingFeature = true;
5735 mCurrentParts.clear();
5736}
5737
5739{
5740 mRenderingFeature = false;
5741
5742 const double prevOpacity = mMarker->opacity();
5743 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
5744
5745 render( context, mCurrentParts, feature, false );
5746
5747 mFeatureSymbolOpacity = 1;
5748 mMarker->setOpacity( prevOpacity );
5749
5750 removeMasks( context, true );
5751}
5752
5753
5755{
5756 mDensityAreaUnit = unit;
5757 if ( mMarker )
5758 {
5759 mMarker->setOutputUnit( unit );
5760 }
5761}
5762
5764{
5765 if ( mMarker )
5766 {
5767 return mMarker->outputUnit();
5768 }
5769 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5770}
5771
5773{
5774 if ( mMarker )
5775 {
5776 return mMarker->usesMapUnits();
5777 }
5778 return false;
5779}
5780
5782{
5783 if ( mMarker )
5784 {
5785 mMarker->setMapUnitScale( scale );
5786 }
5787}
5788
5790{
5791 if ( mMarker )
5792 {
5793 return mMarker->mapUnitScale();
5794 }
5795 return QgsMapUnitScale();
5796}
MarkerClipMode
Marker clipping modes.
Definition qgis.h:2379
@ CompletelyWithin
Render complete markers wherever the completely fall within the polygon shape.
@ NoClipping
No clipping, render complete markers.
@ Shape
Clip to polygon shape.
@ CentroidWithin
Render complete markers wherever their centroid falls within the polygon shape.
LineClipMode
Line clipping modes.
Definition qgis.h:2393
@ NoClipping
Lines are not clipped, will extend to shape's bounding box.
@ ClipPainterOnly
Applying clipping on the painter only (i.e. line endpoints will coincide with polygon bounding box,...
@ ClipToIntersection
Clip lines to intersection with polygon shape (slower) (i.e. line endpoints will coincide with polygo...
GradientColorSource
Gradient color sources.
Definition qgis.h:2308
@ ColorRamp
Gradient color ramp.
@ SimpleTwoColor
Simple two color gradient.
GradientSpread
Gradient spread options, which control how gradients are rendered outside of their start and end poin...
Definition qgis.h:2352
@ Repeat
Repeat gradient.
@ Reflect
Reflect gradient.
@ Pad
Pad out gradient using colors at endpoint of gradient.
@ Png
Export complex styles to separate PNG files for better compatibility with OGC servers.
PointCountMethod
Methods which define the number of points randomly filling a polygon.
Definition qgis.h:2367
@ Absolute
The point count is used as an absolute count of markers.
@ DensityBased
The point count is part of a marker density count.
RenderUnit
Rendering size units.
Definition qgis.h:3627
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
@ Unknown
Mixed or unknown units.
@ MapUnits
Map units.
@ MetersInMapUnits
Meters value as Map units.
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
GradientType
Gradient types.
Definition qgis.h:2322
@ Linear
Linear gradient.
@ Conical
Conical (polar) gradient.
@ Radial
Radial (circular) gradient.
@ Marker
Marker symbol.
@ Line
Line symbol.
SymbolCoordinateReference
Symbol coordinate reference modes.
Definition qgis.h:2337
@ Feature
Relative to feature/shape being rendered.
@ Viewport
Relative to the whole viewport/output device.
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.
int valueAsInt(int key, const QgsExpressionContext &context, int defaultValue=0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an integer.
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.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
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.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
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...
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.
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 QVariantMap properties() const =0
Returns a string map containing all the color ramp's properties.
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
virtual QString type() const =0
Returns a string representing the color ramp type.
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:56
bool isValid() const
Returns the validity of this feature.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:54
static QString uniquePath(const QString &path)
Creates a unique file path name from a desired path by appending "_<n>" (where "<n>" is an integer nu...
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 contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
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.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine representing the specified 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.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
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.
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].
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.
Base class for polygon renderers generating texture images.
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.
A symbol fill consisting of repeated parallel lines.
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.
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.
QPolygonF asQPolygonF() const override
Returns a QPolygonF representing the points.
static QgsLineString * fromQPolygonF(const QPolygonF &polygon)
Returns a new linestring from a QPolygonF polygon input.
A line symbol type, for rendering LineString and MultiLineString geometries.
Perform transforms between map coordinates and device coordinates.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
Struct for storing maximum and minimum scales for measurements in map units.
Line symbol layer type which draws repeating marker symbols along a line feature.
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)
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.
A fill symbol layer which fills polygon shapes with repeating marker symbols.
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.
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)
A class to represent a 2D point.
Definition qgspointxy.h:59
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double x
Definition qgspoint.h:52
double y
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:34
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...
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const override
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.
A fill symbol layer which places markers at random locations within polygons.
~QgsRandomMarkerFillSymbolLayer() override
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 setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
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.
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
A class for filling symbols with a repeated raster image.
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 ...
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.
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() const
Returns the center point of the rectangle.
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
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.
bool forceVectorOutput() const
Returns true if rendering operations should use vector operations instead of any faster raster shortc...
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 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...
A class for filling symbols with a repeated SVG file.
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
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.
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.
~QgsSimpleFillSymbolLayer() override
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)
The QgsSldExportContext class holds SLD export options and other information related to SLD export of...
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 QColor decodeColor(const QString &str)
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 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 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 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 void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
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)
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 void fillToSld(QDomDocument &doc, QDomElement &element, Qt::BrushStyle brushStyle, const QColor &color=QColor())
static Qgis::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
static void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
static void wellKnownMarkerToSld(QDomDocument &doc, QDomElement &element, const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth=-1, double size=-1)
static Qt::PenStyle decodePenStyle(const QString &str)
static void createRotationElement(QDomDocument &doc, QDomElement &element, const QString &rotationFunc)
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 QgsSymbolLayer * createMarkerLayerFromSld(QDomElement &element)
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static QPointF decodePoint(const QString &string)
Decodes a QSizeF from a string.
static QgsSymbolLayer * createLineLayerFromSld(QDomElement &element)
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.
@ PropertyGradientReference1X
Gradient reference point 1 x.
@ PropertyShapeburstIgnoreRings
Shapeburst ignore rings.
@ PropertyGradientReference2X
Gradient reference point 2 x.
@ PropertyStrokeStyle
Stroke style (eg solid, dashed)
@ PropertyDistanceX
Horizontal distance between points.
@ PropertyFile
Filename, eg for svg files.
@ PropertyGradientType
Gradient fill type.
@ PropertyAngle
Symbol angle.
@ PropertyLineClipping
Line clipping mode (since QGIS 3.24)
@ PropertyDistanceY
Vertical distance between points.
@ PropertyDisplacementX
Horizontal displacement.
@ PropertyGradientSpread
Gradient spread mode.
@ PropertyOffsetY
Vertical offset.
@ PropertyGradientReference1Y
Gradient reference point 1 y.
@ PropertyLineDistance
Distance between lines, or length of lines for hash line symbols.
@ PropertyBlurRadius
Shapeburst blur radius.
@ PropertyGradientReference2Y
Gradient reference point 2 y.
@ PropertyMarkerClipping
Marker clipping mode (since QGIS 3.24)
@ PropertyDensityArea
Density area.
@ PropertyGradientReference1IsCentroid
Gradient reference point 1 is centroid.
@ PropertyShapeburstUseWholeShape
Shapeburst use whole shape.
@ PropertyOffsetX
Horizontal offset.
@ PropertyJoinStyle
Line join style.
@ PropertyOpacity
Opacity.
@ PropertySecondaryColor
Secondary color (eg for gradient fills)
@ PropertyCoordinateMode
Gradient coordinate mode.
@ PropertyRandomOffsetY
Random offset Y (since QGIS 3.24)
@ PropertyLineAngle
Line angle, or angle of hash lines for hash line symbols.
@ PropertyShapeburstMaxDistance
Shapeburst fill from edge distance.
@ PropertyOffset
Symbol offset.
@ PropertyStrokeWidth
Stroke width.
@ PropertyFillColor
Fill color.
@ PropertyClipPoints
Whether markers should be clipped to polygon boundaries.
@ PropertyPointCount
Point count.
@ PropertyRandomSeed
Random number seed.
@ PropertyRandomOffsetX
Random offset X (since QGIS 3.24)
@ PropertyFillStyle
Fill style (eg solid, dots)
@ PropertyDisplacementY
Vertical displacement.
@ PropertyStrokeColor
Stroke color.
@ PropertyGradientReference2IsCentroid
Gradient reference point 2 is centroid.
@ PropertyWidth
Symbol width.
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.
void removeMasks(QgsRenderContext &context, bool recursive)
When rendering, remove previously installed masks from context painter if recursive is true masks are...
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
void installMasks(QgsRenderContext &context, bool recursive)
When rendering, install masks on context painter if recursive is true masks are installed recursively...
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 QString layerType() const =0
Returns a string that represents this layer type.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
virtual QColor strokeColor() const
Returns the stroke color for the symbol layer.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
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.
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.
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:94
QColor color() const
Returns the symbol's color.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:153
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)
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:4271
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:4332
QMap< QString, QString > QgsStringMap
Definition qgis.h:4877
#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
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
Single variable definition for use within a QgsExpressionContextScope.