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