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