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