QGIS API Documentation  3.24.2-Tisler (13c1a02865)
qgsfillsymbollayer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsfillsymbollayer.cpp
3  ---------------------
4  begin : November 2009
5  copyright : (C) 2009 by Martin Dobias
6  email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsfillsymbollayer.h"
17 #include "qgslinesymbollayer.h"
18 #include "qgsmarkersymbollayer.h"
19 #include "qgssymbollayerutils.h"
20 #include "qgsdxfexport.h"
21 #include "qgsexpression.h"
22 #include "qgsgeometry.h"
23 #include "qgsgeometrycollection.h"
24 #include "qgsimagecache.h"
25 #include "qgsrendercontext.h"
26 #include "qgsproject.h"
27 #include "qgssvgcache.h"
28 #include "qgslogger.h"
29 #include "qgscolorramp.h"
30 #include "qgscolorrampimpl.h"
31 #include "qgsunittypes.h"
32 #include "qgsmessagelog.h"
33 #include "qgsapplication.h"
34 #include "qgsimageoperation.h"
35 #include "qgspolygon.h"
36 #include "qgslinestring.h"
38 #include "qgssymbol.h"
39 #include "qgsmarkersymbol.h"
40 #include "qgslinesymbol.h"
41 #include "qgsfeedback.h"
42 #include "qgsgeometryengine.h"
43 
44 #include <QPainter>
45 #include <QFile>
46 #include <QSvgRenderer>
47 #include <QDomDocument>
48 #include <QDomElement>
49 #include <random>
50 
51 #ifndef QT_NO_PRINTER
52 #include <QPrinter>
53 #endif
54 
55 QgsSimpleFillSymbolLayer::QgsSimpleFillSymbolLayer( const QColor &color, Qt::BrushStyle style, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth,
56  Qt::PenJoinStyle penJoinStyle )
57  : mBrushStyle( style )
58  , mStrokeColor( strokeColor )
59  , mStrokeStyle( strokeStyle )
60  , mStrokeWidth( strokeWidth )
61  , mPenJoinStyle( penJoinStyle )
62 {
63  mColor = color;
64 }
65 
67 
69 {
70  mStrokeWidthUnit = unit;
71  mOffsetUnit = unit;
72 }
73 
75 {
77  if ( mOffsetUnit != unit )
78  {
80  }
81  return unit;
82 }
83 
85 {
88 }
89 
91 {
93  mOffsetMapUnitScale = scale;
94 }
95 
97 {
99  {
101  }
102  return QgsMapUnitScale();
103 }
104 
105 void QgsSimpleFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QBrush &brush, QPen &pen, QPen &selPen )
106 {
107  if ( !dataDefinedProperties().hasActiveProperties() )
108  return; // shortcut
109 
110  bool ok;
111 
113  {
116  fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
117  brush.setColor( fillColor );
118  }
120  {
123  if ( !exprVal.isNull() )
124  brush.setStyle( QgsSymbolLayerUtils::decodeBrushStyle( exprVal.toString() ) );
125  }
127  {
130  penColor.setAlphaF( context.opacity() * penColor.alphaF() );
131  pen.setColor( penColor );
132  }
134  {
137  if ( !exprVal.isNull() )
138  {
139  double width = exprVal.toDouble( &ok );
140  if ( ok )
141  {
143  pen.setWidthF( width );
144  selPen.setWidthF( width );
145  }
146  }
147  }
149  {
152  if ( ok )
153  {
154  pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
155  selPen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
156  }
157  }
159  {
162  if ( ok )
163  {
164  pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
165  selPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
166  }
167  }
168 }
169 
170 
172 {
174  Qt::BrushStyle style = DEFAULT_SIMPLEFILL_STYLE;
178  Qt::PenJoinStyle penJoinStyle = DEFAULT_SIMPLEFILL_JOINSTYLE;
179  QPointF offset;
180 
181  if ( props.contains( QStringLiteral( "color" ) ) )
182  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
183  if ( props.contains( QStringLiteral( "style" ) ) )
184  style = QgsSymbolLayerUtils::decodeBrushStyle( props[QStringLiteral( "style" )].toString() );
185  if ( props.contains( QStringLiteral( "color_border" ) ) )
186  {
187  //pre 2.5 projects used "color_border"
188  strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color_border" )].toString() );
189  }
190  else if ( props.contains( QStringLiteral( "outline_color" ) ) )
191  {
192  strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
193  }
194  else if ( props.contains( QStringLiteral( "line_color" ) ) )
195  {
196  strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
197  }
198 
199  if ( props.contains( QStringLiteral( "style_border" ) ) )
200  {
201  //pre 2.5 projects used "style_border"
202  strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "style_border" )].toString() );
203  }
204  else if ( props.contains( QStringLiteral( "outline_style" ) ) )
205  {
206  strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
207  }
208  else if ( props.contains( QStringLiteral( "line_style" ) ) )
209  {
210  strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
211  }
212  if ( props.contains( QStringLiteral( "width_border" ) ) )
213  {
214  //pre 2.5 projects used "width_border"
215  strokeWidth = props[QStringLiteral( "width_border" )].toDouble();
216  }
217  else if ( props.contains( QStringLiteral( "outline_width" ) ) )
218  {
219  strokeWidth = props[QStringLiteral( "outline_width" )].toDouble();
220  }
221  else if ( props.contains( QStringLiteral( "line_width" ) ) )
222  {
223  strokeWidth = props[QStringLiteral( "line_width" )].toDouble();
224  }
225  if ( props.contains( QStringLiteral( "offset" ) ) )
226  offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
227  if ( props.contains( QStringLiteral( "joinstyle" ) ) )
228  penJoinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() );
229 
230  std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, style, strokeColor, strokeStyle, strokeWidth, penJoinStyle );
231  sl->setOffset( offset );
232  if ( props.contains( QStringLiteral( "border_width_unit" ) ) )
233  {
234  sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "border_width_unit" )].toString() ) );
235  }
236  else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
237  {
238  sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
239  }
240  else if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
241  {
242  sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
243  }
244  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
245  sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
246 
247  if ( props.contains( QStringLiteral( "border_width_map_unit_scale" ) ) )
248  sl->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "border_width_map_unit_scale" )].toString() ) );
249  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
250  sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
251 
252  sl->restoreOldDataDefinedProperties( props );
253 
254  return sl.release();
255 }
256 
257 
259 {
260  return QStringLiteral( "SimpleFill" );
261 }
262 
264 {
265  QColor fillColor = mColor;
266  fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
267  mBrush = QBrush( fillColor, mBrushStyle );
268 
269  QColor selColor = context.renderContext().selectionColor();
270  QColor selPenColor = selColor == mColor ? selColor : mStrokeColor;
271  if ( ! SELECTION_IS_OPAQUE )
272  selColor.setAlphaF( context.opacity() );
273  mSelBrush = QBrush( selColor );
274  // N.B. unless a "selection line color" is implemented in addition to the "selection color" option
275  // this would mean symbols with "no fill" look the same whether or not they are selected
276  if ( SELECT_FILL_STYLE )
277  mSelBrush.setStyle( mBrushStyle );
278 
279  QColor strokeColor = mStrokeColor;
280  strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
281  mPen = QPen( strokeColor );
282  mSelPen = QPen( selPenColor );
283  mPen.setStyle( mStrokeStyle );
285  mPen.setJoinStyle( mPenJoinStyle );
286 }
287 
289 {
290  Q_UNUSED( context )
291 }
292 
293 void QgsSimpleFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
294 {
295  QPainter *p = context.renderContext().painter();
296  if ( !p )
297  {
298  return;
299  }
300 
301  QColor fillColor = mColor;
302  fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
303  mBrush.setColor( fillColor );
304  QColor strokeColor = mStrokeColor;
305  strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
306  mPen.setColor( strokeColor );
307 
308  applyDataDefinedSymbology( context, mBrush, mPen, mSelPen );
309 
310  QPointF offset = mOffset;
311 
313  {
315  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
316  bool ok = false;
317  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
318  if ( ok )
319  offset = res;
320  }
321 
322  if ( !offset.isNull() )
323  {
326  p->translate( offset );
327  }
328 
329 #ifndef QT_NO_PRINTER
330  if ( mBrush.style() == Qt::SolidPattern || mBrush.style() == Qt::NoBrush || !dynamic_cast<QPrinter *>( p->device() ) )
331 #endif
332  {
333  p->setPen( context.selected() ? mSelPen : mPen );
334  p->setBrush( context.selected() ? mSelBrush : mBrush );
335  _renderPolygon( p, points, rings, context );
336  }
337 #ifndef QT_NO_PRINTER
338  else
339  {
340  // workaround upstream issue https://github.com/qgis/QGIS/issues/36580
341  // when a non-solid brush is set with opacity, the opacity incorrectly applies to the pen
342  // when exporting to PDF/print devices
343  p->setBrush( context.selected() ? mSelBrush : mBrush );
344  p->setPen( Qt::NoPen );
345  _renderPolygon( p, points, rings, context );
346 
347  p->setPen( context.selected() ? mSelPen : mPen );
348  p->setBrush( Qt::NoBrush );
349  _renderPolygon( p, points, rings, context );
350  }
351 #endif
352 
353  if ( !offset.isNull() )
354  {
355  p->translate( -offset );
356  }
357 }
358 
360 {
361  QVariantMap map;
362  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
363  map[QStringLiteral( "style" )] = QgsSymbolLayerUtils::encodeBrushStyle( mBrushStyle );
364  map[QStringLiteral( "outline_color" )] = QgsSymbolLayerUtils::encodeColor( mStrokeColor );
365  map[QStringLiteral( "outline_style" )] = QgsSymbolLayerUtils::encodePenStyle( mStrokeStyle );
366  map[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
367  map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
368  map[QStringLiteral( "border_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
369  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
370  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
371  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
372  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
373  return map;
374 }
375 
377 {
378  std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( mColor, mBrushStyle, mStrokeColor, mStrokeStyle, mStrokeWidth, mPenJoinStyle );
379  sl->setOffset( mOffset );
380  sl->setOffsetUnit( mOffsetUnit );
381  sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
382  sl->setStrokeWidthUnit( mStrokeWidthUnit );
383  sl->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
384  copyDataDefinedProperties( sl.get() );
385  copyPaintEffect( sl.get() );
386  return sl.release();
387 }
388 
389 void QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
390 {
391  if ( mBrushStyle == Qt::NoBrush && mStrokeStyle == Qt::NoPen )
392  return;
393 
394  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
395  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
396  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
397  element.appendChild( symbolizerElem );
398 
399  // <Geometry>
400  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
401 
402  if ( mBrushStyle != Qt::NoBrush )
403  {
404  // <Fill>
405  QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
406  symbolizerElem.appendChild( fillElem );
408  }
409 
410  if ( mStrokeStyle != Qt::NoPen )
411  {
412  // <Stroke>
413  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
414  symbolizerElem.appendChild( strokeElem );
417  }
418 
419  // <se:Displacement>
422 }
423 
424 QString QgsSimpleFillSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
425 {
426  //brush
427  QString symbolStyle;
428  symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStyleBrush( mColor ) );
429  symbolStyle.append( ';' );
430  //pen
431  symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStylePen( mStrokeWidth, mmScaleFactor, mapUnitScaleFactor, mStrokeColor, mPenJoinStyle ) );
432  return symbolStyle;
433 }
434 
436 {
437  QColor color, strokeColor;
438  Qt::BrushStyle fillStyle;
439  Qt::PenStyle strokeStyle;
440  double strokeWidth;
441 
442  QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
443  QgsSymbolLayerUtils::fillFromSld( fillElem, fillStyle, color );
444 
445  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
447 
448  QPointF offset;
450 
451  QString uom = element.attribute( QStringLiteral( "uom" ), QString() );
455 
456  std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, fillStyle, strokeColor, strokeStyle, strokeWidth );
457  sl->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
458  sl->setOffset( offset );
459  return sl.release();
460 }
461 
463 {
464  double penBleed = context.convertToPainterUnits( mStrokeStyle == Qt::NoPen ? 0 : ( mStrokeWidth / 2.0 ), mStrokeWidthUnit, mStrokeWidthMapUnitScale );
465  double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
466  return penBleed + offsetBleed;
467 }
468 
470 {
471  double width = mStrokeWidth;
473  {
476  }
478 }
479 
481 {
482  QColor c = mStrokeColor;
484  {
487  }
488  return c;
489 }
490 
492 {
493  double angle = mAngle;
495  {
496  context.setOriginalValueVariable( mAngle );
498  }
499  return angle;
500 }
501 
503 {
504  return mStrokeStyle;
505 }
506 
508 {
509  QColor c = mColor;
511  {
513  }
514  return c;
515 }
516 
518 {
519  return mBrushStyle;
520 }
521 
522 //QgsGradientFillSymbolLayer
523 
524 QgsGradientFillSymbolLayer::QgsGradientFillSymbolLayer( const QColor &color, const QColor &color2,
525  Qgis::GradientColorSource colorType, Qgis::GradientType gradientType,
527  : mGradientColorType( colorType )
528  , mGradientType( gradientType )
529  , mCoordinateMode( coordinateMode )
530  , mGradientSpread( spread )
531  , mReferencePoint1( QPointF( 0.5, 0 ) )
532  , mReferencePoint2( QPointF( 0.5, 1 ) )
533 {
534  mColor = color;
535  mColor2 = color2;
536 }
537 
539 {
540  delete mGradientRamp;
541 }
542 
544 {
545  //default to a two-color, linear gradient with feature mode and pad spreading
550  //default to gradient from the default fill color to white
551  QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
552  QPointF referencePoint1 = QPointF( 0.5, 0 );
553  bool refPoint1IsCentroid = false;
554  QPointF referencePoint2 = QPointF( 0.5, 1 );
555  bool refPoint2IsCentroid = false;
556  double angle = 0;
557  QPointF offset;
558 
559  //update gradient properties from props
560  if ( props.contains( QStringLiteral( "type" ) ) )
561  type = static_cast< Qgis::GradientType >( props[QStringLiteral( "type" )].toInt() );
562  if ( props.contains( QStringLiteral( "coordinate_mode" ) ) )
563  coordinateMode = static_cast< Qgis::SymbolCoordinateReference >( props[QStringLiteral( "coordinate_mode" )].toInt() );
564  if ( props.contains( QStringLiteral( "spread" ) ) )
565  gradientSpread = static_cast< Qgis::GradientSpread >( props[QStringLiteral( "spread" )].toInt() );
566  if ( props.contains( QStringLiteral( "color_type" ) ) )
567  colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
568  if ( props.contains( QStringLiteral( "gradient_color" ) ) )
569  {
570  //pre 2.5 projects used "gradient_color"
571  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color" )].toString() );
572  }
573  else if ( props.contains( QStringLiteral( "color" ) ) )
574  {
575  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
576  }
577  if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
578  {
579  color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
580  }
581 
582  if ( props.contains( QStringLiteral( "reference_point1" ) ) )
583  referencePoint1 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point1" )].toString() );
584  if ( props.contains( QStringLiteral( "reference_point1_iscentroid" ) ) )
585  refPoint1IsCentroid = props[QStringLiteral( "reference_point1_iscentroid" )].toInt();
586  if ( props.contains( QStringLiteral( "reference_point2" ) ) )
587  referencePoint2 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point2" )].toString() );
588  if ( props.contains( QStringLiteral( "reference_point2_iscentroid" ) ) )
589  refPoint2IsCentroid = props[QStringLiteral( "reference_point2_iscentroid" )].toInt();
590  if ( props.contains( QStringLiteral( "angle" ) ) )
591  angle = props[QStringLiteral( "angle" )].toDouble();
592 
593  if ( props.contains( QStringLiteral( "offset" ) ) )
594  offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
595 
596  //attempt to create color ramp from props
597  QgsColorRamp *gradientRamp = nullptr;
598  if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
599  {
600  gradientRamp = QgsCptCityColorRamp::create( props );
601  }
602  else
603  {
604  gradientRamp = QgsGradientColorRamp::create( props );
605  }
606 
607  //create a new gradient fill layer with desired properties
608  std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( color, color2, colorType, type, coordinateMode, gradientSpread );
609  sl->setOffset( offset );
610  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
611  sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
612  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
613  sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
614  sl->setReferencePoint1( referencePoint1 );
615  sl->setReferencePoint1IsCentroid( refPoint1IsCentroid );
616  sl->setReferencePoint2( referencePoint2 );
617  sl->setReferencePoint2IsCentroid( refPoint2IsCentroid );
618  sl->setAngle( angle );
619  if ( gradientRamp )
620  sl->setColorRamp( gradientRamp );
621 
622  sl->restoreOldDataDefinedProperties( props );
623 
624  return sl.release();
625 }
626 
628 {
629  delete mGradientRamp;
630  mGradientRamp = ramp;
631 }
632 
634 {
635  return QStringLiteral( "GradientFill" );
636 }
637 
638 void QgsGradientFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, const QPolygonF &points )
639 {
640  if ( !dataDefinedProperties().hasActiveProperties() && !mReferencePoint1IsCentroid && !mReferencePoint2IsCentroid )
641  {
642  //shortcut
645  return;
646  }
647 
648  bool ok;
649 
650  //first gradient color
651  QColor color = mColor;
653  {
656  color.setAlphaF( context.opacity() * color.alphaF() );
657  }
658 
659  //second gradient color
660  QColor color2 = mColor2;
662  {
665  color2.setAlphaF( context.opacity() * color2.alphaF() );
666  }
667 
668  //gradient rotation angle
669  double angle = mAngle;
671  {
672  context.setOriginalValueVariable( mAngle );
674  }
675 
676  //gradient type
679  {
681  if ( ok )
682  {
683  if ( currentType == QObject::tr( "linear" ) )
684  {
686  }
687  else if ( currentType == QObject::tr( "radial" ) )
688  {
690  }
691  else if ( currentType == QObject::tr( "conical" ) )
692  {
694  }
695  }
696  }
697 
698  //coordinate mode
701  {
702  QString currentCoordMode = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyCoordinateMode, context.renderContext().expressionContext(), QString(), &ok );
703  if ( ok )
704  {
705  if ( currentCoordMode == QObject::tr( "feature" ) )
706  {
708  }
709  else if ( currentCoordMode == QObject::tr( "viewport" ) )
710  {
712  }
713  }
714  }
715 
716  //gradient spread
719  {
721  if ( ok )
722  {
723  if ( currentSpread == QObject::tr( "pad" ) )
724  {
725  spread = Qgis::GradientSpread::Pad;
726  }
727  else if ( currentSpread == QObject::tr( "repeat" ) )
728  {
730  }
731  else if ( currentSpread == QObject::tr( "reflect" ) )
732  {
734  }
735  }
736  }
737 
738  //reference point 1 x & y
739  double refPoint1X = mReferencePoint1.x();
741  {
742  context.setOriginalValueVariable( refPoint1X );
744  }
745  double refPoint1Y = mReferencePoint1.y();
747  {
748  context.setOriginalValueVariable( refPoint1Y );
750  }
751  bool refPoint1IsCentroid = mReferencePoint1IsCentroid;
753  {
754  context.setOriginalValueVariable( refPoint1IsCentroid );
756  }
757 
758  //reference point 2 x & y
759  double refPoint2X = mReferencePoint2.x();
761  {
762  context.setOriginalValueVariable( refPoint2X );
764  }
765  double refPoint2Y = mReferencePoint2.y();
767  {
768  context.setOriginalValueVariable( refPoint2Y );
770  }
771  bool refPoint2IsCentroid = mReferencePoint2IsCentroid;
773  {
774  context.setOriginalValueVariable( refPoint2IsCentroid );
776  }
777 
778  if ( refPoint1IsCentroid || refPoint2IsCentroid )
779  {
780  //either the gradient is starting or ending at a centroid, so calculate it
782  //centroid coordinates need to be scaled to a range [0, 1] relative to polygon bounds
783  QRectF bbox = points.boundingRect();
784  double centroidX = ( centroid.x() - bbox.left() ) / bbox.width();
785  double centroidY = ( centroid.y() - bbox.top() ) / bbox.height();
786 
787  if ( refPoint1IsCentroid )
788  {
789  refPoint1X = centroidX;
790  refPoint1Y = centroidY;
791  }
792  if ( refPoint2IsCentroid )
793  {
794  refPoint2X = centroidX;
795  refPoint2Y = centroidY;
796  }
797  }
798 
799  //update gradient with data defined values
801  spread, QPointF( refPoint1X, refPoint1Y ), QPointF( refPoint2X, refPoint2Y ), angle );
802 }
803 
804 QPointF QgsGradientFillSymbolLayer::rotateReferencePoint( QPointF refPoint, double angle )
805 {
806  //rotate a reference point by a specified angle around the point (0.5, 0.5)
807 
808  //create a line from the centrepoint of a rectangle bounded by (0, 0) and (1, 1) to the reference point
809  QLineF refLine = QLineF( QPointF( 0.5, 0.5 ), refPoint );
810  //rotate this line by the current rotation angle
811  refLine.setAngle( refLine.angle() + angle );
812  //get new end point of line
813  QPointF rotatedReferencePoint = refLine.p2();
814  //make sure coords of new end point is within [0, 1]
815  if ( rotatedReferencePoint.x() > 1 )
816  rotatedReferencePoint.setX( 1 );
817  if ( rotatedReferencePoint.x() < 0 )
818  rotatedReferencePoint.setX( 0 );
819  if ( rotatedReferencePoint.y() > 1 )
820  rotatedReferencePoint.setY( 1 );
821  if ( rotatedReferencePoint.y() < 0 )
822  rotatedReferencePoint.setY( 0 );
823 
824  return rotatedReferencePoint;
825 }
826 
827 void QgsGradientFillSymbolLayer::applyGradient( const QgsSymbolRenderContext &context, QBrush &brush,
828  const QColor &color, const QColor &color2, Qgis::GradientColorSource gradientColorType,
829  QgsColorRamp *gradientRamp, Qgis::GradientType gradientType,
830  Qgis::SymbolCoordinateReference coordinateMode, Qgis::GradientSpread gradientSpread,
831  QPointF referencePoint1, QPointF referencePoint2, const double angle )
832 {
833  //update alpha of gradient colors
834  QColor fillColor = color;
835  fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
836  QColor fillColor2 = color2;
837  fillColor2.setAlphaF( context.opacity() * fillColor2.alphaF() );
838 
839  //rotate reference points
840  QPointF rotatedReferencePoint1 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint1, angle ) : referencePoint1;
841  QPointF rotatedReferencePoint2 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint2, angle ) : referencePoint2;
842 
843  //create a QGradient with the desired properties
844  QGradient gradient;
845  switch ( gradientType )
846  {
848  gradient = QLinearGradient( rotatedReferencePoint1, rotatedReferencePoint2 );
849  break;
851  gradient = QRadialGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).length() );
852  break;
854  gradient = QConicalGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).angle() );
855  break;
856  }
857  switch ( coordinateMode )
858  {
860  gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
861  break;
863  gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
864  break;
865  }
866  switch ( gradientSpread )
867  {
869  gradient.setSpread( QGradient::PadSpread );
870  break;
872  gradient.setSpread( QGradient::ReflectSpread );
873  break;
875  gradient.setSpread( QGradient::RepeatSpread );
876  break;
877  }
878 
879  //add stops to gradient
881  ( gradientRamp->type() == QgsGradientColorRamp::typeString() || gradientRamp->type() == QgsCptCityColorRamp::typeString() ) )
882  {
883  //color ramp gradient
884  QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( gradientRamp );
885  gradRamp->addStopsToGradient( &gradient, context.opacity() );
886  }
887  else
888  {
889  //two color gradient
890  gradient.setColorAt( 0.0, fillColor );
891  gradient.setColorAt( 1.0, fillColor2 );
892  }
893 
894  //update QBrush use gradient
895  brush = QBrush( gradient );
896 }
897 
899 {
900  QColor selColor = context.renderContext().selectionColor();
901  if ( ! SELECTION_IS_OPAQUE )
902  selColor.setAlphaF( context.opacity() );
903  mSelBrush = QBrush( selColor );
904 }
905 
907 {
908  Q_UNUSED( context )
909 }
910 
911 void QgsGradientFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
912 {
913  QPainter *p = context.renderContext().painter();
914  if ( !p )
915  {
916  return;
917  }
918 
919  applyDataDefinedSymbology( context, points );
920 
921  p->setBrush( context.selected() ? mSelBrush : mBrush );
922  p->setPen( Qt::NoPen );
923 
924  QPointF offset = mOffset;
926  {
928  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
929  bool ok = false;
930  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
931  if ( ok )
932  offset = res;
933  }
934 
935  if ( !offset.isNull() )
936  {
939  p->translate( offset );
940  }
941 
942  _renderPolygon( p, points, rings, context );
943 
944  if ( !offset.isNull() )
945  {
946  p->translate( -offset );
947  }
948 }
949 
951 {
952  QVariantMap map;
953  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
954  map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
955  map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
956  map[QStringLiteral( "type" )] = QString::number( static_cast<int>( mGradientType ) );
957  map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
958  map[QStringLiteral( "spread" )] = QString::number( static_cast< int >( mGradientSpread ) );
959  map[QStringLiteral( "reference_point1" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint1 );
960  map[QStringLiteral( "reference_point1_iscentroid" )] = QString::number( mReferencePoint1IsCentroid );
961  map[QStringLiteral( "reference_point2" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint2 );
962  map[QStringLiteral( "reference_point2_iscentroid" )] = QString::number( mReferencePoint2IsCentroid );
963  map[QStringLiteral( "angle" )] = QString::number( mAngle );
964  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
965  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
966  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
967  if ( mGradientRamp )
968  {
969 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
970  map.unite( mGradientRamp->properties() );
971 #else
972  map.insert( mGradientRamp->properties() );
973 #endif
974  }
975  return map;
976 }
977 
979 {
980  std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( mColor, mColor2, mGradientColorType, mGradientType, mCoordinateMode, mGradientSpread );
981  if ( mGradientRamp )
982  sl->setColorRamp( mGradientRamp->clone() );
983  sl->setReferencePoint1( mReferencePoint1 );
984  sl->setReferencePoint1IsCentroid( mReferencePoint1IsCentroid );
985  sl->setReferencePoint2( mReferencePoint2 );
986  sl->setReferencePoint2IsCentroid( mReferencePoint2IsCentroid );
987  sl->setAngle( mAngle );
988  sl->setOffset( mOffset );
989  sl->setOffsetUnit( mOffsetUnit );
990  sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
991  copyDataDefinedProperties( sl.get() );
992  copyPaintEffect( sl.get() );
993  return sl.release();
994 }
995 
997 {
998  double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
999  return offsetBleed;
1000 }
1001 
1003 {
1004  return true;
1005 }
1006 
1008 {
1009  mOffsetUnit = unit;
1010 }
1011 
1013 {
1014  return mOffsetUnit;
1015 }
1016 
1018 {
1020 }
1021 
1023 {
1024  mOffsetMapUnitScale = scale;
1025 }
1026 
1028 {
1029  return mOffsetMapUnitScale;
1030 }
1031 
1032 //QgsShapeburstFillSymbolLayer
1033 
1035  int blurRadius, bool useWholeShape, double maxDistance )
1036  : mBlurRadius( blurRadius )
1037  , mUseWholeShape( useWholeShape )
1038  , mMaxDistance( maxDistance )
1039  , mColorType( colorType )
1040  , mColor2( color2 )
1041 {
1042  mColor = color;
1043 }
1044 
1046 
1048 {
1049  //default to a two-color gradient
1051  QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
1052  int blurRadius = 0;
1053  bool useWholeShape = true;
1054  double maxDistance = 5;
1055  QPointF offset;
1056 
1057  //update fill properties from props
1058  if ( props.contains( QStringLiteral( "color_type" ) ) )
1059  {
1060  colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
1061  }
1062  if ( props.contains( QStringLiteral( "shapeburst_color" ) ) )
1063  {
1064  //pre 2.5 projects used "shapeburst_color"
1065  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color" )].toString() );
1066  }
1067  else if ( props.contains( QStringLiteral( "color" ) ) )
1068  {
1069  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
1070  }
1071 
1072  if ( props.contains( QStringLiteral( "shapeburst_color2" ) ) )
1073  {
1074  //pre 2.5 projects used "shapeburst_color2"
1075  color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color2" )].toString() );
1076  }
1077  else if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
1078  {
1079  color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
1080  }
1081  if ( props.contains( QStringLiteral( "blur_radius" ) ) )
1082  {
1083  blurRadius = props[QStringLiteral( "blur_radius" )].toInt();
1084  }
1085  if ( props.contains( QStringLiteral( "use_whole_shape" ) ) )
1086  {
1087  useWholeShape = props[QStringLiteral( "use_whole_shape" )].toInt();
1088  }
1089  if ( props.contains( QStringLiteral( "max_distance" ) ) )
1090  {
1091  maxDistance = props[QStringLiteral( "max_distance" )].toDouble();
1092  }
1093  if ( props.contains( QStringLiteral( "offset" ) ) )
1094  {
1095  offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
1096  }
1097 
1098  //attempt to create color ramp from props
1099  QgsColorRamp *gradientRamp = nullptr;
1100  if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
1101  {
1102  gradientRamp = QgsCptCityColorRamp::create( props );
1103  }
1104  else
1105  {
1106  gradientRamp = QgsGradientColorRamp::create( props );
1107  }
1108 
1109  //create a new shapeburst fill layer with desired properties
1110  std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( color, color2, colorType, blurRadius, useWholeShape, maxDistance );
1111  sl->setOffset( offset );
1112  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1113  {
1114  sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1115  }
1116  if ( props.contains( QStringLiteral( "distance_unit" ) ) )
1117  {
1118  sl->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "distance_unit" )].toString() ) );
1119  }
1120  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1121  {
1122  sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1123  }
1124  if ( props.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
1125  {
1126  sl->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
1127  }
1128  if ( props.contains( QStringLiteral( "ignore_rings" ) ) )
1129  {
1130  sl->setIgnoreRings( props[QStringLiteral( "ignore_rings" )].toInt() );
1131  }
1132  if ( gradientRamp )
1133  {
1134  sl->setColorRamp( gradientRamp );
1135  }
1136 
1137  sl->restoreOldDataDefinedProperties( props );
1138 
1139  return sl.release();
1140 }
1141 
1143 {
1144  return QStringLiteral( "ShapeburstFill" );
1145 }
1146 
1148 {
1149  if ( mGradientRamp.get() == ramp )
1150  return;
1151 
1152  mGradientRamp.reset( ramp );
1153 }
1154 
1155 void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
1156  double &maxDistance, bool &ignoreRings )
1157 {
1158  //first gradient color
1159  color = mColor;
1161  {
1164  }
1165 
1166  //second gradient color
1167  color2 = mColor2;
1169  {
1172  }
1173 
1174  //blur radius
1175  blurRadius = mBlurRadius;
1177  {
1178  context.setOriginalValueVariable( mBlurRadius );
1180  }
1181 
1182  //use whole shape
1183  useWholeShape = mUseWholeShape;
1185  {
1186  context.setOriginalValueVariable( mUseWholeShape );
1188  }
1189 
1190  //max distance
1191  maxDistance = mMaxDistance;
1193  {
1194  context.setOriginalValueVariable( mMaxDistance );
1196  }
1197 
1198  //ignore rings
1199  ignoreRings = mIgnoreRings;
1201  {
1202  context.setOriginalValueVariable( mIgnoreRings );
1204  }
1205 
1206 }
1207 
1209 {
1210  //TODO - check this
1211  QColor selColor = context.renderContext().selectionColor();
1212  if ( ! SELECTION_IS_OPAQUE )
1213  selColor.setAlphaF( context.opacity() );
1214  mSelBrush = QBrush( selColor );
1215 }
1216 
1218 {
1219  Q_UNUSED( context )
1220 }
1221 
1222 void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1223 {
1224  QPainter *p = context.renderContext().painter();
1225  if ( !p )
1226  {
1227  return;
1228  }
1229 
1230  if ( context.selected() )
1231  {
1232  //feature is selected, draw using selection style
1233  p->setBrush( mSelBrush );
1234  QPointF offset = mOffset;
1235 
1237  {
1239  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1240  bool ok = false;
1241  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1242  if ( ok )
1243  offset = res;
1244  }
1245 
1246  if ( !offset.isNull() )
1247  {
1248  offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1249  offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1250  p->translate( offset );
1251  }
1252  _renderPolygon( p, points, rings, context );
1253  if ( !offset.isNull() )
1254  {
1255  p->translate( -offset );
1256  }
1257  return;
1258  }
1259 
1260  QColor color1, color2;
1261  int blurRadius;
1262  bool useWholeShape;
1263  double maxDistance;
1264  bool ignoreRings;
1265  //calculate data defined symbology
1266  applyDataDefinedSymbology( context, color1, color2, blurRadius, useWholeShape, maxDistance, ignoreRings );
1267 
1268  //calculate max distance for shapeburst fill to extend from polygon boundary, in pixels
1269  int outputPixelMaxDist = 0;
1270  if ( !useWholeShape && !qgsDoubleNear( maxDistance, 0.0 ) )
1271  {
1272  //convert max distance to pixels
1273  outputPixelMaxDist = static_cast< int >( std::round( context.renderContext().convertToPainterUnits( maxDistance, mDistanceUnit, mDistanceMapUnitScale ) ) );
1274  }
1275 
1276  //if we are using the two color mode, create a gradient ramp
1277  std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
1278  if ( mColorType == Qgis::GradientColorSource::SimpleTwoColor )
1279  {
1280  twoColorGradientRamp = std::make_unique< QgsGradientColorRamp >( color1, color2 );
1281  }
1282 
1283  //no stroke for shapeburst fills
1284  p->setPen( QPen( Qt::NoPen ) );
1285 
1286  //calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
1287  int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
1288  //create a QImage to draw shapeburst in
1289  int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1290  int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1291  int imWidth = pointsWidth + ( sideBuffer * 2 );
1292  int imHeight = pointsHeight + ( sideBuffer * 2 );
1293 
1294  // these are all potentially very expensive operations, so check regularly if the job is canceled and abort responsively
1295  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1296  return;
1297 
1298  std::unique_ptr< QImage > fillImage = std::make_unique< QImage >( imWidth,
1299  imHeight, QImage::Format_ARGB32_Premultiplied );
1300  if ( fillImage->isNull() )
1301  {
1302  QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1303  return;
1304  }
1305 
1306  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1307  return;
1308 
1309  //also create an image to store the alpha channel
1310  std::unique_ptr< QImage > alphaImage = std::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1311  if ( alphaImage->isNull() )
1312  {
1313  QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1314  return;
1315  }
1316 
1317  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1318  return;
1319 
1320  //Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
1321  //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
1322  //polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
1323  fillImage->fill( Qt::black );
1324 
1325  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1326  return;
1327 
1328  //initially fill the alpha channel image with a transparent color
1329  alphaImage->fill( Qt::transparent );
1330 
1331  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1332  return;
1333 
1334  //now, draw the polygon in the alpha channel image
1335  QPainter imgPainter;
1336  imgPainter.begin( alphaImage.get() );
1337  imgPainter.setRenderHint( QPainter::Antialiasing, true );
1338  imgPainter.setBrush( QBrush( Qt::white ) );
1339  imgPainter.setPen( QPen( Qt::black ) );
1340  imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1341  _renderPolygon( &imgPainter, points, rings, context );
1342  imgPainter.end();
1343 
1344  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1345  return;
1346 
1347  //now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
1348  //(this avoids calling _renderPolygon twice, since that can be slow)
1349  imgPainter.begin( fillImage.get() );
1350  if ( !ignoreRings )
1351  {
1352  imgPainter.drawImage( 0, 0, *alphaImage );
1353  }
1354  else
1355  {
1356  //using ignore rings mode, so the alpha image can't be used
1357  //directly as the alpha channel contains polygon rings and we need
1358  //to draw now without any rings
1359  imgPainter.setBrush( QBrush( Qt::white ) );
1360  imgPainter.setPen( QPen( Qt::black ) );
1361  imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1362  _renderPolygon( &imgPainter, points, nullptr, context );
1363  }
1364  imgPainter.end();
1365 
1366  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1367  return;
1368 
1369  //apply distance transform to image, uses the current color ramp to calculate final pixel colors
1370  double *dtArray = distanceTransform( fillImage.get(), context.renderContext() );
1371 
1372  //copy distance transform values back to QImage, shading by appropriate color ramp
1373  dtArrayToQImage( dtArray, fillImage.get(), mColorType == Qgis::GradientColorSource::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
1374  context.renderContext(), useWholeShape, outputPixelMaxDist );
1375  if ( context.opacity() < 1 )
1376  {
1377  QgsImageOperation::multiplyOpacity( *fillImage, context.opacity(), context.renderContext().feedback() );
1378  }
1379 
1380  //clean up some variables
1381  delete [] dtArray;
1382 
1383  //apply blur if desired
1384  if ( blurRadius > 0 )
1385  {
1386  QgsImageOperation::stackBlur( *fillImage, blurRadius, false, context.renderContext().feedback() );
1387  }
1388 
1389  //apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1390  imgPainter.begin( fillImage.get() );
1391  imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
1392  imgPainter.drawImage( 0, 0, *alphaImage );
1393  imgPainter.end();
1394  //we're finished with the alpha channel image now
1395  alphaImage.reset();
1396 
1397  //draw shapeburst image in correct place in the destination painter
1398 
1399  QgsScopedQPainterState painterState( p );
1400  QPointF offset = mOffset;
1402  {
1404  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1405  bool ok = false;
1406  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1407  if ( ok )
1408  offset = res;
1409  }
1410  if ( !offset.isNull() )
1411  {
1412  offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1413  offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1414  p->translate( offset );
1415  }
1416 
1417  p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
1418 
1419  if ( !offset.isNull() )
1420  {
1421  p->translate( -offset );
1422  }
1423 }
1424 
1425 //fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
1426 
1427 /* distance transform of a 1d function using squared distance */
1428 void QgsShapeburstFillSymbolLayer::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
1429 {
1430  int k = 0;
1431  v[0] = 0;
1432  z[0] = -INF;
1433  z[1] = + INF;
1434  for ( int q = 1; q <= n - 1; q++ )
1435  {
1436  double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1437  while ( s <= z[k] )
1438  {
1439  k--;
1440  s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1441  }
1442  k++;
1443  v[k] = q;
1444  z[k] = s;
1445  z[k + 1] = + INF;
1446  }
1447 
1448  k = 0;
1449  for ( int q = 0; q <= n - 1; q++ )
1450  {
1451  while ( z[k + 1] < q )
1452  k++;
1453  d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
1454  }
1455 }
1456 
1457 /* distance transform of 2d function using squared distance */
1458 void QgsShapeburstFillSymbolLayer::distanceTransform2d( double *im, int width, int height, QgsRenderContext &context )
1459 {
1460  int maxDimension = std::max( width, height );
1461  double *f = new double[ maxDimension ];
1462  int *v = new int[ maxDimension ];
1463  double *z = new double[ maxDimension + 1 ];
1464  double *d = new double[ maxDimension ];
1465 
1466  // transform along columns
1467  for ( int x = 0; x < width; x++ )
1468  {
1469  if ( context.renderingStopped() )
1470  break;
1471 
1472  for ( int y = 0; y < height; y++ )
1473  {
1474  f[y] = im[ x + y * width ];
1475  }
1476  distanceTransform1d( f, height, v, z, d );
1477  for ( int y = 0; y < height; y++ )
1478  {
1479  im[ x + y * width ] = d[y];
1480  }
1481  }
1482 
1483  // transform along rows
1484  for ( int y = 0; y < height; y++ )
1485  {
1486  if ( context.renderingStopped() )
1487  break;
1488 
1489  for ( int x = 0; x < width; x++ )
1490  {
1491  f[x] = im[ x + y * width ];
1492  }
1493  distanceTransform1d( f, width, v, z, d );
1494  for ( int x = 0; x < width; x++ )
1495  {
1496  im[ x + y * width ] = d[x];
1497  }
1498  }
1499 
1500  delete [] d;
1501  delete [] f;
1502  delete [] v;
1503  delete [] z;
1504 }
1505 
1506 /* distance transform of a binary QImage */
1507 double *QgsShapeburstFillSymbolLayer::distanceTransform( QImage *im, QgsRenderContext &context )
1508 {
1509  int width = im->width();
1510  int height = im->height();
1511 
1512  double *dtArray = new double[width * height];
1513 
1514  //load qImage to array
1515  QRgb tmpRgb;
1516  int idx = 0;
1517  for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1518  {
1519  if ( context.renderingStopped() )
1520  break;
1521 
1522  const QRgb *scanLine = reinterpret_cast< const QRgb * >( im->constScanLine( heightIndex ) );
1523  for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1524  {
1525  tmpRgb = scanLine[widthIndex];
1526  if ( qRed( tmpRgb ) == 0 )
1527  {
1528  //black pixel, so zero distance
1529  dtArray[ idx ] = 0;
1530  }
1531  else
1532  {
1533  //white pixel, so initially set distance as infinite
1534  dtArray[ idx ] = INF;
1535  }
1536  idx++;
1537  }
1538  }
1539 
1540  //calculate squared distance transform
1541  distanceTransform2d( dtArray, width, height, context );
1542 
1543  return dtArray;
1544 }
1545 
1546 void QgsShapeburstFillSymbolLayer::dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, QgsRenderContext &context, bool useWholeShape, int maxPixelDistance )
1547 {
1548  int width = im->width();
1549  int height = im->height();
1550 
1551  //find maximum distance value
1552  double maxDistanceValue;
1553 
1554  if ( useWholeShape )
1555  {
1556  //no max distance specified in symbol properties, so calculate from maximum value in distance transform results
1557  double dtMaxValue = array[0];
1558  for ( int i = 1; i < ( width * height ); ++i )
1559  {
1560  if ( array[i] > dtMaxValue )
1561  {
1562  dtMaxValue = array[i];
1563  }
1564  }
1565 
1566  //values in distance transform are squared
1567  maxDistanceValue = std::sqrt( dtMaxValue );
1568  }
1569  else
1570  {
1571  //use max distance set in symbol properties
1572  maxDistanceValue = maxPixelDistance;
1573  }
1574 
1575  //update the pixels in the provided QImage
1576  int idx = 0;
1577  double squaredVal = 0;
1578  double pixVal = 0;
1579 
1580  for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1581  {
1582  if ( context.renderingStopped() )
1583  break;
1584 
1585  QRgb *scanLine = reinterpret_cast< QRgb * >( im->scanLine( heightIndex ) );
1586  for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1587  {
1588  //result of distance transform
1589  squaredVal = array[idx];
1590 
1591  //scale result to fit in the range [0, 1]
1592  if ( maxDistanceValue > 0 )
1593  {
1594  pixVal = squaredVal > 0 ? std::min( ( std::sqrt( squaredVal ) / maxDistanceValue ), 1.0 ) : 0;
1595  }
1596  else
1597  {
1598  pixVal = 1.0;
1599  }
1600 
1601  //convert value to color from ramp
1602  //premultiply ramp color since we are storing this in a ARGB32_Premultiplied QImage
1603  scanLine[widthIndex] = qPremultiply( ramp->color( pixVal ).rgba() );
1604  idx++;
1605  }
1606  }
1607 }
1608 
1610 {
1611  QVariantMap map;
1612  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
1613  map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
1614  map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mColorType ) );
1615  map[QStringLiteral( "blur_radius" )] = QString::number( mBlurRadius );
1616  map[QStringLiteral( "use_whole_shape" )] = QString::number( mUseWholeShape );
1617  map[QStringLiteral( "max_distance" )] = QString::number( mMaxDistance );
1618  map[QStringLiteral( "distance_unit" )] = QgsUnitTypes::encodeUnit( mDistanceUnit );
1619  map[QStringLiteral( "distance_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale );
1620  map[QStringLiteral( "ignore_rings" )] = QString::number( mIgnoreRings );
1621  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1622  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1623  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1624  if ( mGradientRamp )
1625  {
1626 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1627  map.unite( mGradientRamp->properties() );
1628 #else
1629  map.insert( mGradientRamp->properties() );
1630 #endif
1631  }
1632 
1633  return map;
1634 }
1635 
1637 {
1638  std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( mColor, mColor2, mColorType, mBlurRadius, mUseWholeShape, mMaxDistance );
1639  if ( mGradientRamp )
1640  {
1641  sl->setColorRamp( mGradientRamp->clone() );
1642  }
1643  sl->setDistanceUnit( mDistanceUnit );
1644  sl->setDistanceMapUnitScale( mDistanceMapUnitScale );
1645  sl->setIgnoreRings( mIgnoreRings );
1646  sl->setOffset( mOffset );
1647  sl->setOffsetUnit( mOffsetUnit );
1648  sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1649  copyDataDefinedProperties( sl.get() );
1650  copyPaintEffect( sl.get() );
1651  return sl.release();
1652 }
1653 
1655 {
1656  double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1657  return offsetBleed;
1658 }
1659 
1661 {
1662  return true;
1663 }
1664 
1666 {
1667  mDistanceUnit = unit;
1668  mOffsetUnit = unit;
1669 }
1670 
1672 {
1673  if ( mDistanceUnit == mOffsetUnit )
1674  {
1675  return mDistanceUnit;
1676  }
1678 }
1679 
1681 {
1682  return mDistanceUnit == QgsUnitTypes::RenderMapUnits || mDistanceUnit == QgsUnitTypes::RenderMetersInMapUnits
1683  || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
1684 }
1685 
1687 {
1688  mDistanceMapUnitScale = scale;
1689  mOffsetMapUnitScale = scale;
1690 }
1691 
1693 {
1694  if ( mDistanceMapUnitScale == mOffsetMapUnitScale )
1695  {
1696  return mDistanceMapUnitScale;
1697  }
1698  return QgsMapUnitScale();
1699 }
1700 
1701 
1702 //QgsImageFillSymbolLayer
1703 
1705 {
1706 }
1707 
1709 
1710 void QgsImageFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1711 {
1712  QPainter *p = context.renderContext().painter();
1713  if ( !p )
1714  {
1715  return;
1716  }
1717 
1718  mNextAngle = mAngle;
1719  applyDataDefinedSettings( context );
1720 
1721  p->setPen( QPen( Qt::NoPen ) );
1722 
1723  QTransform bkTransform = mBrush.transform();
1724  if ( applyBrushTransformFromContext( &context ) && !context.renderContext().textureOrigin().isNull() )
1725  {
1726  QPointF leftCorner = context.renderContext().textureOrigin();
1727  QTransform t = mBrush.transform();
1728  t.translate( leftCorner.x(), leftCorner.y() );
1729  mBrush.setTransform( t );
1730  }
1731  else
1732  {
1733  QTransform t = mBrush.transform();
1734  t.translate( 0, 0 );
1735  mBrush.setTransform( t );
1736  }
1737 
1738  if ( context.selected() )
1739  {
1740  QColor selColor = context.renderContext().selectionColor();
1741  p->setBrush( QBrush( selColor ) );
1742  _renderPolygon( p, points, rings, context );
1743  }
1744 
1745  if ( !qgsDoubleNear( mNextAngle, 0.0 ) )
1746  {
1747  QTransform t = mBrush.transform();
1748  t.rotate( mNextAngle );
1749  mBrush.setTransform( t );
1750  }
1751  p->setBrush( mBrush );
1752  _renderPolygon( p, points, rings, context );
1753 
1754  mBrush.setTransform( bkTransform );
1755 }
1756 
1758 {
1759  mStrokeWidthUnit = unit;
1760 }
1761 
1763 {
1764  return mStrokeWidthUnit;
1765 }
1766 
1768 {
1769  mStrokeWidthMapUnitScale = scale;
1770 }
1771 
1773 {
1774  return mStrokeWidthMapUnitScale;
1775 }
1776 
1778 {
1779  double width = mStrokeWidth;
1781  {
1784  }
1786 }
1787 
1789 {
1790  return Qt::SolidLine;
1791 #if 0
1792  if ( !mStroke )
1793  {
1794  return Qt::SolidLine;
1795  }
1796  else
1797  {
1798  return mStroke->dxfPenStyle();
1799  }
1800 #endif //0
1801 }
1802 
1804 {
1805  QVariantMap map;
1806  map.insert( QStringLiteral( "coordinate_reference" ), QgsSymbolLayerUtils::encodeCoordinateReference( mCoordinateReference ) );
1807  return map;
1808 }
1809 
1811 {
1812  //coordinate reference
1815  {
1816  bool ok = false;
1818  if ( ok )
1819  {
1821  if ( !ok )
1823  }
1824  }
1825 
1827 }
1828 
1829 
1830 //QgsSVGFillSymbolLayer
1831 
1832 QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString &svgFilePath, double width, double angle )
1834  , mPatternWidth( width )
1835 {
1836  mStrokeWidth = 0.3;
1837  mAngle = angle;
1838  mColor = QColor( 255, 255, 255 );
1840 }
1841 
1842 QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray &svgData, double width, double angle )
1844  , mPatternWidth( width )
1845  , mSvgData( svgData )
1846 {
1847  storeViewBox();
1848  mStrokeWidth = 0.3;
1849  mAngle = angle;
1850  mColor = QColor( 255, 255, 255 );
1851  setDefaultSvgParams();
1852 }
1853 
1855 
1857 {
1859  mPatternWidthUnit = unit;
1860  mSvgStrokeWidthUnit = unit;
1861  mStrokeWidthUnit = unit;
1862  if ( mStroke )
1863  mStroke->setOutputUnit( unit );
1864 }
1865 
1867 {
1869  if ( mPatternWidthUnit != unit || mSvgStrokeWidthUnit != unit || mStrokeWidthUnit != unit )
1870  {
1872  }
1873  return unit;
1874 }
1875 
1877 {
1879  mPatternWidthMapUnitScale = scale;
1880  mSvgStrokeWidthMapUnitScale = scale;
1881  mStrokeWidthMapUnitScale = scale;
1882 }
1883 
1885 {
1886  if ( QgsImageFillSymbolLayer::mapUnitScale() == mPatternWidthMapUnitScale &&
1887  mPatternWidthMapUnitScale == mSvgStrokeWidthMapUnitScale &&
1888  mSvgStrokeWidthMapUnitScale == mStrokeWidthMapUnitScale )
1889  {
1890  return mPatternWidthMapUnitScale;
1891  }
1892  return QgsMapUnitScale();
1893 }
1894 
1895 void QgsSVGFillSymbolLayer::setSvgFilePath( const QString &svgPath )
1896 {
1897  mSvgData = QgsApplication::svgCache()->getImageData( svgPath );
1898  storeViewBox();
1899 
1900  mSvgFilePath = svgPath;
1901  setDefaultSvgParams();
1902 }
1903 
1904 QgsSymbolLayer *QgsSVGFillSymbolLayer::create( const QVariantMap &properties )
1905 {
1906  QByteArray data;
1907  double width = 20;
1908  QString svgFilePath;
1909  double angle = 0.0;
1910 
1911  if ( properties.contains( QStringLiteral( "width" ) ) )
1912  {
1913  width = properties[QStringLiteral( "width" )].toDouble();
1914  }
1915  if ( properties.contains( QStringLiteral( "svgFile" ) ) )
1916  {
1917  svgFilePath = properties[QStringLiteral( "svgFile" )].toString();
1918  }
1919  if ( properties.contains( QStringLiteral( "angle" ) ) )
1920  {
1921  angle = properties[QStringLiteral( "angle" )].toDouble();
1922  }
1923 
1924  std::unique_ptr< QgsSVGFillSymbolLayer > symbolLayer;
1925  if ( !svgFilePath.isEmpty() )
1926  {
1927  symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( svgFilePath, width, angle );
1928  }
1929  else
1930  {
1931  if ( properties.contains( QStringLiteral( "data" ) ) )
1932  {
1933  data = QByteArray::fromHex( properties[QStringLiteral( "data" )].toString().toLocal8Bit() );
1934  }
1935  symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( data, width, angle );
1936  }
1937 
1938  //svg parameters
1939  if ( properties.contains( QStringLiteral( "svgFillColor" ) ) )
1940  {
1941  //pre 2.5 projects used "svgFillColor"
1942  symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgFillColor" )].toString() ) );
1943  }
1944  else if ( properties.contains( QStringLiteral( "color" ) ) )
1945  {
1946  symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
1947  }
1948  if ( properties.contains( QStringLiteral( "svgOutlineColor" ) ) )
1949  {
1950  //pre 2.5 projects used "svgOutlineColor"
1951  symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgOutlineColor" )].toString() ) );
1952  }
1953  else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
1954  {
1955  symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() ) );
1956  }
1957  else if ( properties.contains( QStringLiteral( "line_color" ) ) )
1958  {
1959  symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() ) );
1960  }
1961  if ( properties.contains( QStringLiteral( "svgOutlineWidth" ) ) )
1962  {
1963  //pre 2.5 projects used "svgOutlineWidth"
1964  symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "svgOutlineWidth" )].toDouble() );
1965  }
1966  else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
1967  {
1968  symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "outline_width" )].toDouble() );
1969  }
1970  else if ( properties.contains( QStringLiteral( "line_width" ) ) )
1971  {
1972  symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "line_width" )].toDouble() );
1973  }
1974 
1975  //units
1976  if ( properties.contains( QStringLiteral( "pattern_width_unit" ) ) )
1977  {
1978  symbolLayer->setPatternWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "pattern_width_unit" )].toString() ) );
1979  }
1980  if ( properties.contains( QStringLiteral( "pattern_width_map_unit_scale" ) ) )
1981  {
1982  symbolLayer->setPatternWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "pattern_width_map_unit_scale" )].toString() ) );
1983  }
1984  if ( properties.contains( QStringLiteral( "svg_outline_width_unit" ) ) )
1985  {
1986  symbolLayer->setSvgStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "svg_outline_width_unit" )].toString() ) );
1987  }
1988  if ( properties.contains( QStringLiteral( "svg_outline_width_map_unit_scale" ) ) )
1989  {
1990  symbolLayer->setSvgStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "svg_outline_width_map_unit_scale" )].toString() ) );
1991  }
1992  if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
1993  {
1994  symbolLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
1995  }
1996  if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
1997  {
1998  symbolLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
1999  }
2000 
2001  if ( properties.contains( QStringLiteral( "parameters" ) ) )
2002  {
2003  const QVariantMap parameters = properties[QStringLiteral( "parameters" )].toMap();
2004  symbolLayer->setParameters( QgsProperty::variantMapToPropertyMap( parameters ) );
2005  }
2006 
2007  symbolLayer->restoreOldDataDefinedProperties( properties );
2008 
2009  return symbolLayer.release();
2010 }
2011 
2012 void QgsSVGFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2013 {
2014  QVariantMap::iterator it = properties.find( QStringLiteral( "svgFile" ) );
2015  if ( it != properties.end() )
2016  {
2017  if ( saving )
2018  it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2019  else
2020  it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2021  }
2022 }
2023 
2025 {
2026  return QStringLiteral( "SVGFill" );
2027 }
2028 
2029 void QgsSVGFillSymbolLayer::applyPattern( QBrush &brush, const QString &svgFilePath, double patternWidth, QgsUnitTypes::RenderUnit patternWidthUnit,
2030  const QColor &svgFillColor, const QColor &svgStrokeColor, double svgStrokeWidth,
2031  QgsUnitTypes::RenderUnit svgStrokeWidthUnit, const QgsSymbolRenderContext &context,
2032  const QgsMapUnitScale &patternWidthMapUnitScale, const QgsMapUnitScale &svgStrokeWidthMapUnitScale, const QgsStringMap svgParameters )
2033 {
2034  if ( mSvgViewBox.isNull() )
2035  {
2036  return;
2037  }
2038 
2040 
2041  if ( static_cast< int >( size ) < 1.0 || 10000.0 < size )
2042  {
2043  brush.setTextureImage( QImage() );
2044  }
2045  else
2046  {
2047  bool fitsInCache = true;
2049  QImage patternImage = QgsApplication::svgCache()->svgAsImage( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2050  context.renderContext().scaleFactor(), fitsInCache, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), svgParameters );
2051  if ( !fitsInCache )
2052  {
2053  QPicture patternPict = QgsApplication::svgCache()->svgAsPicture( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2054  context.renderContext().scaleFactor(), false, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
2055  double hwRatio = 1.0;
2056  if ( patternPict.width() > 0 )
2057  {
2058  hwRatio = static_cast< double >( patternPict.height() ) / static_cast< double >( patternPict.width() );
2059  }
2060  patternImage = QImage( static_cast< int >( size ), static_cast< int >( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
2061  patternImage.fill( 0 ); // transparent background
2062 
2063  QPainter p( &patternImage );
2064  p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
2065  }
2066 
2067  QTransform brushTransform;
2068  if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2069  {
2070  QImage transparentImage = patternImage.copy();
2071  QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2072  brush.setTextureImage( transparentImage );
2073  }
2074  else
2075  {
2076  brush.setTextureImage( patternImage );
2077  }
2078  brush.setTransform( brushTransform );
2079  }
2080 }
2081 
2083 {
2084  QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2085 
2086  applyPattern( mBrush, mSvgFilePath, mPatternWidth, mPatternWidthUnit, mColor, mSvgStrokeColor, mSvgStrokeWidth, mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2087 
2088  if ( mStroke )
2089  {
2090  mStroke->startRender( context.renderContext(), context.fields() );
2091  }
2092 }
2093 
2095 {
2096  if ( mStroke )
2097  {
2098  mStroke->stopRender( context.renderContext() );
2099  }
2100 }
2101 
2102 void QgsSVGFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
2103 {
2104  QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
2105 
2106  if ( mStroke )
2107  {
2108  mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
2109  if ( rings )
2110  {
2111  for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
2112  {
2113  mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
2114  }
2115  }
2116  }
2117 }
2118 
2120 {
2121  QVariantMap map;
2122  if ( !mSvgFilePath.isEmpty() )
2123  {
2124  map.insert( QStringLiteral( "svgFile" ), mSvgFilePath );
2125  }
2126  else
2127  {
2128  map.insert( QStringLiteral( "data" ), QString( mSvgData.toHex() ) );
2129  }
2130 
2131  map.insert( QStringLiteral( "width" ), QString::number( mPatternWidth ) );
2132  map.insert( QStringLiteral( "angle" ), QString::number( mAngle ) );
2133 
2134  //svg parameters
2135  map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
2136  map.insert( QStringLiteral( "outline_color" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2137  map.insert( QStringLiteral( "outline_width" ), QString::number( mSvgStrokeWidth ) );
2138 
2139  //units
2140  map.insert( QStringLiteral( "pattern_width_unit" ), QgsUnitTypes::encodeUnit( mPatternWidthUnit ) );
2141  map.insert( QStringLiteral( "pattern_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mPatternWidthMapUnitScale ) );
2142  map.insert( QStringLiteral( "svg_outline_width_unit" ), QgsUnitTypes::encodeUnit( mSvgStrokeWidthUnit ) );
2143  map.insert( QStringLiteral( "svg_outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mSvgStrokeWidthMapUnitScale ) );
2144  map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
2145  map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
2146 
2147  map[QStringLiteral( "parameters" )] = QgsProperty::propertyMapToVariantMap( mParameters );
2148 
2149  return map;
2150 }
2151 
2153 {
2154  std::unique_ptr< QgsSVGFillSymbolLayer > clonedLayer;
2155  if ( !mSvgFilePath.isEmpty() )
2156  {
2157  clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgFilePath, mPatternWidth, mAngle );
2158  clonedLayer->setSvgFillColor( mColor );
2159  clonedLayer->setSvgStrokeColor( mSvgStrokeColor );
2160  clonedLayer->setSvgStrokeWidth( mSvgStrokeWidth );
2161  }
2162  else
2163  {
2164  clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgData, mPatternWidth, mAngle );
2165  }
2166 
2167  clonedLayer->setPatternWidthUnit( mPatternWidthUnit );
2168  clonedLayer->setPatternWidthMapUnitScale( mPatternWidthMapUnitScale );
2169  clonedLayer->setSvgStrokeWidthUnit( mSvgStrokeWidthUnit );
2170  clonedLayer->setSvgStrokeWidthMapUnitScale( mSvgStrokeWidthMapUnitScale );
2171  clonedLayer->setStrokeWidthUnit( mStrokeWidthUnit );
2172  clonedLayer->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
2173 
2174  clonedLayer->setParameters( mParameters );
2175 
2176  if ( mStroke )
2177  {
2178  clonedLayer->setSubSymbol( mStroke->clone() );
2179  }
2180  copyDataDefinedProperties( clonedLayer.get() );
2181  copyPaintEffect( clonedLayer.get() );
2182  return clonedLayer.release();
2183 }
2184 
2185 void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2186 {
2187  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
2188  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2189  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2190  element.appendChild( symbolizerElem );
2191 
2192  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2193 
2194  QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
2195  symbolizerElem.appendChild( fillElem );
2196 
2197  QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
2198  fillElem.appendChild( graphicFillElem );
2199 
2200  QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2201  graphicFillElem.appendChild( graphicElem );
2202 
2203  if ( !mSvgFilePath.isEmpty() )
2204  {
2205  // encode a parametric SVG reference
2206  double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
2207  double strokeWidth = QgsSymbolLayerUtils::rescaleUom( mSvgStrokeWidth, mSvgStrokeWidthUnit, props );
2208  QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgStrokeColor, strokeWidth );
2209  }
2210  else
2211  {
2212  // TODO: create svg from data
2213  // <se:InlineContent>
2214  symbolizerElem.appendChild( doc.createComment( QStringLiteral( "SVG from data not implemented yet" ) ) );
2215  }
2216 
2217  // <Rotation>
2218  QString angleFunc;
2219  bool ok;
2220  double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
2221  if ( !ok )
2222  {
2223  angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
2224  }
2225  else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2226  {
2227  angleFunc = QString::number( angle + mAngle );
2228  }
2229  QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
2230 
2231  if ( mStroke )
2232  {
2233  // the stroke sub symbol should be stored within the Stroke element,
2234  // but it will be stored in a separated LineSymbolizer because it could
2235  // have more than one layer
2236  mStroke->toSld( doc, element, props );
2237  }
2238 }
2239 
2241 {
2242  return mPatternWidthUnit == QgsUnitTypes::RenderMapUnits || mPatternWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
2243  || mSvgStrokeWidthUnit == QgsUnitTypes::RenderMapUnits || mSvgStrokeWidthUnit == QgsUnitTypes::RenderMetersInMapUnits;
2244 }
2245 
2247 {
2248  return mStroke.get();
2249 }
2250 
2252 {
2253  if ( !symbol ) //unset current stroke
2254  {
2255  mStroke.reset( nullptr );
2256  return true;
2257  }
2258 
2259  if ( symbol->type() != Qgis::SymbolType::Line )
2260  {
2261  delete symbol;
2262  return false;
2263  }
2264 
2265  QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
2266  if ( lineSymbol )
2267  {
2268  mStroke.reset( lineSymbol );
2269  return true;
2270  }
2271 
2272  delete symbol;
2273  return false;
2274 }
2275 
2277 {
2278  if ( mStroke && mStroke->symbolLayer( 0 ) )
2279  {
2280  double subLayerBleed = mStroke->symbolLayer( 0 )->estimateMaxBleed( context );
2281  return subLayerBleed;
2282  }
2283  return 0;
2284 }
2285 
2287 {
2288  Q_UNUSED( context )
2289  if ( !mStroke )
2290  {
2291  return QColor( Qt::black );
2292  }
2293  return mStroke->color();
2294 }
2295 
2296 QSet<QString> QgsSVGFillSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2297 {
2298  QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2299  if ( mStroke )
2300  attr.unite( mStroke->usedAttributes( context ) );
2301  return attr;
2302 }
2303 
2305 {
2307  return true;
2308  if ( mStroke && mStroke->hasDataDefinedProperties() )
2309  return true;
2310  return false;
2311 }
2312 
2314 {
2315  QString path, mimeType;
2316  QColor fillColor, strokeColor;
2317  Qt::PenStyle penStyle;
2318  double size, strokeWidth;
2319 
2320  QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
2321  if ( fillElem.isNull() )
2322  return nullptr;
2323 
2324  QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
2325  if ( graphicFillElem.isNull() )
2326  return nullptr;
2327 
2328  QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
2329  if ( graphicElem.isNull() )
2330  return nullptr;
2331 
2332  if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
2333  return nullptr;
2334 
2335  if ( mimeType != QLatin1String( "image/svg+xml" ) )
2336  return nullptr;
2337 
2338  QgsSymbolLayerUtils::lineFromSld( graphicElem, penStyle, strokeColor, strokeWidth );
2339 
2340  QString uom = element.attribute( QStringLiteral( "uom" ) );
2341  size = QgsSymbolLayerUtils::sizeInPixelsFromSldUom( uom, size );
2342  strokeWidth = QgsSymbolLayerUtils::sizeInPixelsFromSldUom( uom, strokeWidth );
2343 
2344  double angle = 0.0;
2345  QString angleFunc;
2346  if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2347  {
2348  bool ok;
2349  double d = angleFunc.toDouble( &ok );
2350  if ( ok )
2351  angle = d;
2352  }
2353 
2354  std::unique_ptr< QgsSVGFillSymbolLayer > sl = std::make_unique< QgsSVGFillSymbolLayer >( path, size, angle );
2355  sl->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
2356  sl->setSvgFillColor( fillColor );
2357  sl->setSvgStrokeColor( strokeColor );
2358  sl->setSvgStrokeWidth( strokeWidth );
2359 
2360  // try to get the stroke
2361  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2362  if ( !strokeElem.isNull() )
2363  {
2365  if ( l )
2366  {
2367  QgsSymbolLayerList layers;
2368  layers.append( l );
2369  sl->setSubSymbol( new QgsLineSymbol( layers ) );
2370  }
2371  }
2372 
2373  return sl.release();
2374 }
2375 
2377 {
2381  {
2382  return; //no data defined settings
2383  }
2384 
2386  {
2387  context.setOriginalValueVariable( mAngle );
2389  }
2390 
2391  double width = mPatternWidth;
2393  {
2394  context.setOriginalValueVariable( mPatternWidth );
2396  }
2397  QString svgFile = mSvgFilePath;
2399  {
2400  context.setOriginalValueVariable( mSvgFilePath );
2402  context.renderContext().pathResolver() );
2403  }
2404  QColor svgFillColor = mColor;
2406  {
2409  }
2410  QColor svgStrokeColor = mSvgStrokeColor;
2412  {
2413  context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2415  }
2416  double strokeWidth = mSvgStrokeWidth;
2418  {
2419  context.setOriginalValueVariable( mSvgStrokeWidth );
2421  }
2422  QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2423 
2424  applyPattern( mBrush, svgFile, width, mPatternWidthUnit, svgFillColor, svgStrokeColor, strokeWidth,
2425  mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2426 
2427 }
2428 
2429 void QgsSVGFillSymbolLayer::storeViewBox()
2430 {
2431  if ( !mSvgData.isEmpty() )
2432  {
2433  QSvgRenderer r( mSvgData );
2434  if ( r.isValid() )
2435  {
2436  mSvgViewBox = r.viewBoxF();
2437  return;
2438  }
2439  }
2440 
2441  mSvgViewBox = QRectF();
2442 }
2443 
2444 void QgsSVGFillSymbolLayer::setDefaultSvgParams()
2445 {
2446  if ( mSvgFilePath.isEmpty() )
2447  {
2448  return;
2449  }
2450 
2451  bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
2452  bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
2453  QColor defaultFillColor, defaultStrokeColor;
2454  double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
2455  QgsApplication::svgCache()->containsParams( mSvgFilePath, hasFillParam, hasDefaultFillColor, defaultFillColor,
2456  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
2457  hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2458  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
2459  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
2460 
2461  double newFillOpacity = hasFillOpacityParam ? mColor.alphaF() : 1.0;
2462  double newStrokeOpacity = hasStrokeOpacityParam ? mSvgStrokeColor.alphaF() : 1.0;
2463 
2464  if ( hasDefaultFillColor )
2465  {
2466  mColor = defaultFillColor;
2467  mColor.setAlphaF( newFillOpacity );
2468  }
2469  if ( hasDefaultFillOpacity )
2470  {
2471  mColor.setAlphaF( defaultFillOpacity );
2472  }
2473  if ( hasDefaultStrokeColor )
2474  {
2475  mSvgStrokeColor = defaultStrokeColor;
2476  mSvgStrokeColor.setAlphaF( newStrokeOpacity );
2477  }
2478  if ( hasDefaultStrokeOpacity )
2479  {
2480  mSvgStrokeColor.setAlphaF( defaultStrokeOpacity );
2481  }
2482  if ( hasDefaultStrokeWidth )
2483  {
2484  mSvgStrokeWidth = defaultStrokeWidth;
2485  }
2486 }
2487 
2488 void QgsSVGFillSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2489 {
2490  mParameters = parameters;
2491 }
2492 
2493 
2496 {
2497  setSubSymbol( new QgsLineSymbol() );
2498  QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
2499 }
2500 
2502 
2504 {
2505  mFillLineSymbol->setWidth( w );
2506  mLineWidth = w;
2507 }
2508 
2510 {
2511  mFillLineSymbol->setColor( c );
2512  mColor = c;
2513 }
2514 
2516 {
2517  return mFillLineSymbol ? mFillLineSymbol->color() : mColor;
2518 }
2519 
2521 {
2522  if ( !symbol )
2523  {
2524  return false;
2525  }
2526 
2527  if ( symbol->type() == Qgis::SymbolType::Line )
2528  {
2529  mFillLineSymbol.reset( qgis::down_cast<QgsLineSymbol *>( symbol ) );
2530  return true;
2531  }
2532  delete symbol;
2533  return false;
2534 }
2535 
2537 {
2538  return mFillLineSymbol.get();
2539 }
2540 
2542 {
2543  QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2544  if ( mFillLineSymbol )
2545  attr.unite( mFillLineSymbol->usedAttributes( context ) );
2546  return attr;
2547 }
2548 
2550 {
2552  return true;
2553  if ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
2554  return true;
2555  return false;
2556 }
2557 
2559 {
2560  // deliberately don't pass this on to subsymbol here
2561 }
2562 
2564 {
2565  // deliberately don't pass this on to subsymbol here
2566 }
2567 
2569 {
2570  return 0;
2571 }
2572 
2574 {
2576  mDistanceUnit = unit;
2577  mLineWidthUnit = unit;
2578  mOffsetUnit = unit;
2579 }
2580 
2582 {
2584  if ( mDistanceUnit != unit || mLineWidthUnit != unit || ( mOffsetUnit != unit && mOffsetUnit != QgsUnitTypes::RenderPercentage ) )
2585  {
2587  }
2588  return unit;
2589 }
2590 
2592 {
2593  return mDistanceUnit == QgsUnitTypes::RenderMapUnits || mDistanceUnit == QgsUnitTypes::RenderMetersInMapUnits
2594  || mLineWidthUnit == QgsUnitTypes::RenderMapUnits || mLineWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
2595  || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
2596 }
2597 
2599 {
2601  mDistanceMapUnitScale = scale;
2602  mLineWidthMapUnitScale = scale;
2603  mOffsetMapUnitScale = scale;
2604 }
2605 
2607 {
2608  if ( QgsImageFillSymbolLayer::mapUnitScale() == mDistanceMapUnitScale &&
2609  mDistanceMapUnitScale == mLineWidthMapUnitScale &&
2610  mLineWidthMapUnitScale == mOffsetMapUnitScale )
2611  {
2612  return mDistanceMapUnitScale;
2613  }
2614  return QgsMapUnitScale();
2615 }
2616 
2618 {
2619  std::unique_ptr< QgsLinePatternFillSymbolLayer > patternLayer = std::make_unique< QgsLinePatternFillSymbolLayer >();
2620 
2621  //default values
2622  double lineAngle = 45;
2623  double distance = 5;
2624  double lineWidth = 0.5;
2625  QColor color( Qt::black );
2626  double offset = 0.0;
2627 
2628  if ( properties.contains( QStringLiteral( "lineangle" ) ) )
2629  {
2630  //pre 2.5 projects used "lineangle"
2631  lineAngle = properties[QStringLiteral( "lineangle" )].toDouble();
2632  }
2633  else if ( properties.contains( QStringLiteral( "angle" ) ) )
2634  {
2635  lineAngle = properties[QStringLiteral( "angle" )].toDouble();
2636  }
2637  patternLayer->setLineAngle( lineAngle );
2638 
2639  if ( properties.contains( QStringLiteral( "distance" ) ) )
2640  {
2641  distance = properties[QStringLiteral( "distance" )].toDouble();
2642  }
2643  patternLayer->setDistance( distance );
2644 
2645  if ( properties.contains( QStringLiteral( "linewidth" ) ) )
2646  {
2647  //pre 2.5 projects used "linewidth"
2648  lineWidth = properties[QStringLiteral( "linewidth" )].toDouble();
2649  }
2650  else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2651  {
2652  lineWidth = properties[QStringLiteral( "outline_width" )].toDouble();
2653  }
2654  else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2655  {
2656  lineWidth = properties[QStringLiteral( "line_width" )].toDouble();
2657  }
2658  patternLayer->setLineWidth( lineWidth );
2659 
2660  if ( properties.contains( QStringLiteral( "color" ) ) )
2661  {
2662  color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() );
2663  }
2664  else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2665  {
2666  color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() );
2667  }
2668  else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2669  {
2670  color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() );
2671  }
2672  patternLayer->setColor( color );
2673 
2674  if ( properties.contains( QStringLiteral( "offset" ) ) )
2675  {
2676  offset = properties[QStringLiteral( "offset" )].toDouble();
2677  }
2678  patternLayer->setOffset( offset );
2679 
2680 
2681  if ( properties.contains( QStringLiteral( "distance_unit" ) ) )
2682  {
2683  patternLayer->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_unit" )].toString() ) );
2684  }
2685  if ( properties.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
2686  {
2687  patternLayer->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
2688  }
2689  if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
2690  {
2691  patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
2692  }
2693  else if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2694  {
2695  patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2696  }
2697  if ( properties.contains( QStringLiteral( "line_width_map_unit_scale" ) ) )
2698  {
2699  patternLayer->setLineWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "line_width_map_unit_scale" )].toString() ) );
2700  }
2701  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
2702  {
2703  patternLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
2704  }
2705  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
2706  {
2707  patternLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
2708  }
2709  if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2710  {
2711  patternLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2712  }
2713  if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2714  {
2715  patternLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2716  }
2717  if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
2718  {
2719  patternLayer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
2720  }
2721  if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
2722  {
2723  patternLayer->setClipMode( QgsSymbolLayerUtils::decodeLineClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
2724  }
2725 
2726  patternLayer->restoreOldDataDefinedProperties( properties );
2727 
2728  return patternLayer.release();
2729 }
2730 
2732 {
2733  return QStringLiteral( "LinePatternFill" );
2734 }
2735 
2736 void QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double lineAngle, double distance )
2737 {
2738  mBrush.setTextureImage( QImage() ); // set empty in case we have to return
2739 
2740  if ( !mFillLineSymbol )
2741  {
2742  return;
2743  }
2744  // We have to make a copy because marker intervals will have to be adjusted
2745  std::unique_ptr< QgsLineSymbol > fillLineSymbol( mFillLineSymbol->clone() );
2746  if ( !fillLineSymbol )
2747  {
2748  return;
2749  }
2750 
2751  const QgsRenderContext &ctx = context.renderContext();
2752  //double strokePixelWidth = lineWidth * QgsSymbolLayerUtils::pixelSizeScaleFactor( ctx, mLineWidthUnit, mLineWidthMapUnitScale );
2753  double outputPixelDist = ctx.convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
2754  double outputPixelOffset = mOffsetUnit == QgsUnitTypes::RenderPercentage ? outputPixelDist * mOffset / 100
2755  : ctx.convertToPainterUnits( mOffset, mOffsetUnit, mOffsetMapUnitScale );
2756 
2757  // NOTE: this may need to be modified if we ever change from a forced rasterized/brush approach,
2758  // because potentially we may want to allow vector based line pattern fills where the first line
2759  // is offset by a large distance
2760 
2761  // fix truncated pattern with larger offsets
2762  outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDist );
2763  if ( outputPixelOffset > outputPixelDist / 2.0 )
2764  outputPixelOffset -= outputPixelDist;
2765 
2766  // To get all patterns into image, we have to consider symbols size (estimateMaxBleed()).
2767  // For marker lines we have to get markers interval.
2768  double outputPixelBleed = 0;
2769  double outputPixelInterval = 0; // maximum interval
2770  for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2771  {
2772  QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2773  double outputPixelLayerBleed = layer->estimateMaxBleed( context.renderContext() );
2774  outputPixelBleed = std::max( outputPixelBleed, outputPixelLayerBleed );
2775 
2776  QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2777  if ( markerLineLayer )
2778  {
2779  double outputPixelLayerInterval = ctx.convertToPainterUnits( markerLineLayer->interval(), markerLineLayer->intervalUnit(), markerLineLayer->intervalMapUnitScale() );
2780 
2781  // There may be multiple marker lines with different intervals.
2782  // In theory we should find the least common multiple, but that could be too
2783  // big (multiplication of intervals in the worst case).
2784  // Because patterns without small common interval would look strange, we
2785  // believe that the longest interval should usually be sufficient.
2786  outputPixelInterval = std::max( outputPixelInterval, outputPixelLayerInterval );
2787  }
2788  }
2789 
2790  if ( outputPixelInterval > 0 )
2791  {
2792  // We have to adjust marker intervals to integer pixel size to get
2793  // repeatable pattern.
2794  double intervalScale = std::round( outputPixelInterval ) / outputPixelInterval;
2795  outputPixelInterval = std::round( outputPixelInterval );
2796 
2797  for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2798  {
2799  QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2800 
2801  QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2802  if ( markerLineLayer )
2803  {
2804  markerLineLayer->setInterval( intervalScale * markerLineLayer->interval() );
2805  }
2806  }
2807  }
2808 
2809  //create image
2810  int height, width;
2811  lineAngle = std::fmod( lineAngle, 360 );
2812  if ( lineAngle < 0 )
2813  lineAngle += 360;
2814  if ( qgsDoubleNear( lineAngle, 0 ) || qgsDoubleNear( lineAngle, 360 ) || qgsDoubleNear( lineAngle, 180 ) )
2815  {
2816  height = outputPixelDist;
2817  width = outputPixelInterval > 0 ? outputPixelInterval : height;
2818  }
2819  else if ( qgsDoubleNear( lineAngle, 90 ) || qgsDoubleNear( lineAngle, 270 ) )
2820  {
2821  width = outputPixelDist;
2822  height = outputPixelInterval > 0 ? outputPixelInterval : width;
2823  }
2824  else
2825  {
2826  height = outputPixelDist / std::cos( lineAngle * M_PI / 180 ); //keep perpendicular distance between lines constant
2827  width = outputPixelDist / std::sin( lineAngle * M_PI / 180 );
2828 
2829  // recalculate real angle and distance after rounding to pixels
2830  lineAngle = 180 * std::atan2( static_cast< double >( height ), static_cast< double >( width ) ) / M_PI;
2831  if ( lineAngle < 0 )
2832  {
2833  lineAngle += 360.;
2834  }
2835 
2836  height = std::abs( height );
2837  width = std::abs( width );
2838 
2839  outputPixelDist = std::abs( height * std::cos( lineAngle * M_PI / 180 ) );
2840 
2841  // Round offset to correspond to one pixel height, otherwise lines may
2842  // be shifted on tile border if offset falls close to pixel center
2843  int offsetHeight = static_cast< int >( std::round( outputPixelOffset / std::cos( lineAngle * M_PI / 180 ) ) );
2844  outputPixelOffset = offsetHeight * std::cos( lineAngle * M_PI / 180 );
2845  }
2846 
2847  //depending on the angle, we might need to render into a larger image and use a subset of it
2848  double dx = 0;
2849  double dy = 0;
2850 
2851  // Add buffer based on bleed but keep precisely the height/width ratio (angle)
2852  // thus we add integer multiplications of width and height covering the bleed
2853  int bufferMulti = static_cast< int >( std::max( std::ceil( outputPixelBleed / width ), std::ceil( outputPixelBleed / width ) ) );
2854 
2855  // Always buffer at least once so that center of line marker in upper right corner
2856  // does not fall outside due to representation error
2857  bufferMulti = std::max( bufferMulti, 1 );
2858 
2859  int xBuffer = width * bufferMulti;
2860  int yBuffer = height * bufferMulti;
2861  int innerWidth = width;
2862  int innerHeight = height;
2863  width += 2 * xBuffer;
2864  height += 2 * yBuffer;
2865 
2866  //protect from zero width/height image and symbol layer from eating too much memory
2867  if ( width > 10000 || height > 10000 || width == 0 || height == 0 )
2868  {
2869  return;
2870  }
2871 
2872  QImage patternImage( width, height, QImage::Format_ARGB32 );
2873  patternImage.fill( 0 );
2874 
2875  QPointF p1, p2, p3, p4, p5, p6;
2876  if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
2877  {
2878  p1 = QPointF( 0, yBuffer );
2879  p2 = QPointF( width, yBuffer );
2880  p3 = QPointF( 0, yBuffer + innerHeight );
2881  p4 = QPointF( width, yBuffer + innerHeight );
2882  }
2883  else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
2884  {
2885  p1 = QPointF( xBuffer, height );
2886  p2 = QPointF( xBuffer, 0 );
2887  p3 = QPointF( xBuffer + innerWidth, height );
2888  p4 = QPointF( xBuffer + innerWidth, 0 );
2889  }
2890  else if ( lineAngle > 0 && lineAngle < 90 )
2891  {
2892  dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
2893  dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
2894  p1 = QPointF( 0, height );
2895  p2 = QPointF( width, 0 );
2896  p3 = QPointF( -dx, height - dy );
2897  p4 = QPointF( width - dx, -dy );
2898  p5 = QPointF( dx, height + dy );
2899  p6 = QPointF( width + dx, dy );
2900  }
2901  else if ( lineAngle > 180 && lineAngle < 270 )
2902  {
2903  dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
2904  dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
2905  p1 = QPointF( width, 0 );
2906  p2 = QPointF( 0, height );
2907  p3 = QPointF( width - dx, -dy );
2908  p4 = QPointF( -dx, height - dy );
2909  p5 = QPointF( width + dx, dy );
2910  p6 = QPointF( dx, height + dy );
2911  }
2912  else if ( lineAngle > 90 && lineAngle < 180 )
2913  {
2914  dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
2915  dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
2916  p1 = QPointF( 0, 0 );
2917  p2 = QPointF( width, height );
2918  p5 = QPointF( dx, -dy );
2919  p6 = QPointF( width + dx, height - dy );
2920  p3 = QPointF( -dx, dy );
2921  p4 = QPointF( width - dx, height + dy );
2922  }
2923  else if ( lineAngle > 270 && lineAngle < 360 )
2924  {
2925  dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
2926  dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
2927  p1 = QPointF( width, height );
2928  p2 = QPointF( 0, 0 );
2929  p5 = QPointF( width + dx, height - dy );
2930  p6 = QPointF( dx, -dy );
2931  p3 = QPointF( width - dx, height + dy );
2932  p4 = QPointF( -dx, dy );
2933  }
2934 
2935  if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
2936  {
2937  QPointF tempPt;
2938  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
2939  p3 = QPointF( tempPt.x(), tempPt.y() );
2940  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
2941  p4 = QPointF( tempPt.x(), tempPt.y() );
2942  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
2943  p5 = QPointF( tempPt.x(), tempPt.y() );
2944  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
2945  p6 = QPointF( tempPt.x(), tempPt.y() );
2946 
2947  //update p1, p2 last
2948  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
2949  p1 = QPointF( tempPt.x(), tempPt.y() );
2950  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
2951  p2 = QPointF( tempPt.x(), tempPt.y() );
2952  }
2953 
2954  QPainter p( &patternImage );
2955 
2956 #if 0
2957  // DEBUG: Draw rectangle
2958  p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
2959  QPen pen( QColor( Qt::black ) );
2960  pen.setWidthF( 0.1 );
2961  pen.setCapStyle( Qt::FlatCap );
2962  p.setPen( pen );
2963 
2964  // To see this rectangle, comment buffer cut below.
2965  // Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
2966  QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 );
2967  p.drawPolygon( polygon );
2968 
2969  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 );
2970  p.drawPolygon( polygon );
2971 #endif
2972 
2973  // Use antialiasing because without antialiasing lines are rendered to the
2974  // right and below the mathematically defined points (not symmetrical)
2975  // and such tiles become useless for are filling
2976  p.setRenderHint( QPainter::Antialiasing, true );
2977 
2978  // line rendering needs context for drawing on patternImage
2979  QgsRenderContext lineRenderContext;
2980  lineRenderContext.setPainter( &p );
2981  lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
2983  lineRenderContext.setMapToPixel( mtp );
2984  lineRenderContext.setForceVectorOutput( false );
2985  lineRenderContext.setExpressionContext( context.renderContext().expressionContext() );
2987 
2988  fillLineSymbol->startRender( lineRenderContext, context.fields() );
2989 
2990  QVector<QPolygonF> polygons;
2991  polygons.append( QPolygonF() << p1 << p2 );
2992  polygons.append( QPolygonF() << p3 << p4 );
2993  if ( !qgsDoubleNear( lineAngle, 0 ) && !qgsDoubleNear( lineAngle, 360 ) && !qgsDoubleNear( lineAngle, 90 ) && !qgsDoubleNear( lineAngle, 180 ) && !qgsDoubleNear( lineAngle, 270 ) )
2994  {
2995  polygons.append( QPolygonF() << p5 << p6 );
2996  }
2997 
2998  for ( const QPolygonF &polygon : std::as_const( polygons ) )
2999  {
3000  fillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, context.selected() );
3001  }
3002 
3003  fillLineSymbol->stopRender( lineRenderContext );
3004  p.end();
3005 
3006  // Cut off the buffer
3007  patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
3008 
3009  //set image to mBrush
3010  if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3011  {
3012  QImage transparentImage = patternImage.copy();
3013  QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3014  brush.setTextureImage( transparentImage );
3015  }
3016  else
3017  {
3018  brush.setTextureImage( patternImage );
3019  }
3020 
3021  QTransform brushTransform;
3022  brush.setTransform( brushTransform );
3023 }
3024 
3026 {
3027  // if we are using a vector based output, we need to render points as vectors
3028  // (OR if the line has data defined symbology, in which case we need to evaluate this line-by-line)
3029  mRenderUsingLines = context.renderContext().forceVectorOutput()
3030  || mFillLineSymbol->hasDataDefinedProperties()
3031  || mClipMode != Qgis::LineClipMode::ClipPainterOnly
3033 
3034  if ( mRenderUsingLines )
3035  {
3036  if ( mFillLineSymbol )
3037  mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3038  }
3039  else
3040  {
3041  // optimised render for screen only, use image based brush
3042  applyPattern( context, mBrush, mLineAngle, mDistance );
3043  }
3044 }
3045 
3047 {
3048  if ( mRenderUsingLines && mFillLineSymbol )
3049  {
3050  mFillLineSymbol->stopRender( context.renderContext() );
3051  }
3052 }
3053 
3054 void QgsLinePatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3055 {
3056  if ( !mRenderUsingLines )
3057  {
3058  // use image based brush for speed
3059  QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3060  return;
3061  }
3062 
3063  // vector based output - so draw line by line!
3064  QPainter *p = context.renderContext().painter();
3065  if ( !p )
3066  {
3067  return;
3068  }
3069 
3070  double lineAngle = mLineAngle;
3072  {
3073  context.setOriginalValueVariable( mLineAngle );
3075  }
3076 
3077  double distance = mDistance;
3079  {
3080  context.setOriginalValueVariable( mDistance );
3082  }
3083  const double outputPixelDistance = context.renderContext().convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
3084 
3085  double offset = mOffset;
3086  double outputPixelOffset = mOffsetUnit == QgsUnitTypes::RenderPercentage ? outputPixelDistance * offset / 100
3087  : context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3088 
3089  // fix truncated pattern with larger offsets
3090  outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDistance );
3091  if ( outputPixelOffset > outputPixelDistance / 2.0 )
3092  outputPixelOffset -= outputPixelDistance;
3093 
3094  p->setPen( QPen( Qt::NoPen ) );
3095 
3096  if ( context.selected() )
3097  {
3098  QColor selColor = context.renderContext().selectionColor();
3099  p->setBrush( QBrush( selColor ) );
3100  _renderPolygon( p, points, rings, context );
3101  }
3102 
3103  // if invalid parameters, skip out
3104  if ( qgsDoubleNear( distance, 0 ) )
3105  return;
3106 
3107  p->save();
3108 
3109  Qgis::LineClipMode clipMode = mClipMode;
3111  {
3113  bool ok = false;
3114  const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyLineClipping, context.renderContext().expressionContext(), QString(), &ok );
3115  if ( ok )
3116  {
3117  Qgis::LineClipMode decodedMode = QgsSymbolLayerUtils::decodeLineClipMode( valueString, &ok );
3118  if ( ok )
3119  clipMode = decodedMode;
3120  }
3121  }
3122 
3123  std::unique_ptr< QgsPolygon > shapePolygon;
3124  std::unique_ptr< QgsGeometryEngine > shapeEngine;
3125  switch ( clipMode )
3126  {
3128  break;
3129 
3131  {
3132  shapePolygon = std::make_unique< QgsPolygon >();
3133  shapePolygon->setExteriorRing( QgsLineString::fromQPolygonF( points ) );
3134  if ( rings )
3135  {
3136  for ( const QPolygonF &ring : *rings )
3137  {
3138  shapePolygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
3139  }
3140  }
3141  shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3142  shapeEngine->prepareGeometry();
3143  break;
3144  }
3145 
3147  {
3148  QPainterPath path;
3149  path.addPolygon( points );
3150  if ( rings )
3151  {
3152  for ( const QPolygonF &ring : *rings )
3153  {
3154  path.addPolygon( ring );
3155  }
3156  }
3157  p->setClipPath( path, Qt::IntersectClip );
3158  break;
3159  }
3160  }
3161 
3162  const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3163  const QRectF boundingRect = points.boundingRect();
3164 
3165  QTransform invertedRotateTransform;
3166  double left;
3167  double top;
3168  double right;
3169  double bottom;
3170 
3171  QTransform transform;
3172  if ( applyBrushTransform )
3173  {
3174  // rotation applies around center of feature
3175  transform.translate( -boundingRect.center().x(),
3176  -boundingRect.center().y() );
3177  transform.rotate( lineAngle );
3178  transform.translate( boundingRect.center().x(),
3179  boundingRect.center().y() );
3180  }
3181  else
3182  {
3183  // rotation applies around top of viewport
3184  transform.rotate( lineAngle );
3185  }
3186 
3187  const QRectF transformedBounds = transform.map( points ).boundingRect();
3188 
3189  // bounds are expanded out a bit to account for maximum line width
3190  const double buffer = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFillLineSymbol.get(), context.renderContext() );
3191  left = transformedBounds.left() - buffer * 2;
3192  top = transformedBounds.top() - buffer * 2;
3193  right = transformedBounds.right() + buffer * 2;
3194  bottom = transformedBounds.bottom() + buffer * 2;
3195  invertedRotateTransform = transform.inverted();
3196 
3197  if ( !applyBrushTransform )
3198  {
3199  top -= transformedBounds.top() - ( outputPixelDistance * std::floor( transformedBounds.top() / outputPixelDistance ) );
3200  }
3201 
3203  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
3204  const bool needsExpressionContext = mFillLineSymbol->hasDataDefinedProperties();
3205 
3206  const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3208 
3209  int currentLine = 0;
3210  for ( double currentY = top; currentY <= bottom; currentY += outputPixelDistance )
3211  {
3212  if ( context.renderContext().renderingStopped() )
3213  break;
3214 
3215  if ( needsExpressionContext )
3216  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_line_number" ), ++currentLine, true ) );
3217 
3218  double x1 = left;
3219  double y1 = currentY;
3220  double x2 = left;
3221  double y2 = currentY;
3222  invertedRotateTransform.map( left, currentY - outputPixelOffset, &x1, &y1 );
3223  invertedRotateTransform.map( right, currentY - outputPixelOffset, &x2, &y2 );
3224 
3225  if ( shapeEngine )
3226  {
3227  QgsLineString ls( QgsPoint( x1, y1 ), QgsPoint( x2, y2 ) );
3228  std::unique_ptr< QgsAbstractGeometry > intersection( shapeEngine->intersection( &ls ) );
3229  for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
3230  {
3231  if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( *it ) )
3232  {
3233  mFillLineSymbol->renderPolyline( ls->asQPolygonF(), context.feature(), context.renderContext() );
3234  }
3235  }
3236  }
3237  else
3238  {
3239  mFillLineSymbol->renderPolyline( QPolygonF() << QPointF( x1, y1 ) << QPointF( x2, y2 ), context.feature(), context.renderContext() );
3240  }
3241  }
3242 
3243  p->restore();
3244 
3246 }
3247 
3249 {
3250  QVariantMap map = QgsImageFillSymbolLayer::properties();
3251  map.insert( QStringLiteral( "angle" ), QString::number( mLineAngle ) );
3252  map.insert( QStringLiteral( "distance" ), QString::number( mDistance ) );
3253  map.insert( QStringLiteral( "line_width" ), QString::number( mLineWidth ) );
3254  map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
3255  map.insert( QStringLiteral( "offset" ), QString::number( mOffset ) );
3256  map.insert( QStringLiteral( "distance_unit" ), QgsUnitTypes::encodeUnit( mDistanceUnit ) );
3257  map.insert( QStringLiteral( "line_width_unit" ), QgsUnitTypes::encodeUnit( mLineWidthUnit ) );
3258  map.insert( QStringLiteral( "offset_unit" ), QgsUnitTypes::encodeUnit( mOffsetUnit ) );
3259  map.insert( QStringLiteral( "distance_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale ) );
3260  map.insert( QStringLiteral( "line_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineWidthMapUnitScale ) );
3261  map.insert( QStringLiteral( "offset_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ) );
3262  map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3263  map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3264  map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeLineClipMode( mClipMode ) );
3265  return map;
3266 }
3267 
3269 {
3271  if ( mFillLineSymbol )
3272  {
3273  clonedLayer->setSubSymbol( mFillLineSymbol->clone() );
3274  }
3275  copyPaintEffect( clonedLayer );
3276  copyDataDefinedProperties( clonedLayer );
3277  return clonedLayer;
3278 }
3279 
3280 void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3281 {
3282  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
3283  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
3284  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
3285  element.appendChild( symbolizerElem );
3286 
3287  // <Geometry>
3288  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
3289 
3290  QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
3291  symbolizerElem.appendChild( fillElem );
3292 
3293  QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
3294  fillElem.appendChild( graphicFillElem );
3295 
3296  QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
3297  graphicFillElem.appendChild( graphicElem );
3298 
3299  //line properties must be inside the graphic definition
3300  QColor lineColor = mFillLineSymbol ? mFillLineSymbol->color() : QColor();
3301  double lineWidth = mFillLineSymbol ? mFillLineSymbol->width() : 0.0;
3302  lineWidth = QgsSymbolLayerUtils::rescaleUom( lineWidth, mLineWidthUnit, props );
3303  double distance = QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, props );
3304  QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "horline" ), QColor(), lineColor, Qt::SolidLine, lineWidth, distance );
3305 
3306  // <Rotation>
3307  QString angleFunc;
3308  bool ok;
3309  double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
3310  if ( !ok )
3311  {
3312  angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mLineAngle );
3313  }
3314  else if ( !qgsDoubleNear( angle + mLineAngle, 0.0 ) )
3315  {
3316  angleFunc = QString::number( angle + mLineAngle );
3317  }
3318  QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
3319 
3320  // <se:Displacement>
3321  QPointF lineOffset( std::sin( mLineAngle ) * mOffset, std::cos( mLineAngle ) * mOffset );
3322  lineOffset = QgsSymbolLayerUtils::rescaleUom( lineOffset, mOffsetUnit, props );
3323  QgsSymbolLayerUtils::createDisplacementElement( doc, graphicElem, lineOffset );
3324 }
3325 
3326 QString QgsLinePatternFillSymbolLayer::ogrFeatureStyleWidth( double widthScaleFactor ) const
3327 {
3328  QString featureStyle;
3329  featureStyle.append( "Brush(" );
3330  featureStyle.append( QStringLiteral( "fc:%1" ).arg( mColor.name() ) );
3331  featureStyle.append( QStringLiteral( ",bc:%1" ).arg( QLatin1String( "#00000000" ) ) ); //transparent background
3332  featureStyle.append( ",id:\"ogr-brush-2\"" );
3333  featureStyle.append( QStringLiteral( ",a:%1" ).arg( mLineAngle ) );
3334  featureStyle.append( QStringLiteral( ",s:%1" ).arg( mLineWidth * widthScaleFactor ) );
3335  featureStyle.append( ",dx:0mm" );
3336  featureStyle.append( QStringLiteral( ",dy:%1mm" ).arg( mDistance * widthScaleFactor ) );
3337  featureStyle.append( ')' );
3338  return featureStyle;
3339 }
3340 
3342 {
3344  && ( !mFillLineSymbol || !mFillLineSymbol->hasDataDefinedProperties() ) )
3345  {
3346  return; //no data defined settings
3347  }
3348 
3349  double lineAngle = mLineAngle;
3351  {
3352  context.setOriginalValueVariable( mLineAngle );
3354  }
3355  double distance = mDistance;
3357  {
3358  context.setOriginalValueVariable( mDistance );
3360  }
3361  applyPattern( context, mBrush, lineAngle, distance );
3362 }
3363 
3365 {
3366  QString name;
3367  QColor fillColor, lineColor;
3368  double size, lineWidth;
3369  Qt::PenStyle lineStyle;
3370 
3371  QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
3372  if ( fillElem.isNull() )
3373  return nullptr;
3374 
3375  QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
3376  if ( graphicFillElem.isNull() )
3377  return nullptr;
3378 
3379  QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
3380  if ( graphicElem.isNull() )
3381  return nullptr;
3382 
3383  if ( !QgsSymbolLayerUtils::wellKnownMarkerFromSld( graphicElem, name, fillColor, lineColor, lineStyle, lineWidth, size ) )
3384  return nullptr;
3385 
3386  if ( name != QLatin1String( "horline" ) )
3387  return nullptr;
3388 
3389  double angle = 0.0;
3390  QString angleFunc;
3391  if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3392  {
3393  bool ok;
3394  double d = angleFunc.toDouble( &ok );
3395  if ( ok )
3396  angle = d;
3397  }
3398 
3399  double offset = 0.0;
3400  QPointF vectOffset;
3401  if ( QgsSymbolLayerUtils::displacementFromSldElement( graphicElem, vectOffset ) )
3402  {
3403  offset = std::sqrt( std::pow( vectOffset.x(), 2 ) + std::pow( vectOffset.y(), 2 ) );
3404  }
3405 
3406  QString uom = element.attribute( QStringLiteral( "uom" ) );
3407  size = QgsSymbolLayerUtils::sizeInPixelsFromSldUom( uom, size );
3409 
3410  std::unique_ptr< QgsLinePatternFillSymbolLayer > sl = std::make_unique< QgsLinePatternFillSymbolLayer >();
3411  sl->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
3412  sl->setColor( lineColor );
3413  sl->setLineWidth( lineWidth );
3414  sl->setLineAngle( angle );
3415  sl->setOffset( offset );
3416  sl->setDistance( size );
3417 
3418  // try to get the stroke
3419  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
3420  if ( !strokeElem.isNull() )
3421  {
3423  if ( l )
3424  {
3425  QgsSymbolLayerList layers;
3426  layers.append( l );
3427  sl->setSubSymbol( new QgsLineSymbol( layers ) );
3428  }
3429  }
3430 
3431  return sl.release();
3432 }
3433 
3434 
3436 
3439 {
3440  setSubSymbol( new QgsMarkerSymbol() );
3441  QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
3442 }
3443 
3445 
3447 {
3449  mDistanceXUnit = unit;
3450  mDistanceYUnit = unit;
3451  // don't change "percentage" units -- since they adapt directly to whatever other unit is set
3453  mDisplacementXUnit = unit;
3455  mDisplacementYUnit = unit;
3457  mOffsetXUnit = unit;
3459  mOffsetYUnit = unit;
3461  mRandomDeviationXUnit = unit;
3463  mRandomDeviationYUnit = unit;
3464 
3465  if ( mMarkerSymbol )
3466  {
3467  mMarkerSymbol->setOutputUnit( unit );
3468  }
3469 }
3470 
3472 {
3474  if ( mDistanceXUnit != unit ||
3475  mDistanceYUnit != unit ||
3482  {
3484  }
3485  return unit;
3486 }
3487 
3489 {
3498 }
3499 
3501 {
3503  mDistanceXMapUnitScale = scale;
3504  mDistanceYMapUnitScale = scale;
3507  mOffsetXMapUnitScale = scale;
3508  mOffsetYMapUnitScale = scale;
3511 }
3512 
3514 {
3523  {
3524  return mDistanceXMapUnitScale;
3525  }
3526  return QgsMapUnitScale();
3527 }
3528 
3530 {
3531  std::unique_ptr< QgsPointPatternFillSymbolLayer > layer = std::make_unique< QgsPointPatternFillSymbolLayer >();
3532  if ( properties.contains( QStringLiteral( "distance_x" ) ) )
3533  {
3534  layer->setDistanceX( properties[QStringLiteral( "distance_x" )].toDouble() );
3535  }
3536  if ( properties.contains( QStringLiteral( "distance_y" ) ) )
3537  {
3538  layer->setDistanceY( properties[QStringLiteral( "distance_y" )].toDouble() );
3539  }
3540  if ( properties.contains( QStringLiteral( "displacement_x" ) ) )
3541  {
3542  layer->setDisplacementX( properties[QStringLiteral( "displacement_x" )].toDouble() );
3543  }
3544  if ( properties.contains( QStringLiteral( "displacement_y" ) ) )
3545  {
3546  layer->setDisplacementY( properties[QStringLiteral( "displacement_y" )].toDouble() );
3547  }
3548  if ( properties.contains( QStringLiteral( "offset_x" ) ) )
3549  {
3550  layer->setOffsetX( properties[QStringLiteral( "offset_x" )].toDouble() );
3551  }
3552  if ( properties.contains( QStringLiteral( "offset_y" ) ) )
3553  {
3554  layer->setOffsetY( properties[QStringLiteral( "offset_y" )].toDouble() );
3555  }
3556 
3557  if ( properties.contains( QStringLiteral( "distance_x_unit" ) ) )
3558  {
3559  layer->setDistanceXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_x_unit" )].toString() ) );
3560  }
3561  if ( properties.contains( QStringLiteral( "distance_x_map_unit_scale" ) ) )
3562  {
3563  layer->setDistanceXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_x_map_unit_scale" )].toString() ) );
3564  }
3565  if ( properties.contains( QStringLiteral( "distance_y_unit" ) ) )
3566  {
3567  layer->setDistanceYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_y_unit" )].toString() ) );
3568  }
3569  if ( properties.contains( QStringLiteral( "distance_y_map_unit_scale" ) ) )
3570  {
3571  layer->setDistanceYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_y_map_unit_scale" )].toString() ) );
3572  }
3573  if ( properties.contains( QStringLiteral( "displacement_x_unit" ) ) )
3574  {
3575  layer->setDisplacementXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_x_unit" )].toString() ) );
3576  }
3577  if ( properties.contains( QStringLiteral( "displacement_x_map_unit_scale" ) ) )
3578  {
3579  layer->setDisplacementXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_x_map_unit_scale" )].toString() ) );
3580  }
3581  if ( properties.contains( QStringLiteral( "displacement_y_unit" ) ) )
3582  {
3583  layer->setDisplacementYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_y_unit" )].toString() ) );
3584  }
3585  if ( properties.contains( QStringLiteral( "displacement_y_map_unit_scale" ) ) )
3586  {
3587  layer->setDisplacementYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_y_map_unit_scale" )].toString() ) );
3588  }
3589  if ( properties.contains( QStringLiteral( "offset_x_unit" ) ) )
3590  {
3591  layer->setOffsetXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_x_unit" )].toString() ) );
3592  }
3593  if ( properties.contains( QStringLiteral( "offset_x_map_unit_scale" ) ) )
3594  {
3595  layer->setOffsetXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_x_map_unit_scale" )].toString() ) );
3596  }
3597  if ( properties.contains( QStringLiteral( "offset_y_unit" ) ) )
3598  {
3599  layer->setOffsetYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_y_unit" )].toString() ) );
3600  }
3601  if ( properties.contains( QStringLiteral( "offset_y_map_unit_scale" ) ) )
3602  {
3603  layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_y_map_unit_scale" )].toString() ) );
3604  }
3605 
3606  if ( properties.contains( QStringLiteral( "random_deviation_x" ) ) )
3607  {
3608  layer->setMaximumRandomDeviationX( properties[QStringLiteral( "random_deviation_x" )].toDouble() );
3609  }
3610  if ( properties.contains( QStringLiteral( "random_deviation_y" ) ) )
3611  {
3612  layer->setMaximumRandomDeviationY( properties[QStringLiteral( "random_deviation_y" )].toDouble() );
3613  }
3614  if ( properties.contains( QStringLiteral( "random_deviation_x_unit" ) ) )
3615  {
3616  layer->setRandomDeviationXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_x_unit" )].toString() ) );
3617  }
3618  if ( properties.contains( QStringLiteral( "random_deviation_x_map_unit_scale" ) ) )
3619  {
3620  layer->setRandomDeviationXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_x_map_unit_scale" )].toString() ) );
3621  }
3622  if ( properties.contains( QStringLiteral( "random_deviation_y_unit" ) ) )
3623  {
3624  layer->setRandomDeviationYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_y_unit" )].toString() ) );
3625  }
3626  if ( properties.contains( QStringLiteral( "random_deviation_y_map_unit_scale" ) ) )
3627  {
3628  layer->setRandomDeviationYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_y_map_unit_scale" )].toString() ) );
3629  }
3630  unsigned long seed = 0;
3631  if ( properties.contains( QStringLiteral( "seed" ) ) )
3632  seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
3633  else
3634  {
3635  // if we a creating a new point pattern fill from scratch, we default to a random seed
3636  // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
3637  std::random_device rd;
3638  std::mt19937 mt( seed == 0 ? rd() : seed );
3639  std::uniform_int_distribution<> uniformDist( 1, 999999999 );
3640  seed = uniformDist( mt );
3641  }
3642  layer->setSeed( seed );
3643 
3644  if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
3645  {
3646  layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
3647  }
3648  if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
3649  {
3650  layer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
3651  }
3652  if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
3653  {
3654  layer->setClipMode( QgsSymbolLayerUtils::decodeMarkerClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
3655  }
3656  if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
3657  {
3658  layer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
3659  }
3660 
3661  if ( properties.contains( QStringLiteral( "angle" ) ) )
3662  {
3663  layer->setAngle( properties[QStringLiteral( "angle" )].toDouble() );
3664  }
3665 
3666  layer->restoreOldDataDefinedProperties( properties );
3667 
3668  return layer.release();
3669 }
3670 
3672 {
3673  return QStringLiteral( "PointPatternFill" );
3674 }
3675 
3676 void QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double distanceX, double distanceY,
3677  double displacementX, double displacementY, double offsetX, double offsetY )
3678 {
3679  //render 3 rows and columns in one go to easily incorporate displacement
3680  const QgsRenderContext &ctx = context.renderContext();
3683 
3684  double widthOffset = std::fmod(
3686  width );
3687  double heightOffset = std::fmod(
3689  height );
3690 
3691  if ( width > 10000 || height > 10000 ) //protect symbol layer from eating too much memory
3692  {
3693  QImage img;
3694  brush.setTextureImage( img );
3695  return;
3696  }
3697 
3698  QImage patternImage( width, height, QImage::Format_ARGB32 );
3699  patternImage.fill( 0 );
3700  if ( patternImage.isNull() )
3701  {
3702  brush.setTextureImage( QImage() );
3703  return;
3704  }
3705  if ( mMarkerSymbol )
3706  {
3707  QPainter p( &patternImage );
3708 
3709  //marker rendering needs context for drawing on patternImage
3710  QgsRenderContext pointRenderContext;
3711  pointRenderContext.setRendererScale( context.renderContext().rendererScale() );
3712  pointRenderContext.setPainter( &p );
3713  pointRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3714 
3717  pointRenderContext.setMapToPixel( mtp );
3718  pointRenderContext.setForceVectorOutput( false );
3719  pointRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3721 
3722  mMarkerSymbol->startRender( pointRenderContext, context.fields() );
3723 
3724  //render points on distance grid
3725  for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3726  {
3727  for ( double currentY = -height; currentY <= height * 2.0; currentY += height )
3728  {
3729  mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset, currentY + heightOffset ), context.feature(), pointRenderContext );
3730  }
3731  }
3732 
3733  //render displaced points
3734  double displacementPixelX = mDisplacementXUnit == QgsUnitTypes::RenderPercentage
3735  ? ( width * displacementX / 200 )
3737  double displacementPixelY = mDisplacementYUnit == QgsUnitTypes::RenderPercentage
3738  ? ( height * displacementY / 200 )
3740  for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3741  {
3742  for ( double currentY = -height / 2.0; currentY <= height * 2.0; currentY += height )
3743  {
3744  mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + displacementPixelX, currentY + heightOffset ), context.feature(), pointRenderContext );
3745  }
3746  }
3747 
3748  for ( double currentX = -width / 2.0; currentX <= width * 2.0; currentX += width )
3749  {
3750  for ( double currentY = -height; currentY <= height * 2.0; currentY += height / 2.0 )
3751  {
3752  mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + ( std::fmod( currentY, height ) != 0 ? displacementPixelX : 0 ), currentY + heightOffset - displacementPixelY ), context.feature(), pointRenderContext );
3753  }
3754  }
3755 
3756  mMarkerSymbol->stopRender( pointRenderContext );
3757  }
3758 
3759  if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3760  {
3761  QImage transparentImage = patternImage.copy();
3762  QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3763  brush.setTextureImage( transparentImage );
3764  }
3765  else
3766  {
3767  brush.setTextureImage( patternImage );
3768  }
3769  QTransform brushTransform;
3770  brush.setTransform( brushTransform );
3771 }
3772 
3774 {
3775  // if we are using a vector based output, we need to render points as vectors
3776  // (OR if the marker has data defined symbology, in which case we need to evaluate this point-by-point)
3777  mRenderUsingMarkers = context.renderContext().forceVectorOutput()
3778  || mMarkerSymbol->hasDataDefinedProperties()
3782  || mClipMode != Qgis::MarkerClipMode::Shape
3785  || !qgsDoubleNear( mAngle, 0 )
3787 
3788  if ( mRenderUsingMarkers )
3789  {
3790  mMarkerSymbol->startRender( context.renderContext() );
3791  }
3792  else
3793  {
3794  // optimised render for screen only, use image based brush
3796  }
3797 }
3798 
3800 {
3801  if ( mRenderUsingMarkers )
3802  {
3803  mMarkerSymbol->stopRender( context.renderContext() );
3804  }
3805 }
3806 
3808 {
3809  // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3810  // Otherwise generators used in the subsymbol will only render a single point per feature (they
3811  // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
3812 }
3813 
3815 {
3816  // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3817  // Otherwise generators used in the subsymbol will only render a single point per feature (they
3818  // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
3819 }
3820 
3821 void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3822 {
3823  if ( !mRenderUsingMarkers )
3824  {
3825  // use image based brush for speed
3826  QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3827  return;
3828  }
3829 
3830  // vector based output - so draw dot by dot!
3831  QPainter *p = context.renderContext().painter();
3832  if ( !p )
3833  {
3834  return;
3835  }
3836 
3837  double angle = mAngle;
3839  {
3840  context.setOriginalValueVariable( angle );
3842  }
3843 
3844  double distanceX = mDistanceX;
3846  {
3849  }
3851 
3852  double distanceY = mDistanceY;
3854  {
3857  }
3859 
3860  double offsetX = mOffsetX;
3862  {
3865  }
3866  const double widthOffset = std::fmod(
3868  ? ( offsetX * width / 100 )
3870  width );
3871 
3872  double offsetY = mOffsetY;
3874  {
3877  }
3878  const double heightOffset = std::fmod(
3880  ? ( offsetY * height / 100 )
3882  height );
3883 
3884  double displacementX = mDisplacementX;
3886  {
3889  }
3890  const double displacementPixelX = mDisplacementXUnit == QgsUnitTypes::RenderPercentage
3891  ? ( displacementX * width / 100 )
3893 
3894  double displacementY = mDisplacementY;
3896  {
3899  }
3900  const double displacementPixelY = mDisplacementYUnit == QgsUnitTypes::RenderPercentage
3901  ? ( displacementY * height / 100 )
3903 
3904  p->setPen( QPen( Qt::NoPen ) );
3905 
3906  if ( context.selected() )
3907  {
3908  QColor selColor = context.renderContext().selectionColor();
3909  p->setBrush( QBrush( selColor ) );
3910  _renderPolygon( p, points, rings, context );
3911  }
3912 
3913  // if invalid parameters, skip out
3914  if ( qgsDoubleNear( width, 0 ) || qgsDoubleNear( height, 0 ) || width < 0 || height < 0 )
3915  return;
3916 
3917  p->save();
3918 
3919  Qgis::MarkerClipMode clipMode = mClipMode;
3921  {
3923  bool ok = false;
3924  const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyMarkerClipping, context.renderContext().expressionContext(), QString(), &ok );
3925  if ( ok )
3926  {
3927  Qgis::MarkerClipMode decodedMode = QgsSymbolLayerUtils::decodeMarkerClipMode( valueString, &ok );
3928  if ( ok )
3929  clipMode = decodedMode;
3930  }
3931  }
3932 
3933  std::unique_ptr< QgsPolygon > shapePolygon;
3934  std::unique_ptr< QgsGeometryEngine > shapeEngine;
3935  switch ( clipMode )
3936  {
3940  {
3941  shapePolygon = std::make_unique< QgsPolygon >();
3942  shapePolygon->setExteriorRing( QgsLineString::fromQPolygonF( points ) );
3943  if ( rings )
3944  {
3945  for ( const QPolygonF &ring : *rings )
3946  {
3947  shapePolygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
3948  }
3949  }
3950  shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3951  shapeEngine->prepareGeometry();
3952  break;
3953  }
3954 
3956  {
3957  QPainterPath path;
3958  path.addPolygon( points );
3959  if ( rings )
3960  {
3961  for ( const QPolygonF &ring : *rings )
3962  {
3963  path.addPolygon( ring );
3964  }
3965  }
3966  p->setClipPath( path, Qt::IntersectClip );
3967  break;
3968  }
3969  }
3970 
3971  const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3972  const QRectF boundingRect = points.boundingRect();
3973 
3974  QTransform invertedRotateTransform;
3975  double left;
3976  double top;
3977  double right;
3978  double bottom;
3979 
3980  if ( !qgsDoubleNear( angle, 0 ) )
3981  {
3982  QTransform transform;
3983  if ( applyBrushTransform )
3984  {
3985  // rotation applies around center of feature
3986  transform.translate( -boundingRect.center().x(),
3987  -boundingRect.center().y() );
3988  transform.rotate( -angle );
3989  transform.translate( boundingRect.center().x(),
3990  boundingRect.center().y() );
3991  }
3992  else
3993  {
3994  // rotation applies around top of viewport
3995  transform.rotate( -angle );
3996  }
3997 
3998  const QRectF transformedBounds = transform.map( points ).boundingRect();
3999  left = transformedBounds.left() - 2 * width;
4000  top = transformedBounds.top() - 2 * height;
4001  right = transformedBounds.right() + 2 * width;
4002  bottom = transformedBounds.bottom() + 2 * height;
4003  invertedRotateTransform = transform.inverted();
4004 
4005  if ( !applyBrushTransform )
4006  {
4007  left -= transformedBounds.left() - ( width * std::floor( transformedBounds.left() / width ) );
4008  top -= transformedBounds.top() - ( height * std::floor( transformedBounds.top() / height ) );
4009  }
4010  }
4011  else
4012  {
4013  left = boundingRect.left() - 2 * width;
4014  top = boundingRect.top() - 2 * height;
4015  right = boundingRect.right() + 2 * width;
4016  bottom = boundingRect.bottom() + 2 * height;
4017 
4018  if ( !applyBrushTransform )
4019  {
4020  left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
4021  top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
4022  }
4023  }
4024 
4025  unsigned long seed = mSeed;
4027  {
4028  context.renderContext().expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
4030  }
4031 
4032  double maxRandomDeviationX = mRandomDeviationX;
4034  {
4035  context.setOriginalValueVariable( maxRandomDeviationX );
4036  maxRandomDeviationX = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetX, context.renderContext().expressionContext(), maxRandomDeviationX );
4037  }
4038  const double maxRandomDeviationPixelX = mRandomDeviationXUnit == QgsUnitTypes::RenderPercentage ? ( maxRandomDeviationX * width / 100 )
4040 
4041  double maxRandomDeviationY = mRandomDeviationY;
4043  {
4044  context.setOriginalValueVariable( maxRandomDeviationY );
4045  maxRandomDeviationY = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetY, context.renderContext().expressionContext(), maxRandomDeviationY );
4046  }
4047  const double maxRandomDeviationPixelY = mRandomDeviationYUnit == QgsUnitTypes::RenderPercentage ? ( maxRandomDeviationY * height / 100 )
4049 
4050  std::random_device rd;
4051  std::mt19937 mt( seed == 0 ? rd() : seed );
4052  std::uniform_real_distribution<> uniformDist( 0, 1 );
4053  const bool useRandomShift = !qgsDoubleNear( maxRandomDeviationPixelX, 0 ) || !qgsDoubleNear( maxRandomDeviationPixelY, 0 );
4054 
4056  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
4057  int pointNum = 0;
4058  const bool needsExpressionContext = mMarkerSymbol->hasDataDefinedProperties();
4059 
4060  const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4062 
4063  bool alternateColumn = false;
4064  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
4065  for ( double currentX = left; currentX <= right; currentX += width, alternateColumn = !alternateColumn )
4066  {
4067  if ( context.renderContext().renderingStopped() )
4068  break;
4069 
4070  if ( needsExpressionContext )
4071  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_column" ), ++currentCol, true ) );
4072 
4073  bool alternateRow = false;
4074  const double columnX = currentX + widthOffset;
4075  int currentRow = -3;
4076  for ( double currentY = top; currentY <= bottom; currentY += height, alternateRow = !alternateRow )
4077  {
4078  if ( context.renderContext().renderingStopped() )
4079  break;
4080 
4081  double y = currentY + heightOffset;
4082  double x = columnX;
4083  if ( alternateRow )
4084  x += displacementPixelX;
4085 
4086  if ( !alternateColumn )
4087  y -= displacementPixelY;
4088 
4089  if ( !qgsDoubleNear( angle, 0 ) )
4090  {
4091  double xx = x;
4092  double yy = y;
4093  invertedRotateTransform.map( xx, yy, &x, &y );
4094  }
4095 
4096  if ( useRandomShift )
4097  {
4098  x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
4099  y += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelY;
4100  }
4101 
4102  if ( needsExpressionContext )
4103  {
4105  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_row" ), ++currentRow, true ) );
4106  }
4107 
4108  if ( shapeEngine )
4109  {
4110  bool renderPoint = true;
4111  switch ( clipMode )
4112  {
4114  {
4115  // 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
4116  const QgsRectangle markerRect = QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) );
4117  QgsPoint p( markerRect.center() );
4118  renderPoint = shapeEngine->intersects( &p );
4119  break;
4120  }
4121 
4124  {
4125  const QgsGeometry markerBounds = QgsGeometry::fromRect( QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) ) );
4126 
4128  renderPoint = shapeEngine->contains( markerBounds.constGet() );
4129  else
4130  renderPoint = shapeEngine->intersects( markerBounds.constGet() );
4131  break;
4132  }
4133 
4135  break;
4136  }
4137 
4138  if ( !renderPoint )
4139  continue;
4140  }
4141 
4142  mMarkerSymbol->renderPoint( QPointF( x, y ), context.feature(), context.renderContext() );
4143  }
4144  }
4145 
4146  p->restore();
4147 
4149 }
4150 
4152 {
4153  QVariantMap map = QgsImageFillSymbolLayer::properties();
4154  map.insert( QStringLiteral( "distance_x" ), QString::number( mDistanceX ) );
4155  map.insert( QStringLiteral( "distance_y" ), QString::number( mDistanceY ) );
4156  map.insert( QStringLiteral( "displacement_x" ), QString::number( mDisplacementX ) );
4157  map.insert( QStringLiteral( "displacement_y" ), QString::number( mDisplacementY ) );
4158  map.insert( QStringLiteral( "offset_x" ), QString::number( mOffsetX ) );
4159  map.insert( QStringLiteral( "offset_y" ), QString::number( mOffsetY ) );
4160  map.insert( QStringLiteral( "distance_x_unit" ), QgsUnitTypes::encodeUnit( mDistanceXUnit ) );
4161  map.insert( QStringLiteral( "distance_y_unit" ), QgsUnitTypes::encodeUnit( mDistanceYUnit ) );
4162  map.insert( QStringLiteral( "displacement_x_unit" ), QgsUnitTypes::encodeUnit( mDisplacementXUnit ) );
4163  map.insert( QStringLiteral( "displacement_y_unit" ), QgsUnitTypes::encodeUnit( mDisplacementYUnit ) );
4164  map.insert( QStringLiteral( "offset_x_unit" ), QgsUnitTypes::encodeUnit( mOffsetXUnit ) );
4165  map.insert( QStringLiteral( "offset_y_unit" ), QgsUnitTypes::encodeUnit( mOffsetYUnit ) );
4166  map.insert( QStringLiteral( "distance_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceXMapUnitScale ) );
4167  map.insert( QStringLiteral( "distance_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceYMapUnitScale ) );
4168  map.insert( QStringLiteral( "displacement_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementXMapUnitScale ) );
4169  map.insert( QStringLiteral( "displacement_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementYMapUnitScale ) );
4170  map.insert( QStringLiteral( "offset_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetXMapUnitScale ) );
4171  map.insert( QStringLiteral( "offset_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetYMapUnitScale ) );
4172  map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
4173  map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
4174  map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeMarkerClipMode( mClipMode ) );
4175  map.insert( QStringLiteral( "random_deviation_x" ), QString::number( mRandomDeviationX ) );
4176  map.insert( QStringLiteral( "random_deviation_y" ), QString::number( mRandomDeviationY ) );
4177  map.insert( QStringLiteral( "random_deviation_x_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationXUnit ) );
4178  map.insert( QStringLiteral( "random_deviation_y_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationYUnit ) );
4179  map.insert( QStringLiteral( "random_deviation_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationXMapUnitScale ) );
4180  map.insert( QStringLiteral( "random_deviation_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationYMapUnitScale ) );
4181  map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
4182  map.insert( QStringLiteral( "angle" ), mAngle );
4183  return map;
4184 }
4185 
4187 {
4189  if ( mMarkerSymbol )
4190  {
4191  clonedLayer->setSubSymbol( mMarkerSymbol->clone() );
4192  }
4193  clonedLayer->setClipMode( mClipMode );
4194  copyDataDefinedProperties( clonedLayer );
4195  copyPaintEffect( clonedLayer );
4196  return clonedLayer;
4197 }
4198 
4199 void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4200 {
4201  for ( int i = 0; i < mMarkerSymbol->symbolLayerCount(); i++ )
4202  {
4203  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
4204  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
4205  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
4206  element.appendChild( symbolizerElem );
4207 
4208  // <Geometry>
4209  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
4210 
4211  QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
4212  symbolizerElem.appendChild( fillElem );
4213 
4214  QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
4215  fillElem.appendChild( graphicFillElem );
4216 
4217  // store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
4220  QString dist = QgsSymbolLayerUtils::encodePoint( QPointF( dx, dy ) );
4221  QDomElement distanceElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "distance" ), dist );
4222  symbolizerElem.appendChild( distanceElem );
4223 
4224  QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( i );
4225  if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
4226  {
4227  markerLayer->writeSldMarker( doc, graphicFillElem, props );
4228  }
4229  else if ( layer )
4230  {
4231  QString errorMsg = QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() );
4232  graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4233  }
4234  else
4235  {
4236  QString errorMsg = QStringLiteral( "Missing point pattern symbol layer. Skip it." );
4237  graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4238  }
4239  }
4240 }
4241 
4243 {
4244  Q_UNUSED( element )
4245  return nullptr;
4246 }
4247 
4249 {
4250  if ( !symbol )
4251  {
4252  return false;
4253  }
4254 
4255  if ( symbol->type() == Qgis::SymbolType::Marker )
4256  {
4257  QgsMarkerSymbol *markerSymbol = static_cast<QgsMarkerSymbol *>( symbol );
4258  mMarkerSymbol.reset( markerSymbol );
4259  }
4260  return true;
4261 }
4262 
4264 {
4265  return mMarkerSymbol.get();
4266 }
4267 
4269 {
4273  && ( !mMarkerSymbol || !mMarkerSymbol->hasDataDefinedProperties() ) )
4274  {
4275  return;
4276  }
4277 
4278  double distanceX = mDistanceX;
4280  {
4283  }
4284  double distanceY = mDistanceY;
4286  {
4289  }
4290  double displacementX = mDisplacementX;
4292  {
4295  }
4296  double displacementY = mDisplacementY;
4298  {
4301  }
4302  double offsetX = mOffsetX;
4304  {
4307  }
4308  double offsetY = mOffsetY;
4310  {
4313  }
4314  applyPattern( context, mBrush, distanceX, distanceY, displacementX, displacementY, offsetX, offsetY );
4315 }
4316 
4318 {
4319  return 0;
4320 }
4321 
4323 {
4324  QSet<QString> attributes = QgsImageFillSymbolLayer::usedAttributes( context );
4325 
4326  if ( mMarkerSymbol )
4327  attributes.unite( mMarkerSymbol->usedAttributes( context ) );
4328 
4329  return attributes;
4330 }
4331 
4333 {
4335  return true;
4336  if ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
4337  return true;
4338  return false;
4339 }
4340 
4342 {
4343  mColor = c;
4344  if ( mMarkerSymbol )
4345  mMarkerSymbol->setColor( c );
4346 }
4347 
4349 {
4350  return mMarkerSymbol ? mMarkerSymbol->color() : mColor;
4351 }
4352 
4354 
4355 
4357 {
4358  setSubSymbol( new QgsMarkerSymbol() );
4359 }
4360 
4362 
4363 QgsSymbolLayer *QgsCentroidFillSymbolLayer::create( const QVariantMap &properties )
4364 {
4365  std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4366 
4367  if ( properties.contains( QStringLiteral( "point_on_surface" ) ) )
4368  sl->setPointOnSurface( properties[QStringLiteral( "point_on_surface" )].toInt() != 0 );
4369  if ( properties.contains( QStringLiteral( "point_on_all_parts" ) ) )
4370  sl->setPointOnAllParts( properties[QStringLiteral( "point_on_all_parts" )].toInt() != 0 );
4371  if ( properties.contains( QStringLiteral( "clip_points" ) ) )
4372  sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() != 0 );
4373  if ( properties.contains( QStringLiteral( "clip_on_current_part_only" ) ) )
4374  sl->setClipOnCurrentPartOnly( properties[QStringLiteral( "clip_on_current_part_only" )].toInt() != 0 );
4375 
4376  sl->restoreOldDataDefinedProperties( properties );
4377 
4378  return sl.release();
4379 }
4380 
4382 {
4383  return QStringLiteral( "CentroidFill" );
4384 }
4385 
4386 void QgsCentroidFillSymbolLayer::setColor( const QColor &color )
4387 {
4388  mMarker->setColor( color );
4389  mColor = color;
4390 }
4391 
4393 {
4394  return mMarker ? mMarker->color() : mColor;
4395 }
4396 
4398 {
4399  mMarker->startRender( context.renderContext(), context.fields() );
4400 }
4401 
4403 {
4404  mMarker->stopRender( context.renderContext() );
4405 }
4406 
4407 void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4408 {
4409  Part part;
4410  part.exterior = points;
4411  if ( rings )
4412  part.rings = *rings;
4413 
4414  if ( mRenderingFeature )
4415  {
4416  // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
4417  // until after we've received the final part
4418  mFeatureSymbolOpacity = context.opacity();
4419  mCurrentParts << part;
4420  }
4421  else
4422  {
4423  // not rendering a feature, so we can just render the polygon immediately
4424  const double prevOpacity = mMarker->opacity();
4425  mMarker->setOpacity( mMarker->opacity() * context.opacity() );
4426  render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), context.selected() );
4427  mMarker->setOpacity( prevOpacity );
4428  }
4429 }
4430 
4432 {
4433  mRenderingFeature = true;
4434  mCurrentParts.clear();
4435 }
4436 
4438 {
4439  mRenderingFeature = false;
4440 
4441  const double prevOpacity = mMarker->opacity();
4442  mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
4443 
4444  render( context, mCurrentParts, feature, false );
4446  mMarker->setOpacity( prevOpacity );
4447 }
4448 
4449 void QgsCentroidFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsCentroidFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
4450 {
4453  bool clipPoints = mClipPoints;
4455 
4456  // TODO add expressions support
4457 
4458  QVector< QgsGeometry > geometryParts;
4459  geometryParts.reserve( parts.size() );
4460  QPainterPath globalPath;
4461 
4462  int maxArea = 0;
4463  int maxAreaPartIdx = 0;
4464 
4465  for ( int i = 0; i < parts.size(); i++ )
4466  {
4467  const Part part = parts[i];
4468  QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
4469 
4470  if ( !geom.isNull() && !part.rings.empty() )
4471  {
4472  QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
4473 
4474  if ( !pointOnAllParts )
4475  {
4476  int area = poly->area();
4477 
4478  if ( area > maxArea )
4479  {
4480  maxArea = area;
4481  maxAreaPartIdx = i;
4482  }
4483  }
4484  }
4485 
4487  {
4488  globalPath.addPolygon( part.exterior );
4489  for ( const QPolygonF &ring : part.rings )
4490  {
4491  globalPath.addPolygon( ring );
4492  }
4493  }
4494  }
4495 
4496  for ( int i = 0; i < parts.size(); i++ )
4497  {
4498  if ( !pointOnAllParts && i != maxAreaPartIdx )
4499  continue;
4500 
4501  const Part part = parts[i];
4502 
4503  if ( clipPoints )
4504  {
4505  QPainterPath path;
4506 
4507  if ( clipOnCurrentPartOnly )
4508  {
4509  path.addPolygon( part.exterior );
4510  for ( const QPolygonF &ring : part.rings )
4511  {
4512  path.addPolygon( ring );
4513  }
4514  }
4515  else
4516  {
4517  path = globalPath;
4518  }
4519 
4520  context.painter()->save();
4521  context.painter()->setClipPath( path );
4522  }
4523 
4524  QPointF centroid = pointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( part.exterior, &part.rings ) : QgsSymbolLayerUtils::polygonCentroid( part.exterior );
4525 
4526  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4528  mMarker->renderPoint( centroid, feature.isValid() ? &feature : nullptr, context, -1, selected );
4529  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
4530 
4531  if ( clipPoints )
4532  {
4533  context.painter()->restore();
4534  }
4535  }
4536 }
4537 
4539 {
4540  QVariantMap map;
4541  map[QStringLiteral( "point_on_surface" )] = QString::number( mPointOnSurface );
4542  map[QStringLiteral( "point_on_all_parts" )] = QString::number( mPointOnAllParts );
4543  map[QStringLiteral( "clip_points" )] = QString::number( mClipPoints );
4544  map[QStringLiteral( "clip_on_current_part_only" )] = QString::number( mClipOnCurrentPartOnly );
4545  return map;
4546 }
4547 
4549 {
4550  std::unique_ptr< QgsCentroidFillSymbolLayer > x = std::make_unique< QgsCentroidFillSymbolLayer >();
4551  x->mAngle = mAngle;
4552  x->mColor = mColor;
4553  x->setSubSymbol( mMarker->clone() );
4554  x->setPointOnSurface( mPointOnSurface );
4555  x->setPointOnAllParts( mPointOnAllParts );
4556  x->setClipPoints( mClipPoints );
4557  x->setClipOnCurrentPartOnly( mClipOnCurrentPartOnly );
4558  copyDataDefinedProperties( x.get() );
4559  copyPaintEffect( x.get() );
4560  return x.release();
4561 }
4562 
4563 void QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4564 {
4565  // SLD 1.0 specs says: "if a line, polygon, or raster geometry is
4566  // used with PointSymbolizer, then the semantic is to use the centroid
4567  // of the geometry, or any similar representative point.
4568  mMarker->toSld( doc, element, props );
4569 }
4570 
4572 {
4574  if ( !l )
4575  return nullptr;
4576 
4577  QgsSymbolLayerList layers;
4578  layers.append( l );
4579  std::unique_ptr< QgsMarkerSymbol > marker( new QgsMarkerSymbol( layers ) );
4580 
4581  std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4582  sl->setSubSymbol( marker.release() );
4583  sl->setPointOnAllParts( false );
4584  return sl.release();
4585 }
4586 
4587 
4589 {
4590  return mMarker.get();
4591 }
4592 
4594 {
4595  if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
4596  {
4597  delete symbol;
4598  return false;
4599  }
4600 
4601  mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
4602  mColor = mMarker->color();
4603  return true;
4604 }
4605 
4607 {
4608  QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
4609 
4610  if ( mMarker )
4611  attributes.unite( mMarker->usedAttributes( context ) );
4612 
4613  return attributes;
4614 }
4615 
4617 {
4619  return true;
4620  if ( mMarker && mMarker->hasDataDefinedProperties() )
4621  return true;
4622  return false;
4623 }
4624 
4626 {
4627  return true;
4628 }
4629 
4631 {
4632  if ( mMarker )
4633  {
4634  mMarker->setOutputUnit( unit );
4635  }
4636 }
4637 
4639 {
4640  if ( mMarker )
4641  {
4642  return mMarker->outputUnit();
4643  }
4644  return QgsUnitTypes::RenderUnknownUnit; //mOutputUnit;
4645 }
4646 
4648 {
4649  if ( mMarker )
4650  {
4651  return mMarker->usesMapUnits();
4652  }
4653  return false;
4654 }
4655 
4657 {
4658  if ( mMarker )
4659  {
4660  mMarker->setMapUnitScale( scale );
4661  }
4662 }
4663 
4665 {
4666  if ( mMarker )
4667  {
4668  return mMarker->mapUnitScale();
4669  }
4670  return QgsMapUnitScale();
4671 }
4672 
4673 
4674 
4675 
4678  , mImageFilePath( imageFilePath )
4679 {
4680  QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //disable sub symbol
4682 }
4683 
4685 
4686 QgsSymbolLayer *QgsRasterFillSymbolLayer::create( const QVariantMap &properties )
4687 {
4689  double alpha = 1.0;
4690  QPointF offset;
4691  double angle = 0.0;
4692  double width = 0.0;
4693 
4694  QString imagePath;
4695  if ( properties.contains( QStringLiteral( "imageFile" ) ) )
4696  {
4697  imagePath = properties[QStringLiteral( "imageFile" )].toString();
4698  }
4699  if ( properties.contains( QStringLiteral( "coordinate_mode" ) ) )
4700  {
4701  mode = static_cast< Qgis::SymbolCoordinateReference >( properties[QStringLiteral( "coordinate_mode" )].toInt() );
4702  }
4703  if ( properties.contains( QStringLiteral( "alpha" ) ) )
4704  {
4705  alpha = properties[QStringLiteral( "alpha" )].toDouble();
4706  }
4707  if ( properties.contains( QStringLiteral( "offset" ) ) )
4708  {
4709  offset = QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "offset" )].toString() );
4710  }
4711  if ( properties.contains( QStringLiteral( "angle" ) ) )
4712  {
4713  angle = properties[QStringLiteral( "angle" )].toDouble();
4714  }
4715  if ( properties.contains( QStringLiteral( "width" ) ) )
4716  {
4717  width = properties[QStringLiteral( "width" )].toDouble();
4718  }
4719  std::unique_ptr< QgsRasterFillSymbolLayer > symbolLayer = std::make_unique< QgsRasterFillSymbolLayer >( imagePath );
4720  symbolLayer->setCoordinateMode( mode );
4721  symbolLayer->setOpacity( alpha );
4722  symbolLayer->setOffset( offset );
4723  symbolLayer->setAngle( angle );
4724  symbolLayer->setWidth( width );
4725  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
4726  {
4727  symbolLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
4728  }
4729  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
4730  {
4731  symbolLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
4732  }
4733  if ( properties.contains( QStringLiteral( "width_unit" ) ) )
4734  {
4735  symbolLayer->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "width_unit" )].toString() ) );
4736  }
4737  if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
4738  {
4739  symbolLayer->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
4740  }
4741 
4742  symbolLayer->restoreOldDataDefinedProperties( properties );
4743 
4744  return symbolLayer.release();
4745 }
4746 
4747 void QgsRasterFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
4748 {
4749  QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
4750  if ( it != properties.end() )
4751  {
4752  if ( saving )
4753  it.value() = pathResolver.writePath( it.value().toString() );
4754  else
4755  it.value() = pathResolver.readPath( it.value().toString() );
4756  }
4757 }
4758 
4760 {
4761  Q_UNUSED( symbol )
4762  return true;
4763 }
4764 
4766 {
4767  return QStringLiteral( "RasterFill" );
4768 }
4769 
4770 void QgsRasterFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4771 {
4772  QPainter *p = context.renderContext().painter();
4773  if ( !p )
4774  {
4775  return;
4776  }
4777 
4778  QPointF offset = mOffset;
4780  {
4782  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
4783  bool ok = false;
4784  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
4785  if ( ok )
4786  offset = res;
4787  }
4788  if ( !offset.isNull() )
4789  {
4790  offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
4791  offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
4792  p->translate( offset );
4793  }
4794  if ( mCoordinateMode == Qgis::SymbolCoordinateReference::Feature )
4795  {
4796  QRectF boundingRect = points.boundingRect();
4797  mBrush.setTransform( mBrush.transform().translate( boundingRect.left() - mBrush.transform().dx(),
4798  boundingRect.top() - mBrush.transform().dy() ) );
4799  }
4800 
4801  QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
4802  if ( !offset.isNull() )
4803  {
4804  p->translate( -offset );
4805  }
4806 }
4807 
4809 {
4810  applyPattern( mBrush, mImageFilePath, mWidth, mOpacity * context.opacity(), context );
4811 }
4812 
4814 {
4815  Q_UNUSED( context )
4816 }
4817 
4819 {
4820  QVariantMap map;
4821  map[QStringLiteral( "imageFile" )] = mImageFilePath;
4822  map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
4823  map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
4824  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
4825  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
4826  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
4827  map[QStringLiteral( "angle" )] = QString::number( mAngle );
4828  map[QStringLiteral( "width" )] = QString::number( mWidth );
4829  map[QStringLiteral( "width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
4830  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
4831  return map;
4832 }
4833 
4835 {
4836  std::unique_ptr< QgsRasterFillSymbolLayer > sl = std::make_unique< QgsRasterFillSymbolLayer >( mImageFilePath );
4837  sl->setCoordinateMode( mCoordinateMode );
4838  sl->setOpacity( mOpacity );
4839  sl->setOffset( mOffset );
4840  sl->setOffsetUnit( mOffsetUnit );
4841  sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
4842  sl->setAngle( mAngle );
4843  sl->setWidth( mWidth );
4844  sl->setWidthUnit( mWidthUnit );
4845  sl->setWidthMapUnitScale( mWidthMapUnitScale );
4846  copyDataDefinedProperties( sl.get() );
4847  copyPaintEffect( sl.get() );
4848  return sl.release();
4849 }
4850 
4852 {
4853  return context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
4854 }
4855 
4857 {
4858  return mWidthUnit == QgsUnitTypes::RenderMapUnits || mWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
4859  || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
4860 }
4861 
4863 {
4864  return QColor();
4865 }
4866 
4867 void QgsRasterFillSymbolLayer::setImageFilePath( const QString &imagePath )
4868 {
4869  mImageFilePath = imagePath;
4870 }
4871 
4873 {
4874  mCoordinateMode = mode;
4875 }
4876 
4877 void QgsRasterFillSymbolLayer::setOpacity( const double opacity )
4878 {
4879  mOpacity = opacity;
4880 }
4881 
4883 {
4884  if ( !dataDefinedProperties().hasActiveProperties() )
4885  return; // shortcut
4886 
4887  bool hasWidthExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyWidth );
4888  bool hasFileExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyFile );
4889  bool hasOpacityExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyOpacity );
4890  bool hasAngleExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyAngle );
4891 
4892  if ( !hasWidthExpression && !hasAngleExpression && !hasOpacityExpression && !hasFileExpression )
4893  {
4894  return; //no data defined settings
4895  }
4896 
4897  bool ok;
4898  if ( hasAngleExpression )
4899  {
4900  context.setOriginalValueVariable( mAngle );
4902  if ( ok )
4903  mNextAngle = nextAngle;
4904  }
4905 
4906  if ( !hasWidthExpression && !hasOpacityExpression && !hasFileExpression )
4907  {
4908  return; //nothing further to do
4909  }
4910 
4911  double width = mWidth;
4912  if ( hasWidthExpression )
4913  {
4914  context.setOriginalValueVariable( mWidth );
4916  }
4917  double opacity = mOpacity;
4918  if ( hasOpacityExpression )
4919  {
4920  context.setOriginalValueVariable( mOpacity );
4922  }
4923  QString file = mImageFilePath;
4924  if ( hasFileExpression )
4925  {
4926  context.setOriginalValueVariable( mImageFilePath );
4928  }
4929  applyPattern( mBrush, file, width, opacity, context );
4930 }
4931 
4933 {
4934  return false;
4935 }
4936 
4937 void QgsRasterFillSymbolLayer::applyPattern( QBrush &brush, const QString &imageFilePath, const double width, const double alpha, const QgsSymbolRenderContext &context )
4938 {
4939  QSize size;
4940  if ( width > 0 )
4941  {
4942  if ( mWidthUnit != QgsUnitTypes::RenderPercentage )
4943  {
4944  size.setWidth( context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale ) );
4945  }
4946  else
4947  {
4948  // RenderPercentage Unit Type takes original image size
4950  if ( size.isEmpty() )
4951  return;
4952 
4953  size.setWidth( ( width * size.width() ) / 100.0 );
4954 
4955  // don't render symbols with size below one or above 10,000 pixels
4956  if ( static_cast< int >( size.width() ) < 1 || 10000.0 < size.width() )
4957  return;
4958  }
4959 
4960  size.setHeight( 0 );
4961  }
4962 
4963  bool cached;
4964  QImage img = QgsApplication::imageCache()->pathAsImage( imageFilePath, size, true, alpha, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
4965  if ( img.isNull() )
4966  return;
4967 
4968  brush.setTextureImage( img );
4969 }
4970 
4971 
4972 //
4973 // QgsRandomMarkerFillSymbolLayer
4974 //
4975 
4976 QgsRandomMarkerFillSymbolLayer::QgsRandomMarkerFillSymbolLayer( int pointCount, Qgis::PointCountMethod method, double densityArea, unsigned long seed )
4977  : mCountMethod( method )
4978  , mPointCount( pointCount )
4979  , mDensityArea( densityArea )
4980  , mSeed( seed )
4981 {
4982  setSubSymbol( new QgsMarkerSymbol() );
4983 }
4984 
4986 
4988 {
4989  const Qgis::PointCountMethod countMethod = static_cast< Qgis::PointCountMethod >( properties.value( QStringLiteral( "count_method" ), QStringLiteral( "0" ) ).toInt() );
4990  const int pointCount = properties.value( QStringLiteral( "point_count" ), QStringLiteral( "10" ) ).toInt();
4991  const double densityArea = properties.value( QStringLiteral( "density_area" ), QStringLiteral( "250.0" ) ).toDouble();
4992 
4993  unsigned long seed = 0;
4994  if ( properties.contains( QStringLiteral( "seed" ) ) )
4995  seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
4996  else
4997  {
4998  // if we a creating a new random marker fill from scratch, we default to a random seed
4999  // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
5000  std::random_device rd;
5001  std::mt19937 mt( seed == 0 ? rd() : seed );
5002  std::uniform_int_distribution<> uniformDist( 1, 999999999 );
5003  seed = uniformDist( mt );
5004  }
5005 
5006  std::unique_ptr< QgsRandomMarkerFillSymbolLayer > sl = std::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, countMethod, densityArea, seed );
5007 
5008  if ( properties.contains( QStringLiteral( "density_area_unit" ) ) )
5009  sl->setDensityAreaUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "density_area_unit" )].toString() ) );
5010  if ( properties.contains( QStringLiteral( "density_area_unit_scale" ) ) )
5011  sl->setDensityAreaUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "density_area_unit_scale" )].toString() ) );
5012 
5013  if ( properties.contains( QStringLiteral( "clip_points" ) ) )
5014  {
5015  sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() );
5016  }
5017 
5018  return sl.release();
5019 }
5020 
5022 {
5023  return QStringLiteral( "RandomMarkerFill" );
5024 }
5025 
5026 void QgsRandomMarkerFillSymbolLayer::setColor( const QColor &color )
5027 {
5028  mMarker->setColor( color );
5029  mColor = color;
5030 }
5031 
5033 {
5034  return mMarker ? mMarker->color() : mColor;
5035 }
5036 
5038 {
5039  mMarker->startRender( context.renderContext(), context.fields() );
5040 }
5041 
5043 {
5044  mMarker->stopRender( context.renderContext() );
5045 }
5046 
5047 void QgsRandomMarkerFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5048 {
5049  Part part;
5050  part.exterior = points;
5051  if ( rings )
5052  part.rings = *rings;
5053 
5054  if ( mRenderingFeature )
5055  {
5056  // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
5057  // until after we've received the final part
5058  mFeatureSymbolOpacity = context.opacity();
5059  mCurrentParts << part;
5060  }
5061  else
5062  {
5063  // not rendering a feature, so we can just render the polygon immediately
5064  const double prevOpacity = mMarker->opacity();
5065  mMarker->setOpacity( mMarker->opacity() * context.opacity() );
5066  render( context.renderContext(), QVector< Part>() << part, context.feature() ? *context.feature() : QgsFeature(), context.selected() );
5067  mMarker->setOpacity( prevOpacity );
5068  }
5069 }
5070 
5071 void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsRandomMarkerFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
5072 {
5073  bool clipPoints = mClipPoints;
5075  {
5078  }
5079 
5080  QVector< QgsGeometry > geometryParts;
5081  geometryParts.reserve( parts.size() );
5082  QPainterPath path;
5083 
5084  for ( const Part &part : parts )
5085  {
5086  QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
5087  if ( !geom.isNull() && !part.rings.empty() )
5088  {
5089  QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
5090  for ( const QPolygonF &ring : part.rings )
5091  {
5093  }
5094  }
5095  if ( !geom.isGeosValid() )
5096  {
5097  geom = geom.buffer( 0, 0 );
5098  }
5099  geometryParts << geom;
5100 
5101  if ( clipPoints )
5102  {
5103  path.addPolygon( part.exterior );
5104  for ( const QPolygonF &ring : part.rings )
5105  {
5106  path.addPolygon( ring );
5107  }
5108  }
5109  }
5110 
5111  const QgsGeometry geom = geometryParts.count() != 1 ? QgsGeometry::unaryUnion( geometryParts ) : geometryParts.at( 0 );
5112 
5113  if ( clipPoints )
5114  {
5115  context.painter()->save();
5116  context.painter()->setClipPath( path );
5117  }
5118 
5119 
5120  int count = mPointCount;
5122  {
5123  context.expressionContext().setOriginalValueVariable( count );
5125  }
5126 
5127  switch ( mCountMethod )
5128  {
5129  case Qgis::PointCountMethod::DensityBased:
5130  {
5131  double densityArea = mDensityArea;
5133  {
5136  }
5137  densityArea = context.convertToPainterUnits( std::sqrt( densityArea ), mDensityAreaUnit, mDensityAreaUnitScale );
5138  densityArea = std::pow( densityArea, 2 );
5139  count = std::max( 0.0, std::ceil( count * ( geom.area() / densityArea ) ) );
5140  break;
5141  }
5142  case Qgis::PointCountMethod::Absolute:
5143  break;
5144  }
5145 
5146  unsigned long seed = mSeed;
5148  {
5149  context.expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
5151  }
5152 
5153  QVector< QgsPointXY > randomPoints = geom.randomPointsInPolygon( count, seed );
5154 #if 0
5155  // in some cases rendering from top to bottom is nice (e.g. randomised tree markers), but in other cases it's not wanted..
5156  // TODO consider exposing this as an option
5157  std::sort( randomPoints.begin(), randomPoints.end(), []( const QgsPointXY & a, const QgsPointXY & b )->bool
5158  {
5159  return a.y() < b.y();
5160  } );
5161 #endif
5163  QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scope );
5164  int pointNum = 0;
5165  const bool needsExpressionContext = mMarker->hasDataDefinedProperties();
5166 
5167  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
5169 
5170  for ( const QgsPointXY &p : std::as_const( randomPoints ) )
5171  {
5172  if ( needsExpressionContext )
5174  mMarker->renderPoint( QPointF( p.x(), p.y() ), feature.isValid() ? &feature : nullptr, context, -1, selected );
5175  }
5176 
5177  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
5178 
5179  if ( clipPoints )
5180  {
5181  context.painter()->restore();
5182  }
5183 }
5184 
5186 {
5187  QVariantMap map;
5188  map.insert( QStringLiteral( "count_method" ), QString::number( static_cast< int >( mCountMethod ) ) );
5189  map.insert( QStringLiteral( "point_count" ), QString::number( mPointCount ) );
5190  map.insert( QStringLiteral( "density_area" ), QString::number( mDensityArea ) );
5191  map.insert( QStringLiteral( "density_area_unit" ), QgsUnitTypes::encodeUnit( mDensityAreaUnit ) );
5192  map.insert( QStringLiteral( "density_area_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDensityAreaUnitScale ) );
5193  map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
5194  map.insert( QStringLiteral( "clip_points" ), QString::number( mClipPoints ) );
5195  return map;
5196 }
5197 
5199 {
5200  std::unique_ptr< QgsRandomMarkerFillSymbolLayer > res = std::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mCountMethod, mDensityArea, mSeed );
5201  res->mAngle = mAngle;
5202  res->mColor = mColor;
5203  res->setDensityAreaUnit( mDensityAreaUnit );
5204  res->setDensityAreaUnitScale( mDensityAreaUnitScale );
5205  res->mClipPoints = mClipPoints;
5206  res->setSubSymbol( mMarker->clone() );
5207  copyDataDefinedProperties( res.get() );
5208  copyPaintEffect( res.get() );
5209  return res.release();
5210 }
5211 
5213 {
5214  return true;
5215 }
5216 
5218 {
5219  return mMarker.get();
5220 }
5221 
5223 {
5224  if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5225  {
5226  delete symbol;
5227  return false;
5228  }
5229 
5230  mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5231  mColor = mMarker->color();
5232  return true;
5233 }
5234 
5236 {
5237  QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5238 
5239  if ( mMarker )
5240  attributes.unite( mMarker->usedAttributes( context ) );
5241 
5242  return attributes;
5243 }
5244 
5246 {
5248  return true;
5249  if ( mMarker && mMarker->hasDataDefinedProperties() )
5250  return true;
5251  return false;
5252 }
5253 
5255 {
5256  return mPointCount;
5257 }
5258 
5260 {
5261  mPointCount = pointCount;
5262 }
5263 
5265 {
5266  return mSeed;
5267 }
5268 
5270 {
5271  mSeed = seed;
5272 }
5273 
5275 {
5276  return mClipPoints;
5277 }
5278 
5280 {
5281  mClipPoints = clipPoints;
5282 }
5283 
5285 {
5286  return mCountMethod;
5287 }
5288 
5290 {
5291  mCountMethod = method;
5292 }
5293 
5295 {
5296  return mDensityArea;
5297 }
5298 
5300 {
5301  mDensityArea = area;
5302 }
5303 
5305 {
5306  mRenderingFeature = true;
5307  mCurrentParts.clear();
5308 }
5309 
5311 {
5312  mRenderingFeature = false;
5313 
5314  const double prevOpacity = mMarker->opacity();
5315  mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
5316 
5317  render( context, mCurrentParts, feature, false );
5318 
5319  mFeatureSymbolOpacity = 1;
5320  mMarker->setOpacity( prevOpacity );
5321 }
5322 
5323 
5325 {
5326  if ( mMarker )
5327  {
5328  mMarker->setOutputUnit( unit );
5329  }
5330 }
5331 
5333 {
5334  if ( mMarker )
5335  {
5336  return mMarker->outputUnit();
5337  }
5338  return QgsUnitTypes::RenderUnknownUnit; //mOutputUnit;
5339 }
5340 
5342 {
5343  if ( mMarker )
5344  {
5345  return mMarker->usesMapUnits();
5346  }
5347  return false;
5348 }
5349 
5351 {
5352  if ( mMarker )
5353  {
5354  mMarker->setMapUnitScale( scale );
5355  }
5356 }
5357 
5359 {
5360  if ( mMarker )
5361  {
5362  return mMarker->mapUnitScale();
5363  }
5364  return QgsMapUnitScale();
5365 }
5366 
MarkerClipMode
Marker clipping modes.
Definition: qgis.h:1176
@ 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:1190
@ 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:1105
@ 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:1149
@ Repeat
Repeat gradient.
@ Reflect
Reflect gradient.
@ Pad
Pad out gradient using colors at endpoint of gradient.
PointCountMethod
Methods which define the number of points randomly filling a polygon.
Definition: qgis.h:1164
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
GradientType
Gradient types.
Definition: qgis.h:1119
@ 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:1134
@ 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)
void setOutputUnit(QgsUnitTypes::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.
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.
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.
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...
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
~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 QString type() const =0
Returns a string representing the color ramp type.
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
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, QgsUnitTypes::RenderUnit symbolUnits, QgsUnitTypes::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
QgsUnitTypes::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:209
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
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:125
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.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries)
Compute the unary union on a list of geometries.
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
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...
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.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit mOffsetUnit
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
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...
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
Base class for polygon renderers generating texture images.
QgsMapUnitScale mStrokeWidthMapUnitScale
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsMapUnitScale mapUnitScale() const override
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
void setMapUnitScale(const QgsMapUnitScale &scale) override
virtual void applyDataDefinedSettings(QgsSymbolRenderContext &context)
Applies data defined settings prior to generating the fill symbol brush.
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,...
QgsUnitTypes::RenderUnit mStrokeWidthUnit
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.
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.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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 setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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.
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.
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:44
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
QgsUnitTypes::RenderUnit mRandomDeviationYUnit
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,...
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
double distanceX() const
Returns the horizontal distance between rendered markers in the fill.
QgsUnitTypes::RenderUnit mDisplacementXUnit
double displacementY() const
Returns the vertical displacement for odd numbered columns in the pattern.
QgsUnitTypes::RenderUnit mDistanceYUnit
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
QgsUnitTypes::RenderUnit mOffsetXUnit
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.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit mRandomDeviationXUnit
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.
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.
QgsUnitTypes::RenderUnit mDistanceXUnit
QgsUnitTypes::RenderUnit mOffsetYUnit
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.
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.
QgsUnitTypes::RenderUnit mDisplacementYUnit
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
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,...
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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.
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.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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.
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 * 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.
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.
QPainter * painter()
Returns the destination QPainter for the render operation.
double rendererScale() const
Returns the renderer map scale.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
bool forceVectorOutput() const
Returns true if rendering operations should use vector operations instead of any faster raster shortc...
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
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...
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,...
QMap< QString, QgsProperty > parameters() const
Returns the dynamic SVG parameters.
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.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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.
const QgsMapUnitScale & svgStrokeWidthMapUnitScale() const
Returns the map unit scale for the pattern's stroke.
QgsUnitTypes::RenderUnit svgStrokeWidthUnit() const
Returns the units for the stroke width.
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.
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.
const QgsMapUnitScale & patternWidthMapUnitScale() const
Returns the map unit scale for the pattern's width.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
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
QgsUnitTypes::RenderUnit patternWidthUnit() const
Returns the units for the width of the SVG images in the pattern.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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
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 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...
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.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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 ...
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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.
QgsUnitTypes::RenderUnit mOffsetUnit
~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 ...
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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.
QgsMapUnitScale mOffsetMapUnitScale
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
QgsMapUnitScale mStrokeWidthMapUnitScale
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.
QPointF offset()
Returns the offset by which polygons will be translated during rendering.
QgsMapUnitScale mapUnitScale() const override
Qt::PenJoinStyle mPenJoinStyle
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSimpleFillSymbolLayer using the specified properties map containing symbol propertie...
static QgsSymbolLayer * createFromSld(QDomElement &element)
QgsUnitTypes::RenderUnit mStrokeWidthUnit
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 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 double rescaleUom(double size, QgsUnitTypes::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
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 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 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 double sizeInPixelsFromSldUom(const QString &uom, double size)
Returns the size scaled in pixels according to the uom attribute.
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 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 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.
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.
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.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
QgsPropertyCollection mDataDefinedProperties
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.
QgsFields fields() const
Fields of the layer.
bool selected() const
Returns true if symbols should be rendered using the selected symbol coloring and style.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
const QgsFeature * feature() const
Returns the current feature being rendered.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
qreal opacity() const
Returns the opacity for the symbol.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:38
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:97
const QgsMapUnitScale & intervalMapUnitScale() const
Returns the map unit scale for the interval between symbols.
double interval() const
Returns the interval between individual symbols.
void setInterval(double interval)
Sets the interval between individual symbols.
QgsUnitTypes::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE QgsUnitTypes::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:168
@ RenderUnknownUnit
Mixed or unknown units.
Definition: qgsunittypes.h:175
@ RenderMetersInMapUnits
Meters value as Map units.
Definition: qgsunittypes.h:176
@ RenderPercentage
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:172
@ RenderMapUnits
Map units.
Definition: qgsunittypes.h:170
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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1578
QMap< QString, QString > QgsStringMap
Definition: qgis.h:2026
#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:27
Single variable definition for use within a QgsExpressionContextScope.