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