QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 "qgsunittypes.h"
31 #include "qgsmessagelog.h"
32 #include "qgsapplication.h"
33 #include "qgsimageoperation.h"
34 #include "qgspolygon.h"
35 #include "qgslinestring.h"
37 #include "qgssymbol.h"
38 #include "qgsmarkersymbol.h"
39 #include "qgslinesymbol.h"
40 #include "qgsfeedback.h"
41 
42 #include <QPainter>
43 #include <QFile>
44 #include <QSvgRenderer>
45 #include <QDomDocument>
46 #include <QDomElement>
47 #include <random>
48 
49 #ifndef QT_NO_PRINTER
50 #include <QPrinter>
51 #endif
52 
53 QgsSimpleFillSymbolLayer::QgsSimpleFillSymbolLayer( const QColor &color, Qt::BrushStyle style, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth,
54  Qt::PenJoinStyle penJoinStyle )
55  : mBrushStyle( style )
56  , mStrokeColor( strokeColor )
57  , mStrokeStyle( strokeStyle )
58  , mStrokeWidth( strokeWidth )
59  , mPenJoinStyle( penJoinStyle )
60 {
61  mColor = color;
62 }
63 
65 
67 {
68  mStrokeWidthUnit = unit;
69  mOffsetUnit = unit;
70 }
71 
73 {
75  if ( mOffsetUnit != unit )
76  {
78  }
79  return unit;
80 }
81 
83 {
86 }
87 
89 {
91  mOffsetMapUnitScale = scale;
92 }
93 
95 {
97  {
99  }
100  return QgsMapUnitScale();
101 }
102 
103 void QgsSimpleFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QBrush &brush, QPen &pen, QPen &selPen )
104 {
105  if ( !dataDefinedProperties().hasActiveProperties() )
106  return; // shortcut
107 
108  bool ok;
109 
111  {
114  fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
115  brush.setColor( fillColor );
116  }
118  {
121  if ( !exprVal.isNull() )
122  brush.setStyle( QgsSymbolLayerUtils::decodeBrushStyle( exprVal.toString() ) );
123  }
125  {
128  penColor.setAlphaF( context.opacity() * penColor.alphaF() );
129  pen.setColor( penColor );
130  }
132  {
135  if ( !exprVal.isNull() )
136  {
137  double width = exprVal.toDouble( &ok );
138  if ( ok )
139  {
141  pen.setWidthF( width );
142  selPen.setWidthF( width );
143  }
144  }
145  }
147  {
150  if ( ok )
151  {
152  pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
153  selPen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
154  }
155  }
157  {
160  if ( ok )
161  {
162  pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
163  selPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
164  }
165  }
166 }
167 
168 
170 {
172  Qt::BrushStyle style = DEFAULT_SIMPLEFILL_STYLE;
176  Qt::PenJoinStyle penJoinStyle = DEFAULT_SIMPLEFILL_JOINSTYLE;
177  QPointF offset;
178 
179  if ( props.contains( QStringLiteral( "color" ) ) )
180  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
181  if ( props.contains( QStringLiteral( "style" ) ) )
182  style = QgsSymbolLayerUtils::decodeBrushStyle( props[QStringLiteral( "style" )].toString() );
183  if ( props.contains( QStringLiteral( "color_border" ) ) )
184  {
185  //pre 2.5 projects used "color_border"
186  strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color_border" )].toString() );
187  }
188  else if ( props.contains( QStringLiteral( "outline_color" ) ) )
189  {
190  strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
191  }
192  else if ( props.contains( QStringLiteral( "line_color" ) ) )
193  {
194  strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
195  }
196 
197  if ( props.contains( QStringLiteral( "style_border" ) ) )
198  {
199  //pre 2.5 projects used "style_border"
200  strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "style_border" )].toString() );
201  }
202  else if ( props.contains( QStringLiteral( "outline_style" ) ) )
203  {
204  strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
205  }
206  else if ( props.contains( QStringLiteral( "line_style" ) ) )
207  {
208  strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
209  }
210  if ( props.contains( QStringLiteral( "width_border" ) ) )
211  {
212  //pre 2.5 projects used "width_border"
213  strokeWidth = props[QStringLiteral( "width_border" )].toDouble();
214  }
215  else if ( props.contains( QStringLiteral( "outline_width" ) ) )
216  {
217  strokeWidth = props[QStringLiteral( "outline_width" )].toDouble();
218  }
219  else if ( props.contains( QStringLiteral( "line_width" ) ) )
220  {
221  strokeWidth = props[QStringLiteral( "line_width" )].toDouble();
222  }
223  if ( props.contains( QStringLiteral( "offset" ) ) )
224  offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
225  if ( props.contains( QStringLiteral( "joinstyle" ) ) )
226  penJoinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() );
227 
228  std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, style, strokeColor, strokeStyle, strokeWidth, penJoinStyle );
229  sl->setOffset( offset );
230  if ( props.contains( QStringLiteral( "border_width_unit" ) ) )
231  {
232  sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "border_width_unit" )].toString() ) );
233  }
234  else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
235  {
236  sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
237  }
238  else if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
239  {
240  sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
241  }
242  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
243  sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
244 
245  if ( props.contains( QStringLiteral( "border_width_map_unit_scale" ) ) )
246  sl->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "border_width_map_unit_scale" )].toString() ) );
247  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
248  sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
249 
250  sl->restoreOldDataDefinedProperties( props );
251 
252  return sl.release();
253 }
254 
255 
257 {
258  return QStringLiteral( "SimpleFill" );
259 }
260 
262 {
263  QColor fillColor = mColor;
264  fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
265  mBrush = QBrush( fillColor, mBrushStyle );
266 
267  QColor selColor = context.renderContext().selectionColor();
268  QColor selPenColor = selColor == mColor ? selColor : mStrokeColor;
269  if ( ! SELECTION_IS_OPAQUE )
270  selColor.setAlphaF( context.opacity() );
271  mSelBrush = QBrush( selColor );
272  // N.B. unless a "selection line color" is implemented in addition to the "selection color" option
273  // this would mean symbols with "no fill" look the same whether or not they are selected
274  if ( SELECT_FILL_STYLE )
275  mSelBrush.setStyle( mBrushStyle );
276 
277  QColor strokeColor = mStrokeColor;
278  strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
279  mPen = QPen( strokeColor );
280  mSelPen = QPen( selPenColor );
281  mPen.setStyle( mStrokeStyle );
283  mPen.setJoinStyle( mPenJoinStyle );
284 }
285 
287 {
288  Q_UNUSED( context )
289 }
290 
291 void QgsSimpleFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
292 {
293  QPainter *p = context.renderContext().painter();
294  if ( !p )
295  {
296  return;
297  }
298 
299  QColor fillColor = mColor;
300  fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
301  mBrush.setColor( fillColor );
302  QColor strokeColor = mStrokeColor;
303  strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
304  mPen.setColor( strokeColor );
305 
306  applyDataDefinedSymbology( context, mBrush, mPen, mSelPen );
307 
308  QPointF offset = mOffset;
309 
311  {
313  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
314  bool ok = false;
315  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
316  if ( ok )
317  offset = res;
318  }
319 
320  if ( !offset.isNull() )
321  {
324  p->translate( offset );
325  }
326 
327 #ifndef QT_NO_PRINTER
328  if ( mBrush.style() == Qt::SolidPattern || mBrush.style() == Qt::NoBrush || !dynamic_cast<QPrinter *>( p->device() ) )
329 #endif
330  {
331  p->setPen( context.selected() ? mSelPen : mPen );
332  p->setBrush( context.selected() ? mSelBrush : mBrush );
333  _renderPolygon( p, points, rings, context );
334  }
335 #ifndef QT_NO_PRINTER
336  else
337  {
338  // workaround upstream issue https://github.com/qgis/QGIS/issues/36580
339  // when a non-solid brush is set with opacity, the opacity incorrectly applies to the pen
340  // when exporting to PDF/print devices
341  p->setBrush( context.selected() ? mSelBrush : mBrush );
342  p->setPen( Qt::NoPen );
343  _renderPolygon( p, points, rings, context );
344 
345  p->setPen( context.selected() ? mSelPen : mPen );
346  p->setBrush( Qt::NoBrush );
347  _renderPolygon( p, points, rings, context );
348  }
349 #endif
350 
351  if ( !offset.isNull() )
352  {
353  p->translate( -offset );
354  }
355 }
356 
358 {
359  QVariantMap map;
360  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
361  map[QStringLiteral( "style" )] = QgsSymbolLayerUtils::encodeBrushStyle( mBrushStyle );
362  map[QStringLiteral( "outline_color" )] = QgsSymbolLayerUtils::encodeColor( mStrokeColor );
363  map[QStringLiteral( "outline_style" )] = QgsSymbolLayerUtils::encodePenStyle( mStrokeStyle );
364  map[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
365  map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
366  map[QStringLiteral( "border_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
367  map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
368  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
369  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
370  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
371  return map;
372 }
373 
375 {
376  std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( mColor, mBrushStyle, mStrokeColor, mStrokeStyle, mStrokeWidth, mPenJoinStyle );
377  sl->setOffset( mOffset );
378  sl->setOffsetUnit( mOffsetUnit );
379  sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
380  sl->setStrokeWidthUnit( mStrokeWidthUnit );
381  sl->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
382  copyDataDefinedProperties( sl.get() );
383  copyPaintEffect( sl.get() );
384  return sl.release();
385 }
386 
387 void QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
388 {
389  if ( mBrushStyle == Qt::NoBrush && mStrokeStyle == Qt::NoPen )
390  return;
391 
392  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
393  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
394  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
395  element.appendChild( symbolizerElem );
396 
397  // <Geometry>
398  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
399 
400  if ( mBrushStyle != Qt::NoBrush )
401  {
402  // <Fill>
403  QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
404  symbolizerElem.appendChild( fillElem );
406  }
407 
408  if ( mStrokeStyle != Qt::NoPen )
409  {
410  // <Stroke>
411  QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
412  symbolizerElem.appendChild( strokeElem );
415  }
416 
417  // <se:Displacement>
420 }
421 
422 QString QgsSimpleFillSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
423 {
424  //brush
425  QString symbolStyle;
426  symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStyleBrush( mColor ) );
427  symbolStyle.append( ';' );
428  //pen
429  symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStylePen( mStrokeWidth, mmScaleFactor, mapUnitScaleFactor, mStrokeColor, mPenJoinStyle ) );
430  return symbolStyle;
431 }
432 
434 {
435  QColor color, strokeColor;
436  Qt::BrushStyle fillStyle;
437  Qt::PenStyle strokeStyle;
438  double strokeWidth;
439 
440  QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
441  QgsSymbolLayerUtils::fillFromSld( fillElem, fillStyle, color );
442 
443  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
445 
446  QPointF offset;
448 
449  QString uom = element.attribute( QStringLiteral( "uom" ), QString() );
453 
454  std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, fillStyle, strokeColor, strokeStyle, strokeWidth );
455  sl->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
456  sl->setOffset( offset );
457  return sl.release();
458 }
459 
461 {
462  double penBleed = context.convertToPainterUnits( mStrokeStyle == Qt::NoPen ? 0 : ( mStrokeWidth / 2.0 ), mStrokeWidthUnit, mStrokeWidthMapUnitScale );
463  double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
464  return penBleed + offsetBleed;
465 }
466 
468 {
469  double width = mStrokeWidth;
471  {
474  }
476 }
477 
479 {
480  QColor c = mStrokeColor;
482  {
485  }
486  return c;
487 }
488 
490 {
491  double angle = mAngle;
493  {
494  context.setOriginalValueVariable( mAngle );
496  }
497  return angle;
498 }
499 
501 {
502  return mStrokeStyle;
503 }
504 
506 {
507  QColor c = mColor;
509  {
511  }
512  return c;
513 }
514 
516 {
517  return mBrushStyle;
518 }
519 
520 //QgsGradientFillSymbolLayer
521 
522 QgsGradientFillSymbolLayer::QgsGradientFillSymbolLayer( const QColor &color, const QColor &color2,
523  GradientColorType colorType, GradientType gradientType,
524  GradientCoordinateMode coordinateMode, GradientSpread spread )
525  : mGradientColorType( colorType )
526  , mGradientType( gradientType )
527  , mCoordinateMode( coordinateMode )
528  , mGradientSpread( spread )
529  , mReferencePoint1( QPointF( 0.5, 0 ) )
530  , mReferencePoint2( QPointF( 0.5, 1 ) )
531 {
532  mColor = color;
533  mColor2 = color2;
534 }
535 
537 {
538  delete mGradientRamp;
539 }
540 
542 {
543  //default to a two-color, linear gradient with feature mode and pad spreading
548  //default to gradient from the default fill color to white
549  QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
550  QPointF referencePoint1 = QPointF( 0.5, 0 );
551  bool refPoint1IsCentroid = false;
552  QPointF referencePoint2 = QPointF( 0.5, 1 );
553  bool refPoint2IsCentroid = false;
554  double angle = 0;
555  QPointF offset;
556 
557  //update gradient properties from props
558  if ( props.contains( QStringLiteral( "type" ) ) )
559  type = static_cast< GradientType >( props[QStringLiteral( "type" )].toInt() );
560  if ( props.contains( QStringLiteral( "coordinate_mode" ) ) )
561  coordinateMode = static_cast< GradientCoordinateMode >( props[QStringLiteral( "coordinate_mode" )].toInt() );
562  if ( props.contains( QStringLiteral( "spread" ) ) )
563  gradientSpread = static_cast< GradientSpread >( props[QStringLiteral( "spread" )].toInt() );
564  if ( props.contains( QStringLiteral( "color_type" ) ) )
565  colorType = static_cast< GradientColorType >( props[QStringLiteral( "color_type" )].toInt() );
566  if ( props.contains( QStringLiteral( "gradient_color" ) ) )
567  {
568  //pre 2.5 projects used "gradient_color"
569  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color" )].toString() );
570  }
571  else if ( props.contains( QStringLiteral( "color" ) ) )
572  {
573  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
574  }
575  if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
576  {
577  color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
578  }
579 
580  if ( props.contains( QStringLiteral( "reference_point1" ) ) )
581  referencePoint1 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point1" )].toString() );
582  if ( props.contains( QStringLiteral( "reference_point1_iscentroid" ) ) )
583  refPoint1IsCentroid = props[QStringLiteral( "reference_point1_iscentroid" )].toInt();
584  if ( props.contains( QStringLiteral( "reference_point2" ) ) )
585  referencePoint2 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point2" )].toString() );
586  if ( props.contains( QStringLiteral( "reference_point2_iscentroid" ) ) )
587  refPoint2IsCentroid = props[QStringLiteral( "reference_point2_iscentroid" )].toInt();
588  if ( props.contains( QStringLiteral( "angle" ) ) )
589  angle = props[QStringLiteral( "angle" )].toDouble();
590 
591  if ( props.contains( QStringLiteral( "offset" ) ) )
592  offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
593 
594  //attempt to create color ramp from props
595  QgsColorRamp *gradientRamp = nullptr;
596  if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
597  {
598  gradientRamp = QgsCptCityColorRamp::create( props );
599  }
600  else
601  {
602  gradientRamp = QgsGradientColorRamp::create( props );
603  }
604 
605  //create a new gradient fill layer with desired properties
606  std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( color, color2, colorType, type, coordinateMode, gradientSpread );
607  sl->setOffset( offset );
608  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
609  sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
610  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
611  sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
612  sl->setReferencePoint1( referencePoint1 );
613  sl->setReferencePoint1IsCentroid( refPoint1IsCentroid );
614  sl->setReferencePoint2( referencePoint2 );
615  sl->setReferencePoint2IsCentroid( refPoint2IsCentroid );
616  sl->setAngle( angle );
617  if ( gradientRamp )
618  sl->setColorRamp( gradientRamp );
619 
620  sl->restoreOldDataDefinedProperties( props );
621 
622  return sl.release();
623 }
624 
626 {
627  delete mGradientRamp;
628  mGradientRamp = ramp;
629 }
630 
632 {
633  return QStringLiteral( "GradientFill" );
634 }
635 
636 void QgsGradientFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, const QPolygonF &points )
637 {
638  if ( !dataDefinedProperties().hasActiveProperties() && !mReferencePoint1IsCentroid && !mReferencePoint2IsCentroid )
639  {
640  //shortcut
643  return;
644  }
645 
646  bool ok;
647 
648  //first gradient color
649  QColor color = mColor;
651  {
654  color.setAlphaF( context.opacity() * color.alphaF() );
655  }
656 
657  //second gradient color
658  QColor color2 = mColor2;
660  {
663  color2.setAlphaF( context.opacity() * color2.alphaF() );
664  }
665 
666  //gradient rotation angle
667  double angle = mAngle;
669  {
670  context.setOriginalValueVariable( mAngle );
672  }
673 
674  //gradient type
677  {
679  if ( ok )
680  {
681  if ( currentType == QObject::tr( "linear" ) )
682  {
684  }
685  else if ( currentType == QObject::tr( "radial" ) )
686  {
688  }
689  else if ( currentType == QObject::tr( "conical" ) )
690  {
692  }
693  }
694  }
695 
696  //coordinate mode
699  {
700  QString currentCoordMode = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyCoordinateMode, context.renderContext().expressionContext(), QString(), &ok );
701  if ( ok )
702  {
703  if ( currentCoordMode == QObject::tr( "feature" ) )
704  {
706  }
707  else if ( currentCoordMode == QObject::tr( "viewport" ) )
708  {
710  }
711  }
712  }
713 
714  //gradient spread
717  {
719  if ( ok )
720  {
721  if ( currentSpread == QObject::tr( "pad" ) )
722  {
724  }
725  else if ( currentSpread == QObject::tr( "repeat" ) )
726  {
728  }
729  else if ( currentSpread == QObject::tr( "reflect" ) )
730  {
732  }
733  }
734  }
735 
736  //reference point 1 x & y
737  double refPoint1X = mReferencePoint1.x();
739  {
740  context.setOriginalValueVariable( refPoint1X );
742  }
743  double refPoint1Y = mReferencePoint1.y();
745  {
746  context.setOriginalValueVariable( refPoint1Y );
748  }
749  bool refPoint1IsCentroid = mReferencePoint1IsCentroid;
751  {
752  context.setOriginalValueVariable( refPoint1IsCentroid );
754  }
755 
756  //reference point 2 x & y
757  double refPoint2X = mReferencePoint2.x();
759  {
760  context.setOriginalValueVariable( refPoint2X );
762  }
763  double refPoint2Y = mReferencePoint2.y();
765  {
766  context.setOriginalValueVariable( refPoint2Y );
768  }
769  bool refPoint2IsCentroid = mReferencePoint2IsCentroid;
771  {
772  context.setOriginalValueVariable( refPoint2IsCentroid );
774  }
775 
776  if ( refPoint1IsCentroid || refPoint2IsCentroid )
777  {
778  //either the gradient is starting or ending at a centroid, so calculate it
780  //centroid coordinates need to be scaled to a range [0, 1] relative to polygon bounds
781  QRectF bbox = points.boundingRect();
782  double centroidX = ( centroid.x() - bbox.left() ) / bbox.width();
783  double centroidY = ( centroid.y() - bbox.top() ) / bbox.height();
784 
785  if ( refPoint1IsCentroid )
786  {
787  refPoint1X = centroidX;
788  refPoint1Y = centroidY;
789  }
790  if ( refPoint2IsCentroid )
791  {
792  refPoint2X = centroidX;
793  refPoint2Y = centroidY;
794  }
795  }
796 
797  //update gradient with data defined values
799  spread, QPointF( refPoint1X, refPoint1Y ), QPointF( refPoint2X, refPoint2Y ), angle );
800 }
801 
802 QPointF QgsGradientFillSymbolLayer::rotateReferencePoint( QPointF refPoint, double angle )
803 {
804  //rotate a reference point by a specified angle around the point (0.5, 0.5)
805 
806  //create a line from the centrepoint of a rectangle bounded by (0, 0) and (1, 1) to the reference point
807  QLineF refLine = QLineF( QPointF( 0.5, 0.5 ), refPoint );
808  //rotate this line by the current rotation angle
809  refLine.setAngle( refLine.angle() + angle );
810  //get new end point of line
811  QPointF rotatedReferencePoint = refLine.p2();
812  //make sure coords of new end point is within [0, 1]
813  if ( rotatedReferencePoint.x() > 1 )
814  rotatedReferencePoint.setX( 1 );
815  if ( rotatedReferencePoint.x() < 0 )
816  rotatedReferencePoint.setX( 0 );
817  if ( rotatedReferencePoint.y() > 1 )
818  rotatedReferencePoint.setY( 1 );
819  if ( rotatedReferencePoint.y() < 0 )
820  rotatedReferencePoint.setY( 0 );
821 
822  return rotatedReferencePoint;
823 }
824 
825 void QgsGradientFillSymbolLayer::applyGradient( const QgsSymbolRenderContext &context, QBrush &brush,
826  const QColor &color, const QColor &color2, GradientColorType gradientColorType,
827  QgsColorRamp *gradientRamp, GradientType gradientType,
828  GradientCoordinateMode coordinateMode, GradientSpread gradientSpread,
829  QPointF referencePoint1, QPointF referencePoint2, const double angle )
830 {
831  //update alpha of gradient colors
832  QColor fillColor = color;
833  fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
834  QColor fillColor2 = color2;
835  fillColor2.setAlphaF( context.opacity() * fillColor2.alphaF() );
836 
837  //rotate reference points
838  QPointF rotatedReferencePoint1 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint1, angle ) : referencePoint1;
839  QPointF rotatedReferencePoint2 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint2, angle ) : referencePoint2;
840 
841  //create a QGradient with the desired properties
842  QGradient gradient;
843  switch ( gradientType )
844  {
846  gradient = QLinearGradient( rotatedReferencePoint1, rotatedReferencePoint2 );
847  break;
849  gradient = QRadialGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).length() );
850  break;
852  gradient = QConicalGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).angle() );
853  break;
854  }
855  switch ( coordinateMode )
856  {
858  gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
859  break;
861  gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
862  break;
863  }
864  switch ( gradientSpread )
865  {
867  gradient.setSpread( QGradient::PadSpread );
868  break;
870  gradient.setSpread( QGradient::ReflectSpread );
871  break;
873  gradient.setSpread( QGradient::RepeatSpread );
874  break;
875  }
876 
877  //add stops to gradient
879  ( gradientRamp->type() == QgsGradientColorRamp::typeString() || gradientRamp->type() == QgsCptCityColorRamp::typeString() ) )
880  {
881  //color ramp gradient
882  QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( gradientRamp );
883  gradRamp->addStopsToGradient( &gradient, context.opacity() );
884  }
885  else
886  {
887  //two color gradient
888  gradient.setColorAt( 0.0, fillColor );
889  gradient.setColorAt( 1.0, fillColor2 );
890  }
891 
892  //update QBrush use gradient
893  brush = QBrush( gradient );
894 }
895 
897 {
898  QColor selColor = context.renderContext().selectionColor();
899  if ( ! SELECTION_IS_OPAQUE )
900  selColor.setAlphaF( context.opacity() );
901  mSelBrush = QBrush( selColor );
902 }
903 
905 {
906  Q_UNUSED( context )
907 }
908 
909 void QgsGradientFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
910 {
911  QPainter *p = context.renderContext().painter();
912  if ( !p )
913  {
914  return;
915  }
916 
917  applyDataDefinedSymbology( context, points );
918 
919  p->setBrush( context.selected() ? mSelBrush : mBrush );
920  p->setPen( Qt::NoPen );
921 
922  QPointF offset = mOffset;
924  {
926  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
927  bool ok = false;
928  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
929  if ( ok )
930  offset = res;
931  }
932 
933  if ( !offset.isNull() )
934  {
937  p->translate( offset );
938  }
939 
940  _renderPolygon( p, points, rings, context );
941 
942  if ( !offset.isNull() )
943  {
944  p->translate( -offset );
945  }
946 }
947 
949 {
950  QVariantMap map;
951  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
952  map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
953  map[QStringLiteral( "color_type" )] = QString::number( mGradientColorType );
954  map[QStringLiteral( "type" )] = QString::number( mGradientType );
955  map[QStringLiteral( "coordinate_mode" )] = QString::number( mCoordinateMode );
956  map[QStringLiteral( "spread" )] = QString::number( mGradientSpread );
957  map[QStringLiteral( "reference_point1" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint1 );
958  map[QStringLiteral( "reference_point1_iscentroid" )] = QString::number( mReferencePoint1IsCentroid );
959  map[QStringLiteral( "reference_point2" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint2 );
960  map[QStringLiteral( "reference_point2_iscentroid" )] = QString::number( mReferencePoint2IsCentroid );
961  map[QStringLiteral( "angle" )] = QString::number( mAngle );
962  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
963  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
964  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
965  if ( mGradientRamp )
966  {
967 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
968  map.unite( mGradientRamp->properties() );
969 #else
970  map.insert( mGradientRamp->properties() );
971 #endif
972  }
973  return map;
974 }
975 
977 {
978  std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( mColor, mColor2, mGradientColorType, mGradientType, mCoordinateMode, mGradientSpread );
979  if ( mGradientRamp )
980  sl->setColorRamp( mGradientRamp->clone() );
981  sl->setReferencePoint1( mReferencePoint1 );
982  sl->setReferencePoint1IsCentroid( mReferencePoint1IsCentroid );
983  sl->setReferencePoint2( mReferencePoint2 );
984  sl->setReferencePoint2IsCentroid( mReferencePoint2IsCentroid );
985  sl->setAngle( mAngle );
986  sl->setOffset( mOffset );
987  sl->setOffsetUnit( mOffsetUnit );
988  sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
989  copyDataDefinedProperties( sl.get() );
990  copyPaintEffect( sl.get() );
991  return sl.release();
992 }
993 
995 {
996  double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
997  return offsetBleed;
998 }
999 
1001 {
1002  return true;
1003 }
1004 
1006 {
1007  mOffsetUnit = unit;
1008 }
1009 
1011 {
1012  return mOffsetUnit;
1013 }
1014 
1016 {
1018 }
1019 
1021 {
1022  mOffsetMapUnitScale = scale;
1023 }
1024 
1026 {
1027  return mOffsetMapUnitScale;
1028 }
1029 
1030 //QgsShapeburstFillSymbolLayer
1031 
1032 QgsShapeburstFillSymbolLayer::QgsShapeburstFillSymbolLayer( const QColor &color, const QColor &color2, ShapeburstColorType colorType,
1033  int blurRadius, bool useWholeShape, double maxDistance )
1034  : mBlurRadius( blurRadius )
1035  , mUseWholeShape( useWholeShape )
1036  , mMaxDistance( maxDistance )
1037  , mColorType( colorType )
1038  , mColor2( color2 )
1039 {
1040  mColor = color;
1041 }
1042 
1044 
1046 {
1047  //default to a two-color gradient
1049  QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
1050  int blurRadius = 0;
1051  bool useWholeShape = true;
1052  double maxDistance = 5;
1053  QPointF offset;
1054 
1055  //update fill properties from props
1056  if ( props.contains( QStringLiteral( "color_type" ) ) )
1057  {
1058  colorType = static_cast< ShapeburstColorType >( props[QStringLiteral( "color_type" )].toInt() );
1059  }
1060  if ( props.contains( QStringLiteral( "shapeburst_color" ) ) )
1061  {
1062  //pre 2.5 projects used "shapeburst_color"
1063  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color" )].toString() );
1064  }
1065  else if ( props.contains( QStringLiteral( "color" ) ) )
1066  {
1067  color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
1068  }
1069 
1070  if ( props.contains( QStringLiteral( "shapeburst_color2" ) ) )
1071  {
1072  //pre 2.5 projects used "shapeburst_color2"
1073  color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color2" )].toString() );
1074  }
1075  else if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
1076  {
1077  color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
1078  }
1079  if ( props.contains( QStringLiteral( "blur_radius" ) ) )
1080  {
1081  blurRadius = props[QStringLiteral( "blur_radius" )].toInt();
1082  }
1083  if ( props.contains( QStringLiteral( "use_whole_shape" ) ) )
1084  {
1085  useWholeShape = props[QStringLiteral( "use_whole_shape" )].toInt();
1086  }
1087  if ( props.contains( QStringLiteral( "max_distance" ) ) )
1088  {
1089  maxDistance = props[QStringLiteral( "max_distance" )].toDouble();
1090  }
1091  if ( props.contains( QStringLiteral( "offset" ) ) )
1092  {
1093  offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
1094  }
1095 
1096  //attempt to create color ramp from props
1097  QgsColorRamp *gradientRamp = nullptr;
1098  if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
1099  {
1100  gradientRamp = QgsCptCityColorRamp::create( props );
1101  }
1102  else
1103  {
1104  gradientRamp = QgsGradientColorRamp::create( props );
1105  }
1106 
1107  //create a new shapeburst fill layer with desired properties
1108  std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( color, color2, colorType, blurRadius, useWholeShape, maxDistance );
1109  sl->setOffset( offset );
1110  if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1111  {
1112  sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1113  }
1114  if ( props.contains( QStringLiteral( "distance_unit" ) ) )
1115  {
1116  sl->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "distance_unit" )].toString() ) );
1117  }
1118  if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1119  {
1120  sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1121  }
1122  if ( props.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
1123  {
1124  sl->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
1125  }
1126  if ( props.contains( QStringLiteral( "ignore_rings" ) ) )
1127  {
1128  sl->setIgnoreRings( props[QStringLiteral( "ignore_rings" )].toInt() );
1129  }
1130  if ( gradientRamp )
1131  {
1132  sl->setColorRamp( gradientRamp );
1133  }
1134 
1135  sl->restoreOldDataDefinedProperties( props );
1136 
1137  return sl.release();
1138 }
1139 
1141 {
1142  return QStringLiteral( "ShapeburstFill" );
1143 }
1144 
1146 {
1147  if ( mGradientRamp.get() == ramp )
1148  return;
1149 
1150  mGradientRamp.reset( ramp );
1151 }
1152 
1153 void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
1154  double &maxDistance, bool &ignoreRings )
1155 {
1156  //first gradient color
1157  color = mColor;
1159  {
1162  }
1163 
1164  //second gradient color
1165  color2 = mColor2;
1167  {
1170  }
1171 
1172  //blur radius
1173  blurRadius = mBlurRadius;
1175  {
1176  context.setOriginalValueVariable( mBlurRadius );
1178  }
1179 
1180  //use whole shape
1181  useWholeShape = mUseWholeShape;
1183  {
1184  context.setOriginalValueVariable( mUseWholeShape );
1186  }
1187 
1188  //max distance
1189  maxDistance = mMaxDistance;
1191  {
1192  context.setOriginalValueVariable( mMaxDistance );
1194  }
1195 
1196  //ignore rings
1197  ignoreRings = mIgnoreRings;
1199  {
1200  context.setOriginalValueVariable( mIgnoreRings );
1202  }
1203 
1204 }
1205 
1207 {
1208  //TODO - check this
1209  QColor selColor = context.renderContext().selectionColor();
1210  if ( ! SELECTION_IS_OPAQUE )
1211  selColor.setAlphaF( context.opacity() );
1212  mSelBrush = QBrush( selColor );
1213 }
1214 
1216 {
1217  Q_UNUSED( context )
1218 }
1219 
1220 void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1221 {
1222  QPainter *p = context.renderContext().painter();
1223  if ( !p )
1224  {
1225  return;
1226  }
1227 
1228  if ( context.selected() )
1229  {
1230  //feature is selected, draw using selection style
1231  p->setBrush( mSelBrush );
1232  QPointF offset = mOffset;
1233 
1235  {
1237  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1238  bool ok = false;
1239  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1240  if ( ok )
1241  offset = res;
1242  }
1243 
1244  if ( !offset.isNull() )
1245  {
1246  offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1247  offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1248  p->translate( offset );
1249  }
1250  _renderPolygon( p, points, rings, context );
1251  if ( !offset.isNull() )
1252  {
1253  p->translate( -offset );
1254  }
1255  return;
1256  }
1257 
1258  QColor color1, color2;
1259  int blurRadius;
1260  bool useWholeShape;
1261  double maxDistance;
1262  bool ignoreRings;
1263  //calculate data defined symbology
1264  applyDataDefinedSymbology( context, color1, color2, blurRadius, useWholeShape, maxDistance, ignoreRings );
1265 
1266  //calculate max distance for shapeburst fill to extend from polygon boundary, in pixels
1267  int outputPixelMaxDist = 0;
1268  if ( !useWholeShape && !qgsDoubleNear( maxDistance, 0.0 ) )
1269  {
1270  //convert max distance to pixels
1271  outputPixelMaxDist = static_cast< int >( std::round( context.renderContext().convertToPainterUnits( maxDistance, mDistanceUnit, mDistanceMapUnitScale ) ) );
1272  }
1273 
1274  //if we are using the two color mode, create a gradient ramp
1275  std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
1277  {
1278  twoColorGradientRamp = std::make_unique< QgsGradientColorRamp >( color1, color2 );
1279  }
1280 
1281  //no stroke for shapeburst fills
1282  p->setPen( QPen( Qt::NoPen ) );
1283 
1284  //calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
1285  int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
1286  //create a QImage to draw shapeburst in
1287  int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1288  int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1289  int imWidth = pointsWidth + ( sideBuffer * 2 );
1290  int imHeight = pointsHeight + ( sideBuffer * 2 );
1291 
1292  // these are all potentially very expensive operations, so check regularly if the job is canceled and abort responsively
1293  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1294  return;
1295 
1296  std::unique_ptr< QImage > fillImage = std::make_unique< QImage >( imWidth,
1297  imHeight, QImage::Format_ARGB32_Premultiplied );
1298  if ( fillImage->isNull() )
1299  {
1300  QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1301  return;
1302  }
1303 
1304  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1305  return;
1306 
1307  //also create an image to store the alpha channel
1308  std::unique_ptr< QImage > alphaImage = std::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1309  if ( alphaImage->isNull() )
1310  {
1311  QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1312  return;
1313  }
1314 
1315  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1316  return;
1317 
1318  //Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
1319  //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
1320  //polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
1321  fillImage->fill( Qt::black );
1322 
1323  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1324  return;
1325 
1326  //initially fill the alpha channel image with a transparent color
1327  alphaImage->fill( Qt::transparent );
1328 
1329  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1330  return;
1331 
1332  //now, draw the polygon in the alpha channel image
1333  QPainter imgPainter;
1334  imgPainter.begin( alphaImage.get() );
1335  imgPainter.setRenderHint( QPainter::Antialiasing, true );
1336  imgPainter.setBrush( QBrush( Qt::white ) );
1337  imgPainter.setPen( QPen( Qt::black ) );
1338  imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1339  _renderPolygon( &imgPainter, points, rings, context );
1340  imgPainter.end();
1341 
1342  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1343  return;
1344 
1345  //now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
1346  //(this avoids calling _renderPolygon twice, since that can be slow)
1347  imgPainter.begin( fillImage.get() );
1348  if ( !ignoreRings )
1349  {
1350  imgPainter.drawImage( 0, 0, *alphaImage );
1351  }
1352  else
1353  {
1354  //using ignore rings mode, so the alpha image can't be used
1355  //directly as the alpha channel contains polygon rings and we need
1356  //to draw now without any rings
1357  imgPainter.setBrush( QBrush( Qt::white ) );
1358  imgPainter.setPen( QPen( Qt::black ) );
1359  imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1360  _renderPolygon( &imgPainter, points, nullptr, context );
1361  }
1362  imgPainter.end();
1363 
1364  if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1365  return;
1366 
1367  //apply distance transform to image, uses the current color ramp to calculate final pixel colors
1368  double *dtArray = distanceTransform( fillImage.get(), context.renderContext() );
1369 
1370  //copy distance transform values back to QImage, shading by appropriate color ramp
1371  dtArrayToQImage( dtArray, fillImage.get(), mColorType == QgsShapeburstFillSymbolLayer::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
1372  context.renderContext(), useWholeShape, outputPixelMaxDist );
1373  if ( context.opacity() < 1 )
1374  {
1375  QgsImageOperation::multiplyOpacity( *fillImage, context.opacity(), context.renderContext().feedback() );
1376  }
1377 
1378  //clean up some variables
1379  delete [] dtArray;
1380 
1381  //apply blur if desired
1382  if ( blurRadius > 0 )
1383  {
1384  QgsImageOperation::stackBlur( *fillImage, blurRadius, false, context.renderContext().feedback() );
1385  }
1386 
1387  //apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1388  imgPainter.begin( fillImage.get() );
1389  imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
1390  imgPainter.drawImage( 0, 0, *alphaImage );
1391  imgPainter.end();
1392  //we're finished with the alpha channel image now
1393  alphaImage.reset();
1394 
1395  //draw shapeburst image in correct place in the destination painter
1396 
1397  QgsScopedQPainterState painterState( p );
1398  QPointF offset = mOffset;
1400  {
1402  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1403  bool ok = false;
1404  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1405  if ( ok )
1406  offset = res;
1407  }
1408  if ( !offset.isNull() )
1409  {
1410  offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1411  offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1412  p->translate( offset );
1413  }
1414 
1415  p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
1416 
1417  if ( !offset.isNull() )
1418  {
1419  p->translate( -offset );
1420  }
1421 }
1422 
1423 //fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
1424 
1425 /* distance transform of a 1d function using squared distance */
1426 void QgsShapeburstFillSymbolLayer::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
1427 {
1428  int k = 0;
1429  v[0] = 0;
1430  z[0] = -INF;
1431  z[1] = + INF;
1432  for ( int q = 1; q <= n - 1; q++ )
1433  {
1434  double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1435  while ( s <= z[k] )
1436  {
1437  k--;
1438  s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1439  }
1440  k++;
1441  v[k] = q;
1442  z[k] = s;
1443  z[k + 1] = + INF;
1444  }
1445 
1446  k = 0;
1447  for ( int q = 0; q <= n - 1; q++ )
1448  {
1449  while ( z[k + 1] < q )
1450  k++;
1451  d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
1452  }
1453 }
1454 
1455 /* distance transform of 2d function using squared distance */
1456 void QgsShapeburstFillSymbolLayer::distanceTransform2d( double *im, int width, int height, QgsRenderContext &context )
1457 {
1458  int maxDimension = std::max( width, height );
1459  double *f = new double[ maxDimension ];
1460  int *v = new int[ maxDimension ];
1461  double *z = new double[ maxDimension + 1 ];
1462  double *d = new double[ maxDimension ];
1463 
1464  // transform along columns
1465  for ( int x = 0; x < width; x++ )
1466  {
1467  if ( context.renderingStopped() )
1468  break;
1469 
1470  for ( int y = 0; y < height; y++ )
1471  {
1472  f[y] = im[ x + y * width ];
1473  }
1474  distanceTransform1d( f, height, v, z, d );
1475  for ( int y = 0; y < height; y++ )
1476  {
1477  im[ x + y * width ] = d[y];
1478  }
1479  }
1480 
1481  // transform along rows
1482  for ( int y = 0; y < height; y++ )
1483  {
1484  if ( context.renderingStopped() )
1485  break;
1486 
1487  for ( int x = 0; x < width; x++ )
1488  {
1489  f[x] = im[ x + y * width ];
1490  }
1491  distanceTransform1d( f, width, v, z, d );
1492  for ( int x = 0; x < width; x++ )
1493  {
1494  im[ x + y * width ] = d[x];
1495  }
1496  }
1497 
1498  delete [] d;
1499  delete [] f;
1500  delete [] v;
1501  delete [] z;
1502 }
1503 
1504 /* distance transform of a binary QImage */
1505 double *QgsShapeburstFillSymbolLayer::distanceTransform( QImage *im, QgsRenderContext &context )
1506 {
1507  int width = im->width();
1508  int height = im->height();
1509 
1510  double *dtArray = new double[width * height];
1511 
1512  //load qImage to array
1513  QRgb tmpRgb;
1514  int idx = 0;
1515  for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1516  {
1517  if ( context.renderingStopped() )
1518  break;
1519 
1520  const QRgb *scanLine = reinterpret_cast< const QRgb * >( im->constScanLine( heightIndex ) );
1521  for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1522  {
1523  tmpRgb = scanLine[widthIndex];
1524  if ( qRed( tmpRgb ) == 0 )
1525  {
1526  //black pixel, so zero distance
1527  dtArray[ idx ] = 0;
1528  }
1529  else
1530  {
1531  //white pixel, so initially set distance as infinite
1532  dtArray[ idx ] = INF;
1533  }
1534  idx++;
1535  }
1536  }
1537 
1538  //calculate squared distance transform
1539  distanceTransform2d( dtArray, width, height, context );
1540 
1541  return dtArray;
1542 }
1543 
1544 void QgsShapeburstFillSymbolLayer::dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, QgsRenderContext &context, bool useWholeShape, int maxPixelDistance )
1545 {
1546  int width = im->width();
1547  int height = im->height();
1548 
1549  //find maximum distance value
1550  double maxDistanceValue;
1551 
1552  if ( useWholeShape )
1553  {
1554  //no max distance specified in symbol properties, so calculate from maximum value in distance transform results
1555  double dtMaxValue = array[0];
1556  for ( int i = 1; i < ( width * height ); ++i )
1557  {
1558  if ( array[i] > dtMaxValue )
1559  {
1560  dtMaxValue = array[i];
1561  }
1562  }
1563 
1564  //values in distance transform are squared
1565  maxDistanceValue = std::sqrt( dtMaxValue );
1566  }
1567  else
1568  {
1569  //use max distance set in symbol properties
1570  maxDistanceValue = maxPixelDistance;
1571  }
1572 
1573  //update the pixels in the provided QImage
1574  int idx = 0;
1575  double squaredVal = 0;
1576  double pixVal = 0;
1577 
1578  for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1579  {
1580  if ( context.renderingStopped() )
1581  break;
1582 
1583  QRgb *scanLine = reinterpret_cast< QRgb * >( im->scanLine( heightIndex ) );
1584  for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1585  {
1586  //result of distance transform
1587  squaredVal = array[idx];
1588 
1589  //scale result to fit in the range [0, 1]
1590  if ( maxDistanceValue > 0 )
1591  {
1592  pixVal = squaredVal > 0 ? std::min( ( std::sqrt( squaredVal ) / maxDistanceValue ), 1.0 ) : 0;
1593  }
1594  else
1595  {
1596  pixVal = 1.0;
1597  }
1598 
1599  //convert value to color from ramp
1600  //premultiply ramp color since we are storing this in a ARGB32_Premultiplied QImage
1601  scanLine[widthIndex] = qPremultiply( ramp->color( pixVal ).rgba() );
1602  idx++;
1603  }
1604  }
1605 }
1606 
1608 {
1609  QVariantMap map;
1610  map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
1611  map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
1612  map[QStringLiteral( "color_type" )] = QString::number( mColorType );
1613  map[QStringLiteral( "blur_radius" )] = QString::number( mBlurRadius );
1614  map[QStringLiteral( "use_whole_shape" )] = QString::number( mUseWholeShape );
1615  map[QStringLiteral( "max_distance" )] = QString::number( mMaxDistance );
1616  map[QStringLiteral( "distance_unit" )] = QgsUnitTypes::encodeUnit( mDistanceUnit );
1617  map[QStringLiteral( "distance_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale );
1618  map[QStringLiteral( "ignore_rings" )] = QString::number( mIgnoreRings );
1619  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1620  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1621  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1622  if ( mGradientRamp )
1623  {
1624 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1625  map.unite( mGradientRamp->properties() );
1626 #else
1627  map.insert( mGradientRamp->properties() );
1628 #endif
1629  }
1630 
1631  return map;
1632 }
1633 
1635 {
1636  std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( mColor, mColor2, mColorType, mBlurRadius, mUseWholeShape, mMaxDistance );
1637  if ( mGradientRamp )
1638  {
1639  sl->setColorRamp( mGradientRamp->clone() );
1640  }
1641  sl->setDistanceUnit( mDistanceUnit );
1642  sl->setDistanceMapUnitScale( mDistanceMapUnitScale );
1643  sl->setIgnoreRings( mIgnoreRings );
1644  sl->setOffset( mOffset );
1645  sl->setOffsetUnit( mOffsetUnit );
1646  sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1647  copyDataDefinedProperties( sl.get() );
1648  copyPaintEffect( sl.get() );
1649  return sl.release();
1650 }
1651 
1653 {
1654  double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1655  return offsetBleed;
1656 }
1657 
1659 {
1660  return true;
1661 }
1662 
1664 {
1665  mDistanceUnit = unit;
1666  mOffsetUnit = unit;
1667 }
1668 
1670 {
1671  if ( mDistanceUnit == mOffsetUnit )
1672  {
1673  return mDistanceUnit;
1674  }
1676 }
1677 
1679 {
1680  return mDistanceUnit == QgsUnitTypes::RenderMapUnits || mDistanceUnit == QgsUnitTypes::RenderMetersInMapUnits
1681  || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
1682 }
1683 
1685 {
1686  mDistanceMapUnitScale = scale;
1687  mOffsetMapUnitScale = scale;
1688 }
1689 
1691 {
1692  if ( mDistanceMapUnitScale == mOffsetMapUnitScale )
1693  {
1694  return mDistanceMapUnitScale;
1695  }
1696  return QgsMapUnitScale();
1697 }
1698 
1699 
1700 //QgsImageFillSymbolLayer
1701 
1703 {
1704  setSubSymbol( new QgsLineSymbol() );
1705 }
1706 
1708 
1709 void QgsImageFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1710 {
1711  QPainter *p = context.renderContext().painter();
1712  if ( !p )
1713  {
1714  return;
1715  }
1716 
1717  mNextAngle = mAngle;
1718  applyDataDefinedSettings( context );
1719 
1720  p->setPen( QPen( Qt::NoPen ) );
1721 
1722  QTransform bkTransform = mBrush.transform();
1723  if ( applyBrushTransformFromContext() && !context.renderContext().textureOrigin().isNull() )
1724  {
1725  QPointF leftCorner = context.renderContext().textureOrigin();
1726  QTransform t = mBrush.transform();
1727  t.translate( leftCorner.x(), leftCorner.y() );
1728  mBrush.setTransform( t );
1729  }
1730 
1731  if ( context.selected() )
1732  {
1733  QColor selColor = context.renderContext().selectionColor();
1734  p->setBrush( QBrush( selColor ) );
1735  _renderPolygon( p, points, rings, context );
1736  }
1737 
1738  if ( !qgsDoubleNear( mNextAngle, 0.0 ) )
1739  {
1740  QTransform t = mBrush.transform();
1741  t.rotate( mNextAngle );
1742  mBrush.setTransform( t );
1743  }
1744  p->setBrush( mBrush );
1745  _renderPolygon( p, points, rings, context );
1746  if ( mStroke )
1747  {
1748  mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
1749  if ( rings )
1750  {
1751  for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
1752  {
1753  mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
1754  }
1755  }
1756  }
1757 
1758  mBrush.setTransform( bkTransform );
1759 }
1760 
1762 {
1763  return mStroke.get();
1764 }
1765 
1767 {
1768  if ( !symbol ) //unset current stroke
1769  {
1770  mStroke.reset( nullptr );
1771  return true;
1772  }
1773 
1774  if ( symbol->type() != Qgis::SymbolType::Line )
1775  {
1776  delete symbol;
1777  return false;
1778  }
1779 
1780  QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
1781  if ( lineSymbol )
1782  {
1783  mStroke.reset( lineSymbol );
1784  return true;
1785  }
1786 
1787  delete symbol;
1788  return false;
1789 }
1790 
1792 {
1793  mStrokeWidthUnit = unit;
1794 }
1795 
1797 {
1798  return mStrokeWidthUnit;
1799 }
1800 
1802 {
1803  mStrokeWidthMapUnitScale = scale;
1804 }
1805 
1807 {
1808  return mStrokeWidthMapUnitScale;
1809 }
1810 
1812 {
1813  if ( mStroke && mStroke->symbolLayer( 0 ) )
1814  {
1815  double subLayerBleed = mStroke->symbolLayer( 0 )->estimateMaxBleed( context );
1816  return subLayerBleed;
1817  }
1818  return 0;
1819 }
1820 
1822 {
1823  double width = mStrokeWidth;
1825  {
1828  }
1830 }
1831 
1833 {
1834  Q_UNUSED( context )
1835  if ( !mStroke )
1836  {
1837  return QColor( Qt::black );
1838  }
1839  return mStroke->color();
1840 }
1841 
1843 {
1844  return Qt::SolidLine;
1845 #if 0
1846  if ( !mStroke )
1847  {
1848  return Qt::SolidLine;
1849  }
1850  else
1851  {
1852  return mStroke->dxfPenStyle();
1853  }
1854 #endif //0
1855 }
1856 
1857 QSet<QString> QgsImageFillSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
1858 {
1859  QSet<QString> attr = QgsFillSymbolLayer::usedAttributes( context );
1860  if ( mStroke )
1861  attr.unite( mStroke->usedAttributes( context ) );
1862  return attr;
1863 }
1864 
1866 {
1868  return true;
1869  if ( mStroke && mStroke->hasDataDefinedProperties() )
1870  return true;
1871  return false;
1872 }
1873 
1875 {
1876  return true;
1877 }
1878 
1879 
1880 //QgsSVGFillSymbolLayer
1881 
1882 QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString &svgFilePath, double width, double angle )
1884  , mPatternWidth( width )
1885 {
1886  mStrokeWidth = 0.3;
1887  mAngle = angle;
1888  mColor = QColor( 255, 255, 255 );
1890 }
1891 
1892 QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray &svgData, double width, double angle )
1894  , mPatternWidth( width )
1895  , mSvgData( svgData )
1896 {
1897  storeViewBox();
1898  mStrokeWidth = 0.3;
1899  mAngle = angle;
1900  mColor = QColor( 255, 255, 255 );
1901  setSubSymbol( new QgsLineSymbol() );
1902  setDefaultSvgParams();
1903 }
1904 
1906 
1908 {
1910  mPatternWidthUnit = unit;
1911  mSvgStrokeWidthUnit = unit;
1912  mStrokeWidthUnit = unit;
1913  mStroke->setOutputUnit( unit );
1914 }
1915 
1917 {
1919  if ( mPatternWidthUnit != unit || mSvgStrokeWidthUnit != unit || mStrokeWidthUnit != unit )
1920  {
1922  }
1923  return unit;
1924 }
1925 
1927 {
1929  mPatternWidthMapUnitScale = scale;
1930  mSvgStrokeWidthMapUnitScale = scale;
1931  mStrokeWidthMapUnitScale = scale;
1932 }
1933 
1935 {
1936  if ( QgsImageFillSymbolLayer::mapUnitScale() == mPatternWidthMapUnitScale &&
1937  mPatternWidthMapUnitScale == mSvgStrokeWidthMapUnitScale &&
1938  mSvgStrokeWidthMapUnitScale == mStrokeWidthMapUnitScale )
1939  {
1940  return mPatternWidthMapUnitScale;
1941  }
1942  return QgsMapUnitScale();
1943 }
1944 
1945 void QgsSVGFillSymbolLayer::setSvgFilePath( const QString &svgPath )
1946 {
1947  mSvgData = QgsApplication::svgCache()->getImageData( svgPath );
1948  storeViewBox();
1949 
1950  mSvgFilePath = svgPath;
1951  setDefaultSvgParams();
1952 }
1953 
1954 QgsSymbolLayer *QgsSVGFillSymbolLayer::create( const QVariantMap &properties )
1955 {
1956  QByteArray data;
1957  double width = 20;
1958  QString svgFilePath;
1959  double angle = 0.0;
1960 
1961  if ( properties.contains( QStringLiteral( "width" ) ) )
1962  {
1963  width = properties[QStringLiteral( "width" )].toDouble();
1964  }
1965  if ( properties.contains( QStringLiteral( "svgFile" ) ) )
1966  {
1967  svgFilePath = properties[QStringLiteral( "svgFile" )].toString();
1968  }
1969  if ( properties.contains( QStringLiteral( "angle" ) ) )
1970  {
1971  angle = properties[QStringLiteral( "angle" )].toDouble();
1972  }
1973 
1974  std::unique_ptr< QgsSVGFillSymbolLayer > symbolLayer;
1975  if ( !svgFilePath.isEmpty() )
1976  {
1977  symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( svgFilePath, width, angle );
1978  }
1979  else
1980  {
1981  if ( properties.contains( QStringLiteral( "data" ) ) )
1982  {
1983  data = QByteArray::fromHex( properties[QStringLiteral( "data" )].toString().toLocal8Bit() );
1984  }
1985  symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( data, width, angle );
1986  }
1987 
1988  //svg parameters
1989  if ( properties.contains( QStringLiteral( "svgFillColor" ) ) )
1990  {
1991  //pre 2.5 projects used "svgFillColor"
1992  symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgFillColor" )].toString() ) );
1993  }
1994  else if ( properties.contains( QStringLiteral( "color" ) ) )
1995  {
1996  symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
1997  }
1998  if ( properties.contains( QStringLiteral( "svgOutlineColor" ) ) )
1999  {
2000  //pre 2.5 projects used "svgOutlineColor"
2001  symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgOutlineColor" )].toString() ) );
2002  }
2003  else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2004  {
2005  symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() ) );
2006  }
2007  else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2008  {
2009  symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() ) );
2010  }
2011  if ( properties.contains( QStringLiteral( "svgOutlineWidth" ) ) )
2012  {
2013  //pre 2.5 projects used "svgOutlineWidth"
2014  symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "svgOutlineWidth" )].toDouble() );
2015  }
2016  else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2017  {
2018  symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "outline_width" )].toDouble() );
2019  }
2020  else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2021  {
2022  symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "line_width" )].toDouble() );
2023  }
2024 
2025  //units
2026  if ( properties.contains( QStringLiteral( "pattern_width_unit" ) ) )
2027  {
2028  symbolLayer->setPatternWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "pattern_width_unit" )].toString() ) );
2029  }
2030  if ( properties.contains( QStringLiteral( "pattern_width_map_unit_scale" ) ) )
2031  {
2032  symbolLayer->setPatternWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "pattern_width_map_unit_scale" )].toString() ) );
2033  }
2034  if ( properties.contains( QStringLiteral( "svg_outline_width_unit" ) ) )
2035  {
2036  symbolLayer->setSvgStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "svg_outline_width_unit" )].toString() ) );
2037  }
2038  if ( properties.contains( QStringLiteral( "svg_outline_width_map_unit_scale" ) ) )
2039  {
2040  symbolLayer->setSvgStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "svg_outline_width_map_unit_scale" )].toString() ) );
2041  }
2042  if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2043  {
2044  symbolLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2045  }
2046  if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2047  {
2048  symbolLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2049  }
2050 
2051  if ( properties.contains( QStringLiteral( "parameters" ) ) )
2052  {
2053  const QVariantMap parameters = properties[QStringLiteral( "parameters" )].toMap();
2054  symbolLayer->setParameters( QgsProperty::variantMapToPropertyMap( parameters ) );
2055  }
2056 
2057  symbolLayer->restoreOldDataDefinedProperties( properties );
2058 
2059  return symbolLayer.release();
2060 }
2061 
2062 void QgsSVGFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2063 {
2064  QVariantMap::iterator it = properties.find( QStringLiteral( "svgFile" ) );
2065  if ( it != properties.end() )
2066  {
2067  if ( saving )
2068  it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2069  else
2070  it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2071  }
2072 }
2073 
2075 {
2076  return QStringLiteral( "SVGFill" );
2077 }
2078 
2079 void QgsSVGFillSymbolLayer::applyPattern( QBrush &brush, const QString &svgFilePath, double patternWidth, QgsUnitTypes::RenderUnit patternWidthUnit,
2080  const QColor &svgFillColor, const QColor &svgStrokeColor, double svgStrokeWidth,
2081  QgsUnitTypes::RenderUnit svgStrokeWidthUnit, const QgsSymbolRenderContext &context,
2082  const QgsMapUnitScale &patternWidthMapUnitScale, const QgsMapUnitScale &svgStrokeWidthMapUnitScale, const QgsStringMap svgParameters )
2083 {
2084  if ( mSvgViewBox.isNull() )
2085  {
2086  return;
2087  }
2088 
2090 
2091  if ( static_cast< int >( size ) < 1.0 || 10000.0 < size )
2092  {
2093  brush.setTextureImage( QImage() );
2094  }
2095  else
2096  {
2097  bool fitsInCache = true;
2099  QImage patternImage = QgsApplication::svgCache()->svgAsImage( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2100  context.renderContext().scaleFactor(), fitsInCache, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), svgParameters );
2101  if ( !fitsInCache )
2102  {
2103  QPicture patternPict = QgsApplication::svgCache()->svgAsPicture( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2104  context.renderContext().scaleFactor(), false, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
2105  double hwRatio = 1.0;
2106  if ( patternPict.width() > 0 )
2107  {
2108  hwRatio = static_cast< double >( patternPict.height() ) / static_cast< double >( patternPict.width() );
2109  }
2110  patternImage = QImage( static_cast< int >( size ), static_cast< int >( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
2111  patternImage.fill( 0 ); // transparent background
2112 
2113  QPainter p( &patternImage );
2114  p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
2115  }
2116 
2117  QTransform brushTransform;
2118  if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2119  {
2120  QImage transparentImage = patternImage.copy();
2121  QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2122  brush.setTextureImage( transparentImage );
2123  }
2124  else
2125  {
2126  brush.setTextureImage( patternImage );
2127  }
2128  brush.setTransform( brushTransform );
2129  }
2130 }
2131 
2133 {
2134  QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2135 
2136  applyPattern( mBrush, mSvgFilePath, mPatternWidth, mPatternWidthUnit, mColor, mSvgStrokeColor, mSvgStrokeWidth, mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2137 
2138  if ( mStroke )
2139  {
2140  mStroke->startRender( context.renderContext(), context.fields() );
2141  }
2142 }
2143 
2145 {
2146  if ( mStroke )
2147  {
2148  mStroke->stopRender( context.renderContext() );
2149  }
2150 }
2151 
2153 {
2154  QVariantMap map;
2155  if ( !mSvgFilePath.isEmpty() )
2156  {
2157  map.insert( QStringLiteral( "svgFile" ), mSvgFilePath );
2158  }
2159  else
2160  {
2161  map.insert( QStringLiteral( "data" ), QString( mSvgData.toHex() ) );
2162  }
2163 
2164  map.insert( QStringLiteral( "width" ), QString::number( mPatternWidth ) );
2165  map.insert( QStringLiteral( "angle" ), QString::number( mAngle ) );
2166 
2167  //svg parameters
2168  map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
2169  map.insert( QStringLiteral( "outline_color" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2170  map.insert( QStringLiteral( "outline_width" ), QString::number( mSvgStrokeWidth ) );
2171 
2172  //units
2173  map.insert( QStringLiteral( "pattern_width_unit" ), QgsUnitTypes::encodeUnit( mPatternWidthUnit ) );
2174  map.insert( QStringLiteral( "pattern_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mPatternWidthMapUnitScale ) );
2175  map.insert( QStringLiteral( "svg_outline_width_unit" ), QgsUnitTypes::encodeUnit( mSvgStrokeWidthUnit ) );
2176  map.insert( QStringLiteral( "svg_outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mSvgStrokeWidthMapUnitScale ) );
2177  map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
2178  map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
2179 
2180  map[QStringLiteral( "parameters" )] = QgsProperty::propertyMapToVariantMap( mParameters );
2181 
2182  return map;
2183 }
2184 
2186 {
2187  std::unique_ptr< QgsSVGFillSymbolLayer > clonedLayer;
2188  if ( !mSvgFilePath.isEmpty() )
2189  {
2190  clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgFilePath, mPatternWidth, mAngle );
2191  clonedLayer->setSvgFillColor( mColor );
2192  clonedLayer->setSvgStrokeColor( mSvgStrokeColor );
2193  clonedLayer->setSvgStrokeWidth( mSvgStrokeWidth );
2194  }
2195  else
2196  {
2197  clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgData, mPatternWidth, mAngle );
2198  }
2199 
2200  clonedLayer->setPatternWidthUnit( mPatternWidthUnit );
2201  clonedLayer->setPatternWidthMapUnitScale( mPatternWidthMapUnitScale );
2202  clonedLayer->setSvgStrokeWidthUnit( mSvgStrokeWidthUnit );
2203  clonedLayer->setSvgStrokeWidthMapUnitScale( mSvgStrokeWidthMapUnitScale );
2204  clonedLayer->setStrokeWidthUnit( mStrokeWidthUnit );
2205  clonedLayer->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
2206 
2207  clonedLayer->setParameters( mParameters );
2208 
2209  if ( mStroke )
2210  {
2211  clonedLayer->setSubSymbol( mStroke->clone() );
2212  }
2213  copyDataDefinedProperties( clonedLayer.get() );
2214  copyPaintEffect( clonedLayer.get() );
2215  return clonedLayer.release();
2216 }
2217 
2218 void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2219 {
2220  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
2221  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2222  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2223  element.appendChild( symbolizerElem );
2224 
2225  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2226 
2227  QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
2228  symbolizerElem.appendChild( fillElem );
2229 
2230  QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
2231  fillElem.appendChild( graphicFillElem );
2232 
2233  QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2234  graphicFillElem.appendChild( graphicElem );
2235 
2236  if ( !mSvgFilePath.isEmpty() )
2237  {
2238  // encode a parametric SVG reference
2239  double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
2240  double strokeWidth = QgsSymbolLayerUtils::rescaleUom( mSvgStrokeWidth, mSvgStrokeWidthUnit, props );
2241  QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgStrokeColor, strokeWidth );
2242  }
2243  else
2244  {
2245  // TODO: create svg from data
2246  // <se:InlineContent>
2247  symbolizerElem.appendChild( doc.createComment( QStringLiteral( "SVG from data not implemented yet" ) ) );
2248  }
2249 
2250  // <Rotation>
2251  QString angleFunc;
2252  bool ok;
2253  double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
2254  if ( !ok )
2255  {
2256  angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
2257  }
2258  else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2259  {
2260  angleFunc = QString::number( angle + mAngle );
2261  }
2262  QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
2263 
2264  if ( mStroke )
2265  {
2266  // the stroke sub symbol should be stored within the Stroke element,
2267  // but it will be stored in a separated LineSymbolizer because it could
2268  // have more than one layer
2269  mStroke->toSld( doc, element, props );
2270  }
2271 }
2272 
2274 {
2275  return mPatternWidthUnit == QgsUnitTypes::RenderMapUnits || mPatternWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
2276  || mSvgStrokeWidthUnit == QgsUnitTypes::RenderMapUnits || mSvgStrokeWidthUnit == QgsUnitTypes::RenderMetersInMapUnits;
2277 }
2278 
2280 {
2281  QString path, mimeType;
2282  QColor fillColor, strokeColor;
2283  Qt::PenStyle penStyle;
2284  double size, strokeWidth;
2285 
2286  QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
2287  if ( fillElem.isNull() )
2288  return nullptr;
2289 
2290  QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
2291  if ( graphicFillElem.isNull() )
2292  return nullptr;
2293 
2294  QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
2295  if ( graphicElem.isNull() )
2296  return nullptr;
2297 
2298  if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
2299  return nullptr;
2300 
2301  if ( mimeType != QLatin1String( "image/svg+xml" ) )
2302  return nullptr;
2303 
2304  QgsSymbolLayerUtils::lineFromSld( graphicElem, penStyle, strokeColor, strokeWidth );
2305 
2306  QString uom = element.attribute( QStringLiteral( "uom" ) );
2307  size = QgsSymbolLayerUtils::sizeInPixelsFromSldUom( uom, size );
2308  strokeWidth = QgsSymbolLayerUtils::sizeInPixelsFromSldUom( uom, strokeWidth );
2309 
2310  double angle = 0.0;
2311  QString angleFunc;
2312  if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2313  {
2314  bool ok;
2315  double d = angleFunc.toDouble( &ok );
2316  if ( ok )
2317  angle = d;
2318  }
2319 
2320  std::unique_ptr< QgsSVGFillSymbolLayer > sl = std::make_unique< QgsSVGFillSymbolLayer >( path, size, angle );
2321  sl->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
2322  sl->setSvgFillColor( fillColor );
2323  sl->setSvgStrokeColor( strokeColor );
2324  sl->setSvgStrokeWidth( strokeWidth );
2325 
2326  // try to get the stroke
2327  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2328  if ( !strokeElem.isNull() )
2329  {
2331  if ( l )
2332  {
2333  QgsSymbolLayerList layers;
2334  layers.append( l );
2335  sl->setSubSymbol( new QgsLineSymbol( layers ) );
2336  }
2337  }
2338 
2339  return sl.release();
2340 }
2341 
2343 {
2347  {
2348  return; //no data defined settings
2349  }
2350 
2352  {
2353  context.setOriginalValueVariable( mAngle );
2355  }
2356 
2357  double width = mPatternWidth;
2359  {
2360  context.setOriginalValueVariable( mPatternWidth );
2362  }
2363  QString svgFile = mSvgFilePath;
2365  {
2366  context.setOriginalValueVariable( mSvgFilePath );
2368  context.renderContext().pathResolver() );
2369  }
2370  QColor svgFillColor = mColor;
2372  {
2375  }
2376  QColor svgStrokeColor = mSvgStrokeColor;
2378  {
2379  context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2381  }
2382  double strokeWidth = mSvgStrokeWidth;
2384  {
2385  context.setOriginalValueVariable( mSvgStrokeWidth );
2387  }
2388  QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2389 
2390  applyPattern( mBrush, svgFile, width, mPatternWidthUnit, svgFillColor, svgStrokeColor, strokeWidth,
2391  mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2392 
2393 }
2394 
2395 void QgsSVGFillSymbolLayer::storeViewBox()
2396 {
2397  if ( !mSvgData.isEmpty() )
2398  {
2399  QSvgRenderer r( mSvgData );
2400  if ( r.isValid() )
2401  {
2402  mSvgViewBox = r.viewBoxF();
2403  return;
2404  }
2405  }
2406 
2407  mSvgViewBox = QRectF();
2408 }
2409 
2410 void QgsSVGFillSymbolLayer::setDefaultSvgParams()
2411 {
2412  if ( mSvgFilePath.isEmpty() )
2413  {
2414  return;
2415  }
2416 
2417  bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
2418  bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
2419  QColor defaultFillColor, defaultStrokeColor;
2420  double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
2421  QgsApplication::svgCache()->containsParams( mSvgFilePath, hasFillParam, hasDefaultFillColor, defaultFillColor,
2422  hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
2423  hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2424  hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
2425  hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
2426 
2427  double newFillOpacity = hasFillOpacityParam ? mColor.alphaF() : 1.0;
2428  double newStrokeOpacity = hasStrokeOpacityParam ? mSvgStrokeColor.alphaF() : 1.0;
2429 
2430  if ( hasDefaultFillColor )
2431  {
2432  mColor = defaultFillColor;
2433  mColor.setAlphaF( newFillOpacity );
2434  }
2435  if ( hasDefaultFillOpacity )
2436  {
2437  mColor.setAlphaF( defaultFillOpacity );
2438  }
2439  if ( hasDefaultStrokeColor )
2440  {
2441  mSvgStrokeColor = defaultStrokeColor;
2442  mSvgStrokeColor.setAlphaF( newStrokeOpacity );
2443  }
2444  if ( hasDefaultStrokeOpacity )
2445  {
2446  mSvgStrokeColor.setAlphaF( defaultStrokeOpacity );
2447  }
2448  if ( hasDefaultStrokeWidth )
2449  {
2450  mSvgStrokeWidth = defaultStrokeWidth;
2451  }
2452 }
2453 
2454 void QgsSVGFillSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2455 {
2456  mParameters = parameters;
2457 }
2458 
2459 
2462 {
2463  setSubSymbol( new QgsLineSymbol() );
2464  QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
2465 }
2466 
2468 {
2469  delete mFillLineSymbol;
2470 }
2471 
2473 {
2474  mFillLineSymbol->setWidth( w );
2475  mLineWidth = w;
2476 }
2477 
2479 {
2480  mFillLineSymbol->setColor( c );
2481  mColor = c;
2482 }
2483 
2485 {
2486  return mFillLineSymbol ? mFillLineSymbol->color() : mColor;
2487 }
2488 
2490 {
2491  if ( !symbol )
2492  {
2493  return false;
2494  }
2495 
2496  if ( symbol->type() == Qgis::SymbolType::Line )
2497  {
2498  QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
2499  if ( lineSymbol )
2500  {
2501  delete mFillLineSymbol;
2502  mFillLineSymbol = lineSymbol;
2503 
2504  return true;
2505  }
2506  }
2507  delete symbol;
2508  return false;
2509 }
2510 
2512 {
2513  return mFillLineSymbol;
2514 }
2515 
2517 {
2518  QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2519  if ( mFillLineSymbol )
2520  attr.unite( mFillLineSymbol->usedAttributes( context ) );
2521  return attr;
2522 }
2523 
2525 {
2527  return true;
2528  if ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
2529  return true;
2530  return false;
2531 }
2532 
2534 {
2535  return 0;
2536 }
2537 
2539 {
2541  mDistanceUnit = unit;
2542  mLineWidthUnit = unit;
2543  mOffsetUnit = unit;
2544 }
2545 
2547 {
2549  if ( mDistanceUnit != unit || mLineWidthUnit != unit || mOffsetUnit != unit )
2550  {
2552  }
2553  return unit;
2554 }
2555 
2557 {
2558  return mDistanceUnit == QgsUnitTypes::RenderMapUnits || mDistanceUnit == QgsUnitTypes::RenderMetersInMapUnits
2559  || mLineWidthUnit == QgsUnitTypes::RenderMapUnits || mLineWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
2560  || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
2561 }
2562 
2564 {
2566  mDistanceMapUnitScale = scale;
2567  mLineWidthMapUnitScale = scale;
2568  mOffsetMapUnitScale = scale;
2569 }
2570 
2572 {
2573  if ( QgsImageFillSymbolLayer::mapUnitScale() == mDistanceMapUnitScale &&
2574  mDistanceMapUnitScale == mLineWidthMapUnitScale &&
2575  mLineWidthMapUnitScale == mOffsetMapUnitScale )
2576  {
2577  return mDistanceMapUnitScale;
2578  }
2579  return QgsMapUnitScale();
2580 }
2581 
2583 {
2584  std::unique_ptr< QgsLinePatternFillSymbolLayer > patternLayer = std::make_unique< QgsLinePatternFillSymbolLayer >();
2585 
2586  //default values
2587  double lineAngle = 45;
2588  double distance = 5;
2589  double lineWidth = 0.5;
2590  QColor color( Qt::black );
2591  double offset = 0.0;
2592 
2593  if ( properties.contains( QStringLiteral( "lineangle" ) ) )
2594  {
2595  //pre 2.5 projects used "lineangle"
2596  lineAngle = properties[QStringLiteral( "lineangle" )].toDouble();
2597  }
2598  else if ( properties.contains( QStringLiteral( "angle" ) ) )
2599  {
2600  lineAngle = properties[QStringLiteral( "angle" )].toDouble();
2601  }
2602  patternLayer->setLineAngle( lineAngle );
2603 
2604  if ( properties.contains( QStringLiteral( "distance" ) ) )
2605  {
2606  distance = properties[QStringLiteral( "distance" )].toDouble();
2607  }
2608  patternLayer->setDistance( distance );
2609 
2610  if ( properties.contains( QStringLiteral( "linewidth" ) ) )
2611  {
2612  //pre 2.5 projects used "linewidth"
2613  lineWidth = properties[QStringLiteral( "linewidth" )].toDouble();
2614  }
2615  else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2616  {
2617  lineWidth = properties[QStringLiteral( "outline_width" )].toDouble();
2618  }
2619  else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2620  {
2621  lineWidth = properties[QStringLiteral( "line_width" )].toDouble();
2622  }
2623  patternLayer->setLineWidth( lineWidth );
2624 
2625  if ( properties.contains( QStringLiteral( "color" ) ) )
2626  {
2627  color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() );
2628  }
2629  else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2630  {
2631  color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() );
2632  }
2633  else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2634  {
2635  color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() );
2636  }
2637  patternLayer->setColor( color );
2638 
2639  if ( properties.contains( QStringLiteral( "offset" ) ) )
2640  {
2641  offset = properties[QStringLiteral( "offset" )].toDouble();
2642  }
2643  patternLayer->setOffset( offset );
2644 
2645 
2646  if ( properties.contains( QStringLiteral( "distance_unit" ) ) )
2647  {
2648  patternLayer->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_unit" )].toString() ) );
2649  }
2650  if ( properties.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
2651  {
2652  patternLayer->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
2653  }
2654  if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
2655  {
2656  patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
2657  }
2658  else if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2659  {
2660  patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2661  }
2662  if ( properties.contains( QStringLiteral( "line_width_map_unit_scale" ) ) )
2663  {
2664  patternLayer->setLineWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "line_width_map_unit_scale" )].toString() ) );
2665  }
2666  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
2667  {
2668  patternLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
2669  }
2670  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
2671  {
2672  patternLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
2673  }
2674  if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2675  {
2676  patternLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2677  }
2678  if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2679  {
2680  patternLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2681  }
2682 
2683  patternLayer->restoreOldDataDefinedProperties( properties );
2684 
2685  return patternLayer.release();
2686 }
2687 
2689 {
2690  return QStringLiteral( "LinePatternFill" );
2691 }
2692 
2693 void QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double lineAngle, double distance )
2694 {
2695  mBrush.setTextureImage( QImage() ); // set empty in case we have to return
2696 
2697  if ( !mFillLineSymbol )
2698  {
2699  return;
2700  }
2701  // We have to make a copy because marker intervals will have to be adjusted
2702  std::unique_ptr< QgsLineSymbol > fillLineSymbol( mFillLineSymbol->clone() );
2703  if ( !fillLineSymbol )
2704  {
2705  return;
2706  }
2707 
2708  const QgsRenderContext &ctx = context.renderContext();
2709  //double strokePixelWidth = lineWidth * QgsSymbolLayerUtils::pixelSizeScaleFactor( ctx, mLineWidthUnit, mLineWidthMapUnitScale );
2710  double outputPixelDist = ctx.convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
2711  double outputPixelOffset = ctx.convertToPainterUnits( mOffset, mOffsetUnit, mOffsetMapUnitScale );
2712 
2713  // NOTE: this may need to be modified if we ever change from a forced rasterized/brush approach,
2714  // because potentially we may want to allow vector based line pattern fills where the first line
2715  // is offset by a large distance
2716 
2717  // fix truncated pattern with larger offsets
2718  outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDist );
2719  if ( outputPixelOffset > outputPixelDist / 2.0 )
2720  outputPixelOffset -= outputPixelDist;
2721 
2722  // To get all patterns into image, we have to consider symbols size (estimateMaxBleed()).
2723  // For marker lines we have to get markers interval.
2724  double outputPixelBleed = 0;
2725  double outputPixelInterval = 0; // maximum interval
2726  for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2727  {
2728  QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2729  double outputPixelLayerBleed = layer->estimateMaxBleed( context.renderContext() );
2730  outputPixelBleed = std::max( outputPixelBleed, outputPixelLayerBleed );
2731 
2732  QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2733  if ( markerLineLayer )
2734  {
2735  double outputPixelLayerInterval = ctx.convertToPainterUnits( markerLineLayer->interval(), markerLineLayer->intervalUnit(), markerLineLayer->intervalMapUnitScale() );
2736 
2737  // There may be multiple marker lines with different intervals.
2738  // In theory we should find the least common multiple, but that could be too
2739  // big (multiplication of intervals in the worst case).
2740  // Because patterns without small common interval would look strange, we
2741  // believe that the longest interval should usually be sufficient.
2742  outputPixelInterval = std::max( outputPixelInterval, outputPixelLayerInterval );
2743  }
2744  }
2745 
2746  if ( outputPixelInterval > 0 )
2747  {
2748  // We have to adjust marker intervals to integer pixel size to get
2749  // repeatable pattern.
2750  double intervalScale = std::round( outputPixelInterval ) / outputPixelInterval;
2751  outputPixelInterval = std::round( outputPixelInterval );
2752 
2753  for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2754  {
2755  QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2756 
2757  QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2758  if ( markerLineLayer )
2759  {
2760  markerLineLayer->setInterval( intervalScale * markerLineLayer->interval() );
2761  }
2762  }
2763  }
2764 
2765  //create image
2766  int height, width;
2767  lineAngle = std::fmod( lineAngle, 360 );
2768  if ( lineAngle < 0 )
2769  lineAngle += 360;
2770  if ( qgsDoubleNear( lineAngle, 0 ) || qgsDoubleNear( lineAngle, 360 ) || qgsDoubleNear( lineAngle, 180 ) )
2771  {
2772  height = outputPixelDist;
2773  width = outputPixelInterval > 0 ? outputPixelInterval : height;
2774  }
2775  else if ( qgsDoubleNear( lineAngle, 90 ) || qgsDoubleNear( lineAngle, 270 ) )
2776  {
2777  width = outputPixelDist;
2778  height = outputPixelInterval > 0 ? outputPixelInterval : width;
2779  }
2780  else
2781  {
2782  height = outputPixelDist / std::cos( lineAngle * M_PI / 180 ); //keep perpendicular distance between lines constant
2783  width = outputPixelDist / std::sin( lineAngle * M_PI / 180 );
2784 
2785  // recalculate real angle and distance after rounding to pixels
2786  lineAngle = 180 * std::atan2( static_cast< double >( height ), static_cast< double >( width ) ) / M_PI;
2787  if ( lineAngle < 0 )
2788  {
2789  lineAngle += 360.;
2790  }
2791 
2792  height = std::abs( height );
2793  width = std::abs( width );
2794 
2795  outputPixelDist = std::abs( height * std::cos( lineAngle * M_PI / 180 ) );
2796 
2797  // Round offset to correspond to one pixel height, otherwise lines may
2798  // be shifted on tile border if offset falls close to pixel center
2799  int offsetHeight = static_cast< int >( std::round( outputPixelOffset / std::cos( lineAngle * M_PI / 180 ) ) );
2800  outputPixelOffset = offsetHeight * std::cos( lineAngle * M_PI / 180 );
2801  }
2802 
2803  //depending on the angle, we might need to render into a larger image and use a subset of it
2804  double dx = 0;
2805  double dy = 0;
2806 
2807  // Add buffer based on bleed but keep precisely the height/width ratio (angle)
2808  // thus we add integer multiplications of width and height covering the bleed
2809  int bufferMulti = static_cast< int >( std::max( std::ceil( outputPixelBleed / width ), std::ceil( outputPixelBleed / width ) ) );
2810 
2811  // Always buffer at least once so that center of line marker in upper right corner
2812  // does not fall outside due to representation error
2813  bufferMulti = std::max( bufferMulti, 1 );
2814 
2815  int xBuffer = width * bufferMulti;
2816  int yBuffer = height * bufferMulti;
2817  int innerWidth = width;
2818  int innerHeight = height;
2819  width += 2 * xBuffer;
2820  height += 2 * yBuffer;
2821 
2822  //protect from zero width/height image and symbol layer from eating too much memory
2823  if ( width > 10000 || height > 10000 || width == 0 || height == 0 )
2824  {
2825  return;
2826  }
2827 
2828  QImage patternImage( width, height, QImage::Format_ARGB32 );
2829  patternImage.fill( 0 );
2830 
2831  QPointF p1, p2, p3, p4, p5, p6;
2832  if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
2833  {
2834  p1 = QPointF( 0, yBuffer );
2835  p2 = QPointF( width, yBuffer );
2836  p3 = QPointF( 0, yBuffer + innerHeight );
2837  p4 = QPointF( width, yBuffer + innerHeight );
2838  }
2839  else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
2840  {
2841  p1 = QPointF( xBuffer, height );
2842  p2 = QPointF( xBuffer, 0 );
2843  p3 = QPointF( xBuffer + innerWidth, height );
2844  p4 = QPointF( xBuffer + innerWidth, 0 );
2845  }
2846  else if ( lineAngle > 0 && lineAngle < 90 )
2847  {
2848  dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
2849  dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
2850  p1 = QPointF( 0, height );
2851  p2 = QPointF( width, 0 );
2852  p3 = QPointF( -dx, height - dy );
2853  p4 = QPointF( width - dx, -dy );
2854  p5 = QPointF( dx, height + dy );
2855  p6 = QPointF( width + dx, dy );
2856  }
2857  else if ( lineAngle > 180 && lineAngle < 270 )
2858  {
2859  dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
2860  dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
2861  p1 = QPointF( width, 0 );
2862  p2 = QPointF( 0, height );
2863  p3 = QPointF( width - dx, -dy );
2864  p4 = QPointF( -dx, height - dy );
2865  p5 = QPointF( width + dx, dy );
2866  p6 = QPointF( dx, height + dy );
2867  }
2868  else if ( lineAngle > 90 && lineAngle < 180 )
2869  {
2870  dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
2871  dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
2872  p1 = QPointF( 0, 0 );
2873  p2 = QPointF( width, height );
2874  p5 = QPointF( dx, -dy );
2875  p6 = QPointF( width + dx, height - dy );
2876  p3 = QPointF( -dx, dy );
2877  p4 = QPointF( width - dx, height + dy );
2878  }
2879  else if ( lineAngle > 270 && lineAngle < 360 )
2880  {
2881  dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
2882  dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
2883  p1 = QPointF( width, height );
2884  p2 = QPointF( 0, 0 );
2885  p5 = QPointF( width + dx, height - dy );
2886  p6 = QPointF( dx, -dy );
2887  p3 = QPointF( width - dx, height + dy );
2888  p4 = QPointF( -dx, dy );
2889  }
2890 
2891  if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
2892  {
2893  QPointF tempPt;
2894  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
2895  p3 = QPointF( tempPt.x(), tempPt.y() );
2896  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
2897  p4 = QPointF( tempPt.x(), tempPt.y() );
2898  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
2899  p5 = QPointF( tempPt.x(), tempPt.y() );
2900  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
2901  p6 = QPointF( tempPt.x(), tempPt.y() );
2902 
2903  //update p1, p2 last
2904  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
2905  p1 = QPointF( tempPt.x(), tempPt.y() );
2906  tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
2907  p2 = QPointF( tempPt.x(), tempPt.y() );
2908  }
2909 
2910  QPainter p( &patternImage );
2911 
2912 #if 0
2913  // DEBUG: Draw rectangle
2914  p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
2915  QPen pen( QColor( Qt::black ) );
2916  pen.setWidthF( 0.1 );
2917  pen.setCapStyle( Qt::FlatCap );
2918  p.setPen( pen );
2919 
2920  // To see this rectangle, comment buffer cut below.
2921  // Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
2922  QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 );
2923  p.drawPolygon( polygon );
2924 
2925  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 );
2926  p.drawPolygon( polygon );
2927 #endif
2928 
2929  // Use antialiasing because without antialiasing lines are rendered to the
2930  // right and below the mathematically defined points (not symmetrical)
2931  // and such tiles become useless for are filling
2932  p.setRenderHint( QPainter::Antialiasing, true );
2933 
2934  // line rendering needs context for drawing on patternImage
2935  QgsRenderContext lineRenderContext;
2936  lineRenderContext.setPainter( &p );
2937  lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
2939  lineRenderContext.setMapToPixel( mtp );
2940  lineRenderContext.setForceVectorOutput( false );
2941  lineRenderContext.setExpressionContext( context.renderContext().expressionContext() );
2943 
2944  fillLineSymbol->startRender( lineRenderContext, context.fields() );
2945 
2946  QVector<QPolygonF> polygons;
2947  polygons.append( QPolygonF() << p1 << p2 );
2948  polygons.append( QPolygonF() << p3 << p4 );
2949  if ( !qgsDoubleNear( lineAngle, 0 ) && !qgsDoubleNear( lineAngle, 360 ) && !qgsDoubleNear( lineAngle, 90 ) && !qgsDoubleNear( lineAngle, 180 ) && !qgsDoubleNear( lineAngle, 270 ) )
2950  {
2951  polygons.append( QPolygonF() << p5 << p6 );
2952  }
2953 
2954  for ( const QPolygonF &polygon : std::as_const( polygons ) )
2955  {
2956  fillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, context.selected() );
2957  }
2958 
2959  fillLineSymbol->stopRender( lineRenderContext );
2960  p.end();
2961 
2962  // Cut off the buffer
2963  patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
2964 
2965  //set image to mBrush
2966  if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2967  {
2968  QImage transparentImage = patternImage.copy();
2969  QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2970  brush.setTextureImage( transparentImage );
2971  }
2972  else
2973  {
2974  brush.setTextureImage( patternImage );
2975  }
2976 
2977  QTransform brushTransform;
2978  brush.setTransform( brushTransform );
2979 }
2980 
2982 {
2983  applyPattern( context, mBrush, mLineAngle, mDistance );
2984 
2985  if ( mFillLineSymbol )
2986  {
2987  mFillLineSymbol->startRender( context.renderContext(), context.fields() );
2988  }
2989 }
2990 
2992 {
2993  if ( mFillLineSymbol )
2994  {
2995  mFillLineSymbol->stopRender( context.renderContext() );
2996  }
2997 }
2998 
3000 {
3001  QVariantMap map;
3002  map.insert( QStringLiteral( "angle" ), QString::number( mLineAngle ) );
3003  map.insert( QStringLiteral( "distance" ), QString::number( mDistance ) );
3004  map.insert( QStringLiteral( "line_width" ), QString::number( mLineWidth ) );
3005  map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
3006  map.insert( QStringLiteral( "offset" ), QString::number( mOffset ) );
3007  map.insert( QStringLiteral( "distance_unit" ), QgsUnitTypes::encodeUnit( mDistanceUnit ) );
3008  map.insert( QStringLiteral( "line_width_unit" ), QgsUnitTypes::encodeUnit( mLineWidthUnit ) );
3009  map.insert( QStringLiteral( "offset_unit" ), QgsUnitTypes::encodeUnit( mOffsetUnit ) );
3010  map.insert( QStringLiteral( "distance_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale ) );
3011  map.insert( QStringLiteral( "line_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineWidthMapUnitScale ) );
3012  map.insert( QStringLiteral( "offset_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ) );
3013  map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3014  map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3015  return map;
3016 }
3017 
3019 {
3021  if ( mFillLineSymbol )
3022  {
3023  clonedLayer->setSubSymbol( mFillLineSymbol->clone() );
3024  }
3025  copyPaintEffect( clonedLayer );
3026  copyDataDefinedProperties( clonedLayer );
3027  return clonedLayer;
3028 }
3029 
3030 void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3031 {
3032  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
3033  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
3034  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
3035  element.appendChild( symbolizerElem );
3036 
3037  // <Geometry>
3038  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
3039 
3040  QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
3041  symbolizerElem.appendChild( fillElem );
3042 
3043  QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
3044  fillElem.appendChild( graphicFillElem );
3045 
3046  QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
3047  graphicFillElem.appendChild( graphicElem );
3048 
3049  //line properties must be inside the graphic definition
3050  QColor lineColor = mFillLineSymbol ? mFillLineSymbol->color() : QColor();
3051  double lineWidth = mFillLineSymbol ? mFillLineSymbol->width() : 0.0;
3052  lineWidth = QgsSymbolLayerUtils::rescaleUom( lineWidth, mLineWidthUnit, props );
3053  double distance = QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, props );
3054  QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "horline" ), QColor(), lineColor, Qt::SolidLine, lineWidth, distance );
3055 
3056  // <Rotation>
3057  QString angleFunc;
3058  bool ok;
3059  double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
3060  if ( !ok )
3061  {
3062  angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mLineAngle );
3063  }
3064  else if ( !qgsDoubleNear( angle + mLineAngle, 0.0 ) )
3065  {
3066  angleFunc = QString::number( angle + mLineAngle );
3067  }
3068  QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
3069 
3070  // <se:Displacement>
3071  QPointF lineOffset( std::sin( mLineAngle ) * mOffset, std::cos( mLineAngle ) * mOffset );
3072  lineOffset = QgsSymbolLayerUtils::rescaleUom( lineOffset, mOffsetUnit, props );
3073  QgsSymbolLayerUtils::createDisplacementElement( doc, graphicElem, lineOffset );
3074 }
3075 
3076 QString QgsLinePatternFillSymbolLayer::ogrFeatureStyleWidth( double widthScaleFactor ) const
3077 {
3078  QString featureStyle;
3079  featureStyle.append( "Brush(" );
3080  featureStyle.append( QStringLiteral( "fc:%1" ).arg( mColor.name() ) );
3081  featureStyle.append( QStringLiteral( ",bc:%1" ).arg( QLatin1String( "#00000000" ) ) ); //transparent background
3082  featureStyle.append( ",id:\"ogr-brush-2\"" );
3083  featureStyle.append( QStringLiteral( ",a:%1" ).arg( mLineAngle ) );
3084  featureStyle.append( QStringLiteral( ",s:%1" ).arg( mLineWidth * widthScaleFactor ) );
3085  featureStyle.append( ",dx:0mm" );
3086  featureStyle.append( QStringLiteral( ",dy:%1mm" ).arg( mDistance * widthScaleFactor ) );
3087  featureStyle.append( ')' );
3088  return featureStyle;
3089 }
3090 
3092 {
3094  && ( !mFillLineSymbol || !mFillLineSymbol->hasDataDefinedProperties() ) )
3095  {
3096  return; //no data defined settings
3097  }
3098 
3099  double lineAngle = mLineAngle;
3101  {
3102  context.setOriginalValueVariable( mLineAngle );
3104  }
3105  double distance = mDistance;
3107  {
3108  context.setOriginalValueVariable( mDistance );
3110  }
3111  applyPattern( context, mBrush, lineAngle, distance );
3112 }
3113 
3115 {
3116  QString name;
3117  QColor fillColor, lineColor;
3118  double size, lineWidth;
3119  Qt::PenStyle lineStyle;
3120 
3121  QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
3122  if ( fillElem.isNull() )
3123  return nullptr;
3124 
3125  QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
3126  if ( graphicFillElem.isNull() )
3127  return nullptr;
3128 
3129  QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
3130  if ( graphicElem.isNull() )
3131  return nullptr;
3132 
3133  if ( !QgsSymbolLayerUtils::wellKnownMarkerFromSld( graphicElem, name, fillColor, lineColor, lineStyle, lineWidth, size ) )
3134  return nullptr;
3135 
3136  if ( name != QLatin1String( "horline" ) )
3137  return nullptr;
3138 
3139  double angle = 0.0;
3140  QString angleFunc;
3141  if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3142  {
3143  bool ok;
3144  double d = angleFunc.toDouble( &ok );
3145  if ( ok )
3146  angle = d;
3147  }
3148 
3149  double offset = 0.0;
3150  QPointF vectOffset;
3151  if ( QgsSymbolLayerUtils::displacementFromSldElement( graphicElem, vectOffset ) )
3152  {
3153  offset = std::sqrt( std::pow( vectOffset.x(), 2 ) + std::pow( vectOffset.y(), 2 ) );
3154  }
3155 
3156  QString uom = element.attribute( QStringLiteral( "uom" ) );
3157  size = QgsSymbolLayerUtils::sizeInPixelsFromSldUom( uom, size );
3159 
3160  std::unique_ptr< QgsLinePatternFillSymbolLayer > sl = std::make_unique< QgsLinePatternFillSymbolLayer >();
3161  sl->setOutputUnit( QgsUnitTypes::RenderUnit::RenderPixels );
3162  sl->setColor( lineColor );
3163  sl->setLineWidth( lineWidth );
3164  sl->setLineAngle( angle );
3165  sl->setOffset( offset );
3166  sl->setDistance( size );
3167 
3168  // try to get the stroke
3169  QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
3170  if ( !strokeElem.isNull() )
3171  {
3173  if ( l )
3174  {
3175  QgsSymbolLayerList layers;
3176  layers.append( l );
3177  sl->setSubSymbol( new QgsLineSymbol( layers ) );
3178  }
3179  }
3180 
3181  return sl.release();
3182 }
3183 
3184 
3186 
3189 {
3190  mDistanceX = 15;
3191  mDistanceY = 15;
3192  mDisplacementX = 0;
3193  mDisplacementY = 0;
3194  mOffsetX = 0;
3195  mOffsetY = 0;
3196  setSubSymbol( new QgsMarkerSymbol() );
3197  QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
3198 }
3199 
3201 {
3202  delete mMarkerSymbol;
3203 }
3204 
3206 {
3208  mDistanceXUnit = unit;
3209  mDistanceYUnit = unit;
3210  mDisplacementXUnit = unit;
3211  mDisplacementYUnit = unit;
3212  mOffsetXUnit = unit;
3213  mOffsetYUnit = unit;
3214  if ( mMarkerSymbol )
3215  {
3216  mMarkerSymbol->setOutputUnit( unit );
3217  }
3218 
3219 }
3220 
3222 {
3224  if ( mDistanceXUnit != unit || mDistanceYUnit != unit || mDisplacementXUnit != unit || mDisplacementYUnit != unit || mOffsetXUnit != unit || mOffsetYUnit != unit )
3225  {
3227  }
3228  return unit;
3229 }
3230 
3232 {
3239 }
3240 
3242 {
3244  mDistanceXMapUnitScale = scale;
3245  mDistanceYMapUnitScale = scale;
3248  mOffsetXMapUnitScale = scale;
3249  mOffsetYMapUnitScale = scale;
3250 }
3251 
3253 {
3260  {
3261  return mDistanceXMapUnitScale;
3262  }
3263  return QgsMapUnitScale();
3264 }
3265 
3267 {
3268  std::unique_ptr< QgsPointPatternFillSymbolLayer > layer = std::make_unique< QgsPointPatternFillSymbolLayer >();
3269  if ( properties.contains( QStringLiteral( "distance_x" ) ) )
3270  {
3271  layer->setDistanceX( properties[QStringLiteral( "distance_x" )].toDouble() );
3272  }
3273  if ( properties.contains( QStringLiteral( "distance_y" ) ) )
3274  {
3275  layer->setDistanceY( properties[QStringLiteral( "distance_y" )].toDouble() );
3276  }
3277  if ( properties.contains( QStringLiteral( "displacement_x" ) ) )
3278  {
3279  layer->setDisplacementX( properties[QStringLiteral( "displacement_x" )].toDouble() );
3280  }
3281  if ( properties.contains( QStringLiteral( "displacement_y" ) ) )
3282  {
3283  layer->setDisplacementY( properties[QStringLiteral( "displacement_y" )].toDouble() );
3284  }
3285  if ( properties.contains( QStringLiteral( "offset_x" ) ) )
3286  {
3287  layer->setOffsetX( properties[QStringLiteral( "offset_x" )].toDouble() );
3288  }
3289  if ( properties.contains( QStringLiteral( "offset_y" ) ) )
3290  {
3291  layer->setOffsetY( properties[QStringLiteral( "offset_y" )].toDouble() );
3292  }
3293 
3294  if ( properties.contains( QStringLiteral( "distance_x_unit" ) ) )
3295  {
3296  layer->setDistanceXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_x_unit" )].toString() ) );
3297  }
3298  if ( properties.contains( QStringLiteral( "distance_x_map_unit_scale" ) ) )
3299  {
3300  layer->setDistanceXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_x_map_unit_scale" )].toString() ) );
3301  }
3302  if ( properties.contains( QStringLiteral( "distance_y_unit" ) ) )
3303  {
3304  layer->setDistanceYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_y_unit" )].toString() ) );
3305  }
3306  if ( properties.contains( QStringLiteral( "distance_y_map_unit_scale" ) ) )
3307  {
3308  layer->setDistanceYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_y_map_unit_scale" )].toString() ) );
3309  }
3310  if ( properties.contains( QStringLiteral( "displacement_x_unit" ) ) )
3311  {
3312  layer->setDisplacementXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_x_unit" )].toString() ) );
3313  }
3314  if ( properties.contains( QStringLiteral( "displacement_x_map_unit_scale" ) ) )
3315  {
3316  layer->setDisplacementXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_x_map_unit_scale" )].toString() ) );
3317  }
3318  if ( properties.contains( QStringLiteral( "displacement_y_unit" ) ) )
3319  {
3320  layer->setDisplacementYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_y_unit" )].toString() ) );
3321  }
3322  if ( properties.contains( QStringLiteral( "displacement_y_map_unit_scale" ) ) )
3323  {
3324  layer->setDisplacementYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_y_map_unit_scale" )].toString() ) );
3325  }
3326  if ( properties.contains( QStringLiteral( "offset_x_unit" ) ) )
3327  {
3328  layer->setOffsetXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_x_unit" )].toString() ) );
3329  }
3330  if ( properties.contains( QStringLiteral( "offset_x_map_unit_scale" ) ) )
3331  {
3332  layer->setOffsetXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_x_map_unit_scale" )].toString() ) );
3333  }
3334  if ( properties.contains( QStringLiteral( "offset_y_unit" ) ) )
3335  {
3336  layer->setOffsetYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_y_unit" )].toString() ) );
3337  }
3338  if ( properties.contains( QStringLiteral( "offset_y_map_unit_scale" ) ) )
3339  {
3340  layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_y_map_unit_scale" )].toString() ) );
3341  }
3342 
3343  if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
3344  {
3345  layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
3346  }
3347  if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
3348  {
3349  layer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
3350  }
3351 
3352  layer->restoreOldDataDefinedProperties( properties );
3353 
3354  return layer.release();
3355 }
3356 
3358 {
3359  return QStringLiteral( "PointPatternFill" );
3360 }
3361 
3362 void QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double distanceX, double distanceY,
3363  double displacementX, double displacementY, double offsetX, double offsetY )
3364 {
3365  //render 3 rows and columns in one go to easily incorporate displacement
3366  const QgsRenderContext &ctx = context.renderContext();
3369 
3370  double widthOffset = std::fmod( ctx.convertToPainterUnits( offsetX, mOffsetXUnit, mOffsetXMapUnitScale ), width );
3371  double heightOffset = std::fmod( ctx.convertToPainterUnits( offsetY, mOffsetYUnit, mOffsetYMapUnitScale ), height );
3372 
3373  if ( width > 10000 || height > 10000 ) //protect symbol layer from eating too much memory
3374  {
3375  QImage img;
3376  brush.setTextureImage( img );
3377  return;
3378  }
3379 
3380  QImage patternImage( width, height, QImage::Format_ARGB32 );
3381  patternImage.fill( 0 );
3382  if ( patternImage.isNull() )
3383  {
3384  brush.setTextureImage( QImage() );
3385  return;
3386  }
3387  if ( mMarkerSymbol )
3388  {
3389  QPainter p( &patternImage );
3390 
3391  //marker rendering needs context for drawing on patternImage
3392  QgsRenderContext pointRenderContext;
3393  pointRenderContext.setRendererScale( context.renderContext().rendererScale() );
3394  pointRenderContext.setPainter( &p );
3395  pointRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3396 
3398  pointRenderContext.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
3400 
3403  pointRenderContext.setMapToPixel( mtp );
3404  pointRenderContext.setForceVectorOutput( false );
3405  pointRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3407 
3408  mMarkerSymbol->startRender( pointRenderContext, context.fields() );
3409 
3410  //render points on distance grid
3411  for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3412  {
3413  for ( double currentY = -height; currentY <= height * 2.0; currentY += height )
3414  {
3415  mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset, currentY + heightOffset ), context.feature(), pointRenderContext );
3416  }
3417  }
3418 
3419  //render displaced points
3422  for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3423  {
3424  for ( double currentY = -height / 2.0; currentY <= height * 2.0; currentY += height )
3425  {
3426  mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + displacementPixelX, currentY + heightOffset ), context.feature(), pointRenderContext );
3427  }
3428  }
3429 
3430  for ( double currentX = -width / 2.0; currentX <= width * 2.0; currentX += width )
3431  {
3432  for ( double currentY = -height; currentY <= height * 2.0; currentY += height / 2.0 )
3433  {
3434  mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + ( std::fmod( currentY, height ) != 0 ? displacementPixelX : 0 ), currentY + heightOffset - displacementPixelY ), context.feature(), pointRenderContext );
3435  }
3436  }
3437 
3438  mMarkerSymbol->stopRender( pointRenderContext );
3439  }
3440 
3441  if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3442  {
3443  QImage transparentImage = patternImage.copy();
3444  QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3445  brush.setTextureImage( transparentImage );
3446  }
3447  else
3448  {
3449  brush.setTextureImage( patternImage );
3450  }
3451  QTransform brushTransform;
3452  brush.setTransform( brushTransform );
3453 }
3454 
3456 {
3457  // if we are using a vector based output, we need to render points as vectors
3458  // (OR if the marker has data defined symbology, in which case we need to evaluate this point-by-point)
3459  mRenderUsingMarkers = context.renderContext().forceVectorOutput() || mMarkerSymbol->hasDataDefinedProperties();
3460 
3461  if ( mRenderUsingMarkers )
3462  {
3463  mMarkerSymbol->startRender( context.renderContext() );
3464  }
3465  else
3466  {
3467  // optimised render for screen only, use image based brush
3469  }
3470 
3471  if ( mStroke )
3472  {
3473  mStroke->startRender( context.renderContext(), context.fields() );
3474  }
3475 }
3476 
3478 {
3479  if ( mRenderUsingMarkers )
3480  {
3481  mMarkerSymbol->stopRender( context.renderContext() );
3482  }
3483 
3484  if ( mStroke )
3485  {
3486  mStroke->stopRender( context.renderContext() );
3487  }
3488 }
3489 
3490 void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3491 {
3492  if ( !mRenderUsingMarkers )
3493  {
3494  // use image based brush for speed
3495  QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3496  return;
3497  }
3498 
3499  // vector based output - so draw dot by dot!
3500  QPainter *p = context.renderContext().painter();
3501  if ( !p )
3502  {
3503  return;
3504  }
3505 
3506  double distanceX = mDistanceX;
3508  {
3511  }
3513 
3514  double distanceY = mDistanceY;
3516  {
3519  }
3521 
3522  double offsetX = mOffsetX;
3524  {
3527  }
3528  const double widthOffset = std::fmod( context.renderContext().convertToPainterUnits( offsetX, mOffsetXUnit, mOffsetXMapUnitScale ), width );
3529 
3530  double offsetY = mOffsetY;
3532  {
3535  }
3536  const double heightOffset = std::fmod( context.renderContext().convertToPainterUnits( offsetY, mOffsetYUnit, mOffsetYMapUnitScale ), height );
3537 
3538  double displacementX = mDisplacementX;
3540  {
3543  }
3544  const double displacementPixelX = context.renderContext().convertToPainterUnits( displacementX, mDisplacementXUnit, mDisplacementXMapUnitScale );
3545 
3546  double displacementY = mDisplacementY;
3548  {
3551  }
3552  const double displacementPixelY = context.renderContext().convertToPainterUnits( displacementY, mDisplacementYUnit, mDisplacementYMapUnitScale );
3553 
3554  p->setPen( QPen( Qt::NoPen ) );
3555 
3556  if ( context.selected() )
3557  {
3558  QColor selColor = context.renderContext().selectionColor();
3559  p->setBrush( QBrush( selColor ) );
3560  _renderPolygon( p, points, rings, context );
3561  }
3562 
3563  // if invalid parameters, skip out
3564  if ( qgsDoubleNear( width, 0 ) || qgsDoubleNear( height, 0 ) || width < 0 || height < 0 )
3565  return;
3566 
3567  p->save();
3568 
3569  QPainterPath path;
3570  path.addPolygon( points );
3571  if ( rings )
3572  {
3573  for ( const QPolygonF &ring : *rings )
3574  {
3575  path.addPolygon( ring );
3576  }
3577  }
3578  p->setClipPath( path, Qt::IntersectClip );
3579 
3580  const double left = points.boundingRect().left() - 2 * width;
3581  const double top = points.boundingRect().top() - 2 * height;
3582  const double right = points.boundingRect().right() + 2 * width;
3583  const double bottom = points.boundingRect().bottom() + 2 * height;
3584 
3586  QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
3587  int pointNum = 0;
3588  const bool needsExpressionContext = hasDataDefinedProperties();
3589 
3590  const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3592 
3593  bool alternateColumn = false;
3594  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
3595  for ( double currentX = left; currentX <= right; currentX += width, alternateColumn = !alternateColumn )
3596  {
3597  if ( needsExpressionContext )
3598  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_column" ), ++currentCol, true ) );
3599 
3600  bool alternateRow = false;
3601  const double columnX = currentX + widthOffset;
3602  int currentRow = -3;
3603  for ( double currentY = top; currentY <= bottom; currentY += height, alternateRow = !alternateRow )
3604  {
3605  double y = currentY + heightOffset;
3606  double x = columnX;
3607  if ( alternateRow )
3608  x += displacementPixelX;
3609 
3610  if ( !alternateColumn )
3611  y -= displacementPixelY;
3612 
3613  if ( needsExpressionContext )
3614  {
3616  scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_row" ), ++currentRow, true ) );
3617  }
3618 
3619  mMarkerSymbol->renderPoint( QPointF( x, y ), context.feature(), context.renderContext() );
3620  }
3621  }
3622 
3623  p->restore();
3624 
3625  if ( mStroke )
3626  {
3627  mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
3628  if ( rings )
3629  {
3630  for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
3631  {
3632  mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
3633  }
3634  }
3635  }
3636 
3638 }
3639 
3641 {
3642  QVariantMap map;
3643  map.insert( QStringLiteral( "distance_x" ), QString::number( mDistanceX ) );
3644  map.insert( QStringLiteral( "distance_y" ), QString::number( mDistanceY ) );
3645  map.insert( QStringLiteral( "displacement_x" ), QString::number( mDisplacementX ) );
3646  map.insert( QStringLiteral( "displacement_y" ), QString::number( mDisplacementY ) );
3647  map.insert( QStringLiteral( "offset_x" ), QString::number( mOffsetX ) );
3648  map.insert( QStringLiteral( "offset_y" ), QString::number( mOffsetY ) );
3649  map.insert( QStringLiteral( "distance_x_unit" ), QgsUnitTypes::encodeUnit( mDistanceXUnit ) );
3650  map.insert( QStringLiteral( "distance_y_unit" ), QgsUnitTypes::encodeUnit( mDistanceYUnit ) );
3651  map.insert( QStringLiteral( "displacement_x_unit" ), QgsUnitTypes::encodeUnit( mDisplacementXUnit ) );
3652  map.insert( QStringLiteral( "displacement_y_unit" ), QgsUnitTypes::encodeUnit( mDisplacementYUnit ) );
3653  map.insert( QStringLiteral( "offset_x_unit" ), QgsUnitTypes::encodeUnit( mOffsetXUnit ) );
3654  map.insert( QStringLiteral( "offset_y_unit" ), QgsUnitTypes::encodeUnit( mOffsetYUnit ) );
3655  map.insert( QStringLiteral( "distance_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceXMapUnitScale ) );
3656  map.insert( QStringLiteral( "distance_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceYMapUnitScale ) );
3657  map.insert( QStringLiteral( "displacement_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementXMapUnitScale ) );
3658  map.insert( QStringLiteral( "displacement_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementYMapUnitScale ) );
3659  map.insert( QStringLiteral( "offset_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetXMapUnitScale ) );
3660  map.insert( QStringLiteral( "offset_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetYMapUnitScale ) );
3661  map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3662  map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3663  return map;
3664 }
3665 
3667 {
3669  if ( mMarkerSymbol )
3670  {
3671  clonedLayer->setSubSymbol( mMarkerSymbol->clone() );
3672  }
3673  copyDataDefinedProperties( clonedLayer );
3674  copyPaintEffect( clonedLayer );
3675  return clonedLayer;
3676 }
3677 
3678 void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3679 {
3680  for ( int i = 0; i < mMarkerSymbol->symbolLayerCount(); i++ )
3681  {
3682  QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
3683  if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
3684  symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
3685  element.appendChild( symbolizerElem );
3686 
3687  // <Geometry>
3688  QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
3689 
3690  QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
3691  symbolizerElem.appendChild( fillElem );
3692 
3693  QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
3694  fillElem.appendChild( graphicFillElem );
3695 
3696  // store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
3699  QString dist = QgsSymbolLayerUtils::encodePoint( QPointF( dx, dy ) );
3700  QDomElement distanceElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "distance" ), dist );
3701  symbolizerElem.appendChild( distanceElem );
3702 
3703  QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( i );
3704  if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
3705  {
3706  markerLayer->writeSldMarker( doc, graphicFillElem, props );
3707  }
3708  else if ( layer )
3709  {
3710  QString errorMsg = QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() );
3711  graphicFillElem.appendChild( doc.createComment( errorMsg ) );
3712  }
3713  else
3714  {
3715  QString errorMsg = QStringLiteral( "Missing point pattern symbol layer. Skip it." );
3716  graphicFillElem.appendChild( doc.createComment( errorMsg ) );
3717  }
3718  }
3719 }
3720 
3722 {
3723  Q_UNUSED( element )
3724  return nullptr;
3725 }
3726 
3728 {
3729  if ( !symbol )
3730  {
3731  return false;
3732  }
3733 
3734  if ( symbol->type() == Qgis::SymbolType::Marker )
3735  {
3736  QgsMarkerSymbol *markerSymbol = static_cast<QgsMarkerSymbol *>( symbol );
3737  delete mMarkerSymbol;
3738  mMarkerSymbol = markerSymbol;
3739  }
3740  return true;
3741 }
3742 
3744 {
3745  return mMarkerSymbol;
3746 }
3747 
3749 {
3754  {
3755  return;
3756  }
3757 
3758  double distanceX = mDistanceX;
3760  {
3763  }
3764  double distanceY = mDistanceY;
3766  {
3769  }
3770  double displacementX = mDisplacementX;
3772  {
3775  }
3776  double displacementY = mDisplacementY;
3778  {
3781  }
3782  double offsetX = mOffsetX;
3784  {
3787  }
3788  double offsetY = mOffsetY;
3790  {
3793  }
3794  applyPattern( context, mBrush, distanceX, distanceY, displacementX, displacementY, offsetX, offsetY );
3795 }
3796 
3798 {
3799  return 0;
3800 }
3801 
3803 {
3804  QSet<QString> attributes = QgsImageFillSymbolLayer::usedAttributes( context );
3805 
3806  if ( mMarkerSymbol )
3807  attributes.unite( mMarkerSymbol->usedAttributes( context ) );
3808 
3809  return attributes;
3810 }
3811 
3813 {
3815  return true;
3817  return true;
3818  return false;
3819 }
3820 
3822 {
3823  mColor = c;
3824  if ( mMarkerSymbol )
3825  mMarkerSymbol->setColor( c );
3826 }
3827 
3829 {
3830  return mMarkerSymbol ? mMarkerSymbol->color() : mColor;
3831 }
3832 
3834 
3835 
3837 {
3838  setSubSymbol( new QgsMarkerSymbol() );
3839 }
3840 
3842 
3843 QgsSymbolLayer *QgsCentroidFillSymbolLayer::create( const QVariantMap &properties )
3844 {
3845  std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
3846 
3847  if ( properties.contains( QStringLiteral( "point_on_surface" ) ) )
3848  sl->setPointOnSurface( properties[QStringLiteral( "point_on_surface" )].toInt() != 0 );
3849  if ( properties.contains( QStringLiteral( "point_on_all_parts" ) ) )
3850  sl->setPointOnAllParts( properties[QStringLiteral( "point_on_all_parts" )].toInt() != 0 );
3851  if ( properties.contains( QStringLiteral( "clip_points" ) ) )
3852  sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() != 0 );
3853  if ( properties.contains( QStringLiteral( "clip_on_current_part_only" ) ) )
3854  sl->setClipOnCurrentPartOnly( properties[QStringLiteral( "clip_on_current_part_only" )].toInt() != 0 );
3855 
3856  sl->restoreOldDataDefinedProperties( properties );
3857 
3858  return sl.release();
3859 }
3860 
3862 {
3863  return QStringLiteral( "CentroidFill" );
3864 }
3865 
3866 void QgsCentroidFillSymbolLayer::setColor( const QColor &color )
3867 {
3868  mMarker->setColor( color );
3869  mColor = color;
3870 }
3871 
3873 {
3874  return mMarker ? mMarker->color() : mColor;
3875 }
3876 
3878 {
3879  mMarker->startRender( context.renderContext(), context.fields() );
3880 
3881  mCurrentFeatureId = -1;
3882  mBiggestPartIndex = 0;
3883 }
3884 
3886 {
3887  mMarker->stopRender( context.renderContext() );
3888 }
3889 
3890 void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3891 {
3892  Part part;
3893  part.exterior = points;
3894  if ( rings )
3895  part.rings = *rings;
3896 
3897  if ( mRenderingFeature )
3898  {
3899  // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
3900  // until after we've received the final part
3901  mFeatureSymbolOpacity = context.opacity();
3902  mCurrentParts << part;
3903  }
3904  else
3905  {
3906  // not rendering a feature, so we can just render the polygon immediately
3907  const double prevOpacity = mMarker->opacity();
3908  mMarker->setOpacity( mMarker->opacity() * context.opacity() );
3909  render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), context.selected() );
3910  mMarker->setOpacity( prevOpacity );
3911  }
3912 }
3913 
3915 {
3916  mRenderingFeature = true;
3917  mCurrentParts.clear();
3918 }
3919 
3921 {
3922  mRenderingFeature = false;
3923 
3924  const double prevOpacity = mMarker->opacity();
3925  mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
3926 
3927  render( context, mCurrentParts, feature, false );
3929  mMarker->setOpacity( prevOpacity );
3930 }
3931 
3932 void QgsCentroidFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsCentroidFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
3933 {
3936  bool clipPoints = mClipPoints;
3938 
3939  // TODO add expressions support
3940 
3941  QVector< QgsGeometry > geometryParts;
3942  geometryParts.reserve( parts.size() );
3943  QPainterPath globalPath;
3944 
3945  int maxArea = 0;
3946  int maxAreaPartIdx = 0;
3947 
3948  for ( int i = 0; i < parts.size(); i++ )
3949  {
3950  const Part part = parts[i];
3951  QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
3952 
3953  if ( !geom.isNull() && !part.rings.empty() )
3954  {
3955  QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
3956 
3957  if ( !pointOnAllParts )
3958  {
3959  int area = poly->area();
3960 
3961  if ( area > maxArea )
3962  {
3963  maxArea = area;
3964  maxAreaPartIdx = i;
3965  }
3966  }
3967  }
3968 
3970  {
3971  globalPath.addPolygon( part.exterior );
3972  for ( const QPolygonF &ring : part.rings )
3973  {
3974  globalPath.addPolygon( ring );
3975  }
3976  }
3977  }
3978 
3979  for ( int i = 0; i < parts.size(); i++ )
3980  {
3981  if ( !pointOnAllParts && i != maxAreaPartIdx )
3982  continue;
3983 
3984  const Part part = parts[i];
3985 
3986  if ( clipPoints )
3987  {
3988  QPainterPath path;
3989 
3990  if ( clipOnCurrentPartOnly )
3991  {
3992  path.addPolygon( part.exterior );
3993  for ( const QPolygonF &ring : part.rings )
3994  {
3995  path.addPolygon( ring );
3996  }
3997  }
3998  else
3999  {
4000  path = globalPath;
4001  }
4002 
4003  context.painter()->save();
4004  context.painter()->setClipPath( path );
4005  }
4006 
4007  QPointF centroid = pointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( part.exterior, &part.rings ) : QgsSymbolLayerUtils::polygonCentroid( part.exterior );
4008 
4009  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4011  mMarker->renderPoint( centroid, feature.isValid() ? &feature : nullptr, context, -1, selected );
4012  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
4013 
4014  if ( clipPoints )
4015  {
4016  context.painter()->restore();
4017  }
4018  }
4019 }
4020 
4022 {
4023  QVariantMap map;
4024  map[QStringLiteral( "point_on_surface" )] = QString::number( mPointOnSurface );
4025  map[QStringLiteral( "point_on_all_parts" )] = QString::number( mPointOnAllParts );
4026  map[QStringLiteral( "clip_points" )] = QString::number( mClipPoints );
4027  map[QStringLiteral( "clip_on_current_part_only" )] = QString::number( mClipOnCurrentPartOnly );
4028  return map;
4029 }
4030 
4032 {
4033  std::unique_ptr< QgsCentroidFillSymbolLayer > x = std::make_unique< QgsCentroidFillSymbolLayer >();
4034  x->mAngle = mAngle;
4035  x->mColor = mColor;
4036  x->setSubSymbol( mMarker->clone() );
4037  x->setPointOnSurface( mPointOnSurface );
4038  x->setPointOnAllParts( mPointOnAllParts );
4039  x->setClipPoints( mClipPoints );
4040  x->setClipOnCurrentPartOnly( mClipOnCurrentPartOnly );
4041  copyDataDefinedProperties( x.get() );
4042  copyPaintEffect( x.get() );
4043  return x.release();
4044 }
4045 
4046 void QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4047 {
4048  // SLD 1.0 specs says: "if a line, polygon, or raster geometry is
4049  // used with PointSymbolizer, then the semantic is to use the centroid
4050  // of the geometry, or any similar representative point.
4051  mMarker->toSld( doc, element, props );
4052 }
4053 
4055 {
4057  if ( !l )
4058  return nullptr;
4059 
4060  QgsSymbolLayerList layers;
4061  layers.append( l );
4062  std::unique_ptr< QgsMarkerSymbol > marker( new QgsMarkerSymbol( layers ) );
4063 
4064  std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4065  sl->setSubSymbol( marker.release() );
4066  sl->setPointOnAllParts( false );
4067  return sl.release();
4068 }
4069 
4070 
4072 {
4073  return mMarker.get();
4074 }
4075 
4077 {
4078  if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
4079  {
4080  delete symbol;
4081  return false;
4082  }
4083 
4084  mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
4085  mColor = mMarker->color();
4086  return true;
4087 }
4088 
4090 {
4091  QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
4092 
4093  if ( mMarker )
4094  attributes.unite( mMarker->usedAttributes( context ) );
4095 
4096  return attributes;
4097 }
4098 
4100 {
4102  return true;
4103  if ( mMarker && mMarker->hasDataDefinedProperties() )
4104  return true;
4105  return false;
4106 }
4107 
4109 {
4110  return true;
4111 }
4112 
4114 {
4115  if ( mMarker )
4116  {
4117  mMarker->setOutputUnit( unit );
4118  }
4119 }
4120 
4122 {
4123  if ( mMarker )
4124  {
4125  return mMarker->outputUnit();
4126  }
4127  return QgsUnitTypes::RenderUnknownUnit; //mOutputUnit;
4128 }
4129 
4131 {
4132  if ( mMarker )
4133  {
4134  return mMarker->usesMapUnits();
4135  }
4136  return false;
4137 }
4138 
4140 {
4141  if ( mMarker )
4142  {
4143  mMarker->setMapUnitScale( scale );
4144  }
4145 }
4146 
4148 {
4149  if ( mMarker )
4150  {
4151  return mMarker->mapUnitScale();
4152  }
4153  return QgsMapUnitScale();
4154 }
4155 
4156 
4157 
4158 
4161  , mImageFilePath( imageFilePath )
4162 {
4163  QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //disable sub symbol
4164 }
4165 
4167 
4168 QgsSymbolLayer *QgsRasterFillSymbolLayer::create( const QVariantMap &properties )
4169 {
4171  double alpha = 1.0;
4172  QPointF offset;
4173  double angle = 0.0;
4174  double width = 0.0;
4175 
4176  QString imagePath;
4177  if ( properties.contains( QStringLiteral( "imageFile" ) ) )
4178  {
4179  imagePath = properties[QStringLiteral( "imageFile" )].toString();
4180  }
4181  if ( properties.contains( QStringLiteral( "coordinate_mode" ) ) )
4182  {
4183  mode = static_cast< FillCoordinateMode >( properties[QStringLiteral( "coordinate_mode" )].toInt() );
4184  }
4185  if ( properties.contains( QStringLiteral( "alpha" ) ) )
4186  {
4187  alpha = properties[QStringLiteral( "alpha" )].toDouble();
4188  }
4189  if ( properties.contains( QStringLiteral( "offset" ) ) )
4190  {
4191  offset = QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "offset" )].toString() );
4192  }
4193  if ( properties.contains( QStringLiteral( "angle" ) ) )
4194  {
4195  angle = properties[QStringLiteral( "angle" )].toDouble();
4196  }
4197  if ( properties.contains( QStringLiteral( "width" ) ) )
4198  {
4199  width = properties[QStringLiteral( "width" )].toDouble();
4200  }
4201  std::unique_ptr< QgsRasterFillSymbolLayer > symbolLayer = std::make_unique< QgsRasterFillSymbolLayer >( imagePath );
4202  symbolLayer->setCoordinateMode( mode );
4203  symbolLayer->setOpacity( alpha );
4204  symbolLayer->setOffset( offset );
4205  symbolLayer->setAngle( angle );
4206  symbolLayer->setWidth( width );
4207  if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
4208  {
4209  symbolLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
4210  }
4211  if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
4212  {
4213  symbolLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
4214  }
4215  if ( properties.contains( QStringLiteral( "width_unit" ) ) )
4216  {
4217  symbolLayer->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "width_unit" )].toString() ) );
4218  }
4219  if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
4220  {
4221  symbolLayer->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
4222  }
4223 
4224  symbolLayer->restoreOldDataDefinedProperties( properties );
4225 
4226  return symbolLayer.release();
4227 }
4228 
4229 void QgsRasterFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
4230 {
4231  QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
4232  if ( it != properties.end() )
4233  {
4234  if ( saving )
4235  it.value() = pathResolver.writePath( it.value().toString() );
4236  else
4237  it.value() = pathResolver.readPath( it.value().toString() );
4238  }
4239 }
4240 
4242 {
4243  Q_UNUSED( symbol )
4244  return true;
4245 }
4246 
4248 {
4249  return QStringLiteral( "RasterFill" );
4250 }
4251 
4252 void QgsRasterFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4253 {
4254  QPainter *p = context.renderContext().painter();
4255  if ( !p )
4256  {
4257  return;
4258  }
4259 
4260  QPointF offset = mOffset;
4262  {
4264  const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
4265  bool ok = false;
4266  const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
4267  if ( ok )
4268  offset = res;
4269  }
4270  if ( !offset.isNull() )
4271  {
4272  offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
4273  offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
4274  p->translate( offset );
4275  }
4276  if ( mCoordinateMode == Feature )
4277  {
4278  QRectF boundingRect = points.boundingRect();
4279  mBrush.setTransform( mBrush.transform().translate( boundingRect.left() - mBrush.transform().dx(),
4280  boundingRect.top() - mBrush.transform().dy() ) );
4281  }
4282 
4283  QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
4284  if ( !offset.isNull() )
4285  {
4286  p->translate( -offset );
4287  }
4288 }
4289 
4291 {
4292  applyPattern( mBrush, mImageFilePath, mWidth, mOpacity * context.opacity(), context );
4293 }
4294 
4296 {
4297  Q_UNUSED( context )
4298 }
4299 
4301 {
4302  QVariantMap map;
4303  map[QStringLiteral( "imageFile" )] = mImageFilePath;
4304  map[QStringLiteral( "coordinate_mode" )] = QString::number( mCoordinateMode );
4305  map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
4306  map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
4307  map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
4308  map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
4309  map[QStringLiteral( "angle" )] = QString::number( mAngle );
4310  map[QStringLiteral( "width" )] = QString::number( mWidth );
4311  map[QStringLiteral( "width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
4312  map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
4313  return map;
4314 }
4315 
4317 {
4318  std::unique_ptr< QgsRasterFillSymbolLayer > sl = std::make_unique< QgsRasterFillSymbolLayer >( mImageFilePath );
4319  sl->setCoordinateMode( mCoordinateMode );
4320  sl->setOpacity( mOpacity );
4321  sl->setOffset( mOffset );
4322  sl->setOffsetUnit( mOffsetUnit );
4323  sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
4324  sl->setAngle( mAngle );
4325  sl->setWidth( mWidth );
4326  sl->setWidthUnit( mWidthUnit );
4327  sl->setWidthMapUnitScale( mWidthMapUnitScale );
4328  copyDataDefinedProperties( sl.get() );
4329  copyPaintEffect( sl.get() );
4330  return sl.release();
4331 }
4332 
4334 {
4335  return context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
4336 }
4337 
4339 {
4340  return mWidthUnit == QgsUnitTypes::RenderMapUnits || mWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
4341  || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
4342 }
4343 
4344 void QgsRasterFillSymbolLayer::setImageFilePath( const QString &imagePath )
4345 {
4346  mImageFilePath = imagePath;
4347 }
4348 
4350 {
4351  mCoordinateMode = mode;
4352 }
4353 
4354 void QgsRasterFillSymbolLayer::setOpacity( const double opacity )
4355 {
4356  mOpacity = opacity;
4357 }
4358 
4360 {
4361  if ( !dataDefinedProperties().hasActiveProperties() )
4362  return; // shortcut
4363 
4364  bool hasWidthExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyWidth );
4365  bool hasFileExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyFile );
4366  bool hasOpacityExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyOpacity );
4367  bool hasAngleExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyAngle );
4368 
4369  if ( !hasWidthExpression && !hasAngleExpression && !hasOpacityExpression && !hasFileExpression )
4370  {
4371  return; //no data defined settings
4372  }
4373 
4374  bool ok;
4375  if ( hasAngleExpression )
4376  {
4377  context.setOriginalValueVariable( mAngle );
4379  if ( ok )
4380  mNextAngle = nextAngle;
4381  }
4382 
4383  if ( !hasWidthExpression && !hasOpacityExpression && !hasFileExpression )
4384  {
4385  return; //nothing further to do
4386  }
4387 
4388  double width = mWidth;
4389  if ( hasWidthExpression )
4390  {
4391  context.setOriginalValueVariable( mWidth );
4393  }
4394  double opacity = mOpacity;
4395  if ( hasOpacityExpression )
4396  {
4397  context.setOriginalValueVariable( mOpacity );
4399  }
4400  QString file = mImageFilePath;
4401  if ( hasFileExpression )
4402  {
4403  context.setOriginalValueVariable( mImageFilePath );
4405  }
4406  applyPattern( mBrush, file, width, opacity, context );
4407 }
4408 
4410 {
4411  return false;
4412 }
4413 
4414 void QgsRasterFillSymbolLayer::applyPattern( QBrush &brush, const QString &imageFilePath, const double width, const double alpha, const QgsSymbolRenderContext &context )
4415 {
4416  QSize size;
4417  if ( width > 0 )
4418  {
4419  if ( mWidthUnit != QgsUnitTypes::RenderPercentage )
4420  {
4421  size.setWidth( context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale ) );
4422  }
4423  else
4424  {
4425  // RenderPercentage Unit Type takes original image size
4427  if ( size.isEmpty() )
4428  return;
4429 
4430  size.setWidth( ( width * size.width() ) / 100.0 );
4431 
4432  // don't render symbols with size below one or above 10,000 pixels
4433  if ( static_cast< int >( size.width() ) < 1 || 10000.0 < size.width() )
4434  return;
4435  }
4436 
4437  size.setHeight( 0 );
4438  }
4439 
4440  bool cached;
4441  QImage img = QgsApplication::imageCache()->pathAsImage( imageFilePath, size, true, alpha, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
4442  if ( img.isNull() )
4443  return;
4444 
4445  brush.setTextureImage( img );
4446 }
4447 
4448 
4449 //
4450 // QgsRandomMarkerFillSymbolLayer
4451 //
4452 
4453 QgsRandomMarkerFillSymbolLayer::QgsRandomMarkerFillSymbolLayer( int pointCount, CountMethod method, double densityArea, unsigned long seed )
4454  : mCountMethod( method )
4455  , mPointCount( pointCount )
4456  , mDensityArea( densityArea )
4457  , mSeed( seed )
4458 {
4459  setSubSymbol( new QgsMarkerSymbol() );
4460 }
4461 
4463 
4465 {
4466  const CountMethod countMethod = static_cast< CountMethod >( properties.value( QStringLiteral( "count_method" ), QStringLiteral( "0" ) ).toInt() );
4467  const int pointCount = properties.value( QStringLiteral( "point_count" ), QStringLiteral( "10" ) ).toInt();
4468  const double densityArea = properties.value( QStringLiteral( "density_area" ), QStringLiteral( "250.0" ) ).toDouble();
4469 
4470  unsigned long seed = 0;
4471  if ( properties.contains( QStringLiteral( "seed" ) ) )
4472  seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
4473  else
4474  {
4475  // if we a creating a new random marker fill from scratch, we default to a random seed
4476  // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
4477  std::random_device rd;
4478  std::mt19937 mt( seed == 0 ? rd() : seed );
4479  std::uniform_int_distribution<> uniformDist( 1, 999999999 );
4480  seed = uniformDist( mt );
4481  }
4482 
4483  std::unique_ptr< QgsRandomMarkerFillSymbolLayer > sl = std::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, countMethod, densityArea, seed );
4484 
4485  if ( properties.contains( QStringLiteral( "density_area_unit" ) ) )
4486  sl->setDensityAreaUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "density_area_unit" )].toString() ) );
4487  if ( properties.contains( QStringLiteral( "density_area_unit_scale" ) ) )
4488  sl->setDensityAreaUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "density_area_unit_scale" )].toString() ) );
4489 
4490  if ( properties.contains( QStringLiteral( "clip_points" ) ) )
4491  {
4492  sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() );
4493  }
4494 
4495  return sl.release();
4496 }
4497 
4499 {
4500  return QStringLiteral( "RandomMarkerFill" );
4501 }
4502 
4503 void QgsRandomMarkerFillSymbolLayer::setColor( const QColor &color )
4504 {
4505  mMarker->setColor( color );
4506  mColor = color;
4507 }
4508 
4510 {
4511  return mMarker ? mMarker->color() : mColor;
4512 }
4513 
4515 {
4516  mMarker->startRender( context.renderContext(), context.fields() );
4517 }
4518 
4520 {
4521  mMarker->stopRender( context.renderContext() );
4522 }
4523 
4524 void QgsRandomMarkerFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4525 {
4526  Part part;
4527  part.exterior = points;
4528  if ( rings )
4529  part.rings = *rings;
4530 
4531  if ( mRenderingFeature )
4532  {
4533  // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
4534  // until after we've received the final part
4535  mFeatureSymbolOpacity = context.opacity();
4536  mCurrentParts << part;
4537  }
4538  else
4539  {
4540  // not rendering a feature, so we can just render the polygon immediately
4541  const double prevOpacity = mMarker->opacity();
4542  mMarker->setOpacity( mMarker->opacity() * context.opacity() );
4543  render( context.renderContext(), QVector< Part>() << part, context.feature() ? *context.feature() : QgsFeature(), context.selected() );
4544  mMarker->setOpacity( prevOpacity );
4545  }
4546 }
4547 
4548 void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsRandomMarkerFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
4549 {
4550  bool clipPoints = mClipPoints;
4552  {
4555  }
4556 
4557  QVector< QgsGeometry > geometryParts;
4558  geometryParts.reserve( parts.size() );
4559  QPainterPath path;
4560 
4561  for ( const Part &part : parts )
4562  {
4563  QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
4564  if ( !geom.isNull() && !part.rings.empty() )
4565  {
4566  QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
4567  for ( const QPolygonF &ring : part.rings )
4568  {
4570  }
4571  }
4572  if ( !geom.isGeosValid() )
4573  {
4574  geom = geom.buffer( 0, 0 );
4575  }
4576  geometryParts << geom;
4577 
4578  if ( clipPoints )
4579  {
4580  path.addPolygon( part.exterior );
4581  for ( const QPolygonF &ring : part.rings )
4582  {
4583  path.addPolygon( ring );
4584  }
4585  }
4586  }
4587 
4588  const QgsGeometry geom = geometryParts.count() != 1 ? QgsGeometry::unaryUnion( geometryParts ) : geometryParts.at( 0 );
4589 
4590  if ( clipPoints )
4591  {
4592  context.painter()->save();
4593  context.painter()->setClipPath( path );
4594  }
4595 
4596 
4597  int count = mPointCount;
4599  {
4600  context.expressionContext().setOriginalValueVariable( count );
4602  }
4603 
4604  switch ( mCountMethod )
4605  {
4606  case DensityBasedCount:
4607  {
4608  double densityArea = mDensityArea;
4610  {
4613  }
4614  densityArea = context.convertToPainterUnits( std::sqrt( densityArea ), mDensityAreaUnit, mDensityAreaUnitScale );
4615  densityArea = std::pow( densityArea, 2 );
4616  count = std::max( 0.0, std::ceil( count * ( geom.area() / densityArea ) ) );
4617  break;
4618  }
4619  case AbsoluteCount:
4620  break;
4621  }
4622 
4623  unsigned long seed = mSeed;
4625  {
4626  context.expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
4628  }
4629 
4630  QVector< QgsPointXY > randomPoints = geom.randomPointsInPolygon( count, seed );
4631 #if 0
4632  // in some cases rendering from top to bottom is nice (e.g. randomised tree markers), but in other cases it's not wanted..
4633  // TODO consider exposing this as an option
4634  std::sort( randomPoints.begin(), randomPoints.end(), []( const QgsPointXY & a, const QgsPointXY & b )->bool
4635  {
4636  return a.y() < b.y();
4637  } );
4638 #endif
4640  QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scope );
4641  int pointNum = 0;
4642  const bool needsExpressionContext = hasDataDefinedProperties();
4643 
4644  const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4646 
4647  for ( const QgsPointXY &p : std::as_const( randomPoints ) )
4648  {
4649  if ( needsExpressionContext )
4651  mMarker->renderPoint( QPointF( p.x(), p.y() ), feature.isValid() ? &feature : nullptr, context, -1, selected );
4652  }
4653 
4654  context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
4655 
4656  if ( clipPoints )
4657  {
4658  context.painter()->restore();
4659  }
4660 }
4661 
4663 {
4664  QVariantMap map;
4665  map.insert( QStringLiteral( "count_method" ), QString::number( static_cast< int >( mCountMethod ) ) );
4666  map.insert( QStringLiteral( "point_count" ), QString::number( mPointCount ) );
4667  map.insert( QStringLiteral( "density_area" ), QString::number( mDensityArea ) );
4668  map.insert( QStringLiteral( "density_area_unit" ), QgsUnitTypes::encodeUnit( mDensityAreaUnit ) );
4669  map.insert( QStringLiteral( "density_area_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDensityAreaUnitScale ) );
4670  map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
4671  map.insert( QStringLiteral( "clip_points" ), QString::number( mClipPoints ) );
4672  return map;
4673 }
4674 
4676 {
4677  std::unique_ptr< QgsRandomMarkerFillSymbolLayer > res = std::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mCountMethod, mDensityArea, mSeed );
4678  res->mAngle = mAngle;
4679  res->mColor = mColor;
4680  res->setDensityAreaUnit( mDensityAreaUnit );
4681  res->setDensityAreaUnitScale( mDensityAreaUnitScale );
4682  res->mClipPoints = mClipPoints;
4683  res->setSubSymbol( mMarker->clone() );
4684  copyDataDefinedProperties( res.get() );
4685  copyPaintEffect( res.get() );
4686  return res.release();
4687 }
4688 
4690 {
4691  return true;
4692 }
4693 
4695 {
4696  return mMarker.get();
4697 }
4698 
4700 {
4701  if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
4702  {
4703  delete symbol;
4704  return false;
4705  }
4706 
4707  mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
4708  mColor = mMarker->color();
4709  return true;
4710 }
4711 
4713 {
4714  QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
4715 
4716  if ( mMarker )
4717  attributes.unite( mMarker->usedAttributes( context ) );
4718 
4719  return attributes;
4720 }
4721 
4723 {
4725  return true;
4726  if ( mMarker && mMarker->hasDataDefinedProperties() )
4727  return true;
4728  return false;
4729 }
4730 
4732 {
4733  return mPointCount;
4734 }
4735 
4737 {
4738  mPointCount = pointCount;
4739 }
4740 
4742 {
4743  return mSeed;
4744 }
4745 
4747 {
4748  mSeed = seed;
4749 }
4750 
4752 {
4753  return mClipPoints;
4754 }
4755 
4757 {
4758  mClipPoints = clipPoints;
4759 }
4760 
4762 {
4763  return mCountMethod;
4764 }
4765 
4767 {
4768  mCountMethod = method;
4769 }
4770 
4772 {
4773  return mDensityArea;
4774 }
4775 
4777 {
4778  mDensityArea = area;
4779 }
4780 
4782 {
4783  mRenderingFeature = true;
4784  mCurrentParts.clear();
4785 }
4786 
4788 {
4789  mRenderingFeature = false;
4790 
4791  const double prevOpacity = mMarker->opacity();
4792  mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
4793 
4794  render( context, mCurrentParts, feature, false );
4795 
4796  mFeatureSymbolOpacity = 1;
4797  mMarker->setOpacity( prevOpacity );
4798 }
4799 
4800 
4802 {
4803  if ( mMarker )
4804  {
4805  mMarker->setOutputUnit( unit );
4806  }
4807 }
4808 
4810 {
4811  if ( mMarker )
4812  {
4813  return mMarker->outputUnit();
4814  }
4815  return QgsUnitTypes::RenderUnknownUnit; //mOutputUnit;
4816 }
4817 
4819 {
4820  if ( mMarker )
4821  {
4822  return mMarker->usesMapUnits();
4823  }
4824  return false;
4825 }
4826 
4828 {
4829  if ( mMarker )
4830  {
4831  mMarker->setMapUnitScale( scale );
4832  }
4833 }
4834 
4836 {
4837  if ( mMarker )
4838  {
4839  return mMarker->mapUnitScale();
4840  }
4841  return QgsMapUnitScale();
4842 }
4843 
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ LosslessImageRendering
Render images losslessly whenever possible, instead of the default lossy jpeg rendering used for some...
@ Antialiasing
Use antialiasing while drawing.
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
@ Marker
Marker symbol.
@ Line
Line symbol.
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
The fill color.
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
The fill color.
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:32
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.
Definition: qgscolorramp.h:714
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...
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.
bool isGeosValid(Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Checks validity of the geometry using GEOS.
double area() const
Returns the planar, 2-dimensional area of the geometry.
QgsGeometry buffer(double distance, int segments) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
Definition: qgscolorramp.h:153
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.
Definition: qgscolorramp.h:181
void addStopsToGradient(QGradient *gradient, double opacity=1)
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
GradientCoordinateMode coordinateMode() const
Coordinate mode for gradient. Controls how the gradient stops are positioned.
QColor color2() const
Color for endpoint of gradient, only used if the gradient color type is set to SimpleTwoColor.
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.
GradientColorType mGradientColorType
QgsMapUnitScale mapUnitScale() const override
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
GradientSpread gradientSpread() const
Gradient spread mode. Controls how the gradient behaves outside of the predefined stops.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
GradientColorType gradientColorType() const
Gradient color mode, controls how gradient color stops are created.
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 ...
GradientCoordinateMode mCoordinateMode
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QPointF offset() const
Returns the offset by which polygons will be translated during rendering.
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.
QgsGradientFillSymbolLayer(const QColor &color=DEFAULT_SIMPLEFILL_COLOR, const QColor &color2=Qt::white, GradientColorType gradientColorType=SimpleTwoColor, GradientType gradientType=Linear, GradientCoordinateMode coordinateMode=Feature, GradientSpread gradientSpread=Pad)
GradientType gradientType() const
Type of gradient, e.g., linear or radial.
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.
std::unique_ptr< QgsLineSymbol > mStroke
Custom stroke.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
QgsMapUnitScale mStrokeWidthMapUnitScale
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.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
double mStrokeWidth
Stroke width.
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
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.
virtual void applyDataDefinedSettings(QgsSymbolRenderContext &context)
virtual bool applyBrushTransformFromContext() const
Returns true if the image brush should be transformed using the render context's texture origin.
QColor dxfColor(QgsSymbolRenderContext &context) const override
Gets color.
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
~QgsImageFillSymbolLayer() override
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.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QColor color() const override
The fill color.
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
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.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
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
The fill color.
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.
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.
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
QgsLineSymbol * clone() const override
Returns a deep copy of this symbol.
double width() const
Returns the estimated width for the whole symbol, which is the maximum width of all marker symbol lay...
void setWidth(double width)
Sets the width for the whole line symbol.
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.
QgsMarkerSymbol * clone() const override
Returns a deep copy of this symbol.
void renderPoint(QPointF point, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)
Renders the symbol at the specified point, using the given render context.
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.
QgsMapUnitScale mapUnitScale() const override
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit mDisplacementXUnit
QgsUnitTypes::RenderUnit mDistanceYUnit
void setColor(const QColor &c) override
The fill color.
QgsUnitTypes::RenderUnit mOffsetXUnit
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsPointPatternFillSymbolLayer using the specified properties map containing symbol pro...
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
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QColor color() const override
The fill color.
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.
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
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsUnitTypes::RenderUnit mDisplacementYUnit
static QgsSymbolLayer * createFromSld(QDomElement &element)
A class to represent a 2D point.
Definition: qgspointxy.h:59
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.
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.
void setCountMethod(CountMethod method)
Sets the count method used to randomly fill the polygon.
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.
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
The fill color.
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...
QgsRandomMarkerFillSymbolLayer(int pointCount=10, CountMethod method=AbsoluteCount, double densityArea=250.0, unsigned long seed=0)
Constructor for QgsRandomMarkerFillSymbolLayer, with the specified pointCount.
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.
CountMethod
Methods to define the number of points randomly filling the polygon.
@ AbsoluteCount
The point count is used as an absolute count of markers.
@ DensityBasedCount
The point count is part of a marker density count.
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
The fill color.
void setDensityArea(double area)
Sets the density area used to count the number of points to randomly fill the polygon.
QgsMapUnitScale mapUnitScale() const override
CountMethod countMethod() const
Returns the count method used to randomly fill the polygon.
A class for filling symbols with a repeated raster image.
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
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.
FillCoordinateMode
Fill coordinate modes, dictates fill tiling behavior.
@ Feature
Tiling is based on feature bounding box.
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.
bool applyBrushTransformFromContext() const override
Returns true if the image brush should be transformed using the render context's texture origin.
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.
void setCoordinateMode(FillCoordinateMode mode)
Set the coordinate mode for fill.
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.
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.
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.
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.
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.
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.
~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
QgsMapUnitScale mapUnitScale() const override
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 * 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
QgsShapeburstFillSymbolLayer(const QColor &color=DEFAULT_SIMPLEFILL_COLOR, const QColor &color2=Qt::white, ShapeburstColorType colorType=SimpleTwoColor, int blurRadius=0, bool useWholeShape=true, double maxDistance=5)
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.
ShapeburstColorType colorType() const
Returns the color mode used for the shapeburst fill.
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.
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
Gets stroke color.
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
Gets fill color.
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 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 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 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 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)
@ 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.
@ 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.
@ 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.
@ 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.
@ PropertyFillStyle
Fill style (eg solid, dots)
@ PropertyDisplacementY
Vertical displacement.
@ PropertyStrokeColor
Stroke color.
@ PropertyGradientReference2IsCentroid
Gradient reference point 2 is centroid.
@ PropertyWidth
Symbol width.
Qgis::SymbolType type() const
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
virtual QColor color() const
The fill color.
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 QColor fillColor() const
Gets fill color.
virtual QString layerType() const =0
Returns a string that represents this layer type.
virtual QColor strokeColor() const
Gets stroke color.
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
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
Definition: qgssymbol.cpp:420
void stopRender(QgsRenderContext &context)
Ends the rendering process.
Definition: qgssymbol.cpp:516
void setOutputUnit(QgsUnitTypes::RenderUnit unit)
Sets the units to use for sizes and widths within the symbol.
Definition: qgssymbol.cpp:337
bool hasDataDefinedProperties() const
Returns whether the symbol utilizes any data defined properties.
Definition: qgssymbol.cpp:839
QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns a list of attributes required to render this feature.
Definition: qgssymbol.cpp:817
void setColor(const QColor &color)
Sets the color for the symbol.
Definition: qgssymbol.cpp:541
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:160
QColor color() const
Returns the symbol's color.
Definition: qgssymbol.cpp:551
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:97
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
Definition: qgssymbol.cpp:489
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:1246
QMap< QString, QString > QgsStringMap
Definition: qgis.h:1703
#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.