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