QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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"
19#include "qgssymbollayerutils.h"
20#include "qgsdxfexport.h"
21#include "qgsexpression.h"
22#include "qgsgeometry.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
55QgsSimpleFillSymbolLayer::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
105void 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;
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
293void 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
389void 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 );
407
408 QColor color { mColor };
409
410 // Apply alpha from symbol
411 bool ok;
412 const double alpha { props.value( QStringLiteral( "alpha" ), QVariant() ).toDouble( &ok ) };
413 if ( ok )
414 {
415 color.setAlphaF( color.alphaF() * alpha );
416 }
418 }
419
420 if ( mStrokeStyle != Qt::NoPen )
421 {
422 // <Stroke>
423 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
424 symbolizerElem.appendChild( strokeElem );
426 // Apply alpha from symbol
427 bool ok;
428 const double alpha { props.value( QStringLiteral( "alpha" ), QVariant() ).toDouble( &ok ) };
429 QColor strokeColor { mStrokeColor };
430 if ( ok )
431 {
432 strokeColor.setAlphaF( strokeColor.alphaF() * alpha );
433 }
435 }
436
437 // <se:Displacement>
440}
441
442QString QgsSimpleFillSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
443{
444 //brush
445 QString symbolStyle;
446 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStyleBrush( mColor ) );
447 symbolStyle.append( ';' );
448 //pen
449 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStylePen( mStrokeWidth, mmScaleFactor, mapUnitScaleFactor, mStrokeColor, mPenJoinStyle ) );
450 return symbolStyle;
451}
452
454{
455 QColor color, strokeColor;
456 Qt::BrushStyle fillStyle;
457 Qt::PenStyle strokeStyle;
458 double strokeWidth;
459
460 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
461 QgsSymbolLayerUtils::fillFromSld( fillElem, fillStyle, color );
462
463 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
465
466 QPointF offset;
468
469 double scaleFactor = 1.0;
470 const QString uom = element.attribute( QStringLiteral( "uom" ) );
471 QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
472 offset.setX( offset.x() * scaleFactor );
473 offset.setY( offset.y() * scaleFactor );
474 strokeWidth = strokeWidth * scaleFactor;
475
476 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, fillStyle, strokeColor, strokeStyle, strokeWidth );
477 sl->setOutputUnit( sldUnitSize );
478 sl->setOffset( offset );
479 return sl.release();
480}
481
483{
484 double penBleed = context.convertToPainterUnits( mStrokeStyle == Qt::NoPen ? 0 : ( mStrokeWidth / 2.0 ), mStrokeWidthUnit, mStrokeWidthMapUnitScale );
485 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
486 return penBleed + offsetBleed;
487}
488
490{
491 double width = mStrokeWidth;
493 {
496 }
498}
499
501{
502 QColor c = mStrokeColor;
504 {
507 }
508 return c;
509}
510
512{
513 double angle = mAngle;
515 {
518 }
519 return angle;
520}
521
523{
524 return mStrokeStyle;
525}
526
528{
529 QColor c = mColor;
531 {
533 }
534 return c;
535}
536
538{
539 return mBrushStyle;
540}
541
542//QgsGradientFillSymbolLayer
543
544QgsGradientFillSymbolLayer::QgsGradientFillSymbolLayer( const QColor &color, const QColor &color2,
545 Qgis::GradientColorSource colorType, Qgis::GradientType gradientType,
547 : mGradientColorType( colorType )
548 , mGradientType( gradientType )
549 , mCoordinateMode( coordinateMode )
550 , mGradientSpread( spread )
551 , mReferencePoint1( QPointF( 0.5, 0 ) )
552 , mReferencePoint2( QPointF( 0.5, 1 ) )
553{
554 mColor = color;
555 mColor2 = color2;
556}
557
559{
560 delete mGradientRamp;
561}
562
564{
565 //default to a two-color, linear gradient with feature mode and pad spreading
570 //default to gradient from the default fill color to white
571 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
572 QPointF referencePoint1 = QPointF( 0.5, 0 );
573 bool refPoint1IsCentroid = false;
574 QPointF referencePoint2 = QPointF( 0.5, 1 );
575 bool refPoint2IsCentroid = false;
576 double angle = 0;
577 QPointF offset;
578
579 //update gradient properties from props
580 if ( props.contains( QStringLiteral( "type" ) ) )
581 type = static_cast< Qgis::GradientType >( props[QStringLiteral( "type" )].toInt() );
582 if ( props.contains( QStringLiteral( "coordinate_mode" ) ) )
583 coordinateMode = static_cast< Qgis::SymbolCoordinateReference >( props[QStringLiteral( "coordinate_mode" )].toInt() );
584 if ( props.contains( QStringLiteral( "spread" ) ) )
585 gradientSpread = static_cast< Qgis::GradientSpread >( props[QStringLiteral( "spread" )].toInt() );
586 if ( props.contains( QStringLiteral( "color_type" ) ) )
587 colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
588 if ( props.contains( QStringLiteral( "gradient_color" ) ) )
589 {
590 //pre 2.5 projects used "gradient_color"
591 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color" )].toString() );
592 }
593 else if ( props.contains( QStringLiteral( "color" ) ) )
594 {
595 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
596 }
597 if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
598 {
599 color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
600 }
601
602 if ( props.contains( QStringLiteral( "reference_point1" ) ) )
603 referencePoint1 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point1" )].toString() );
604 if ( props.contains( QStringLiteral( "reference_point1_iscentroid" ) ) )
605 refPoint1IsCentroid = props[QStringLiteral( "reference_point1_iscentroid" )].toInt();
606 if ( props.contains( QStringLiteral( "reference_point2" ) ) )
607 referencePoint2 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point2" )].toString() );
608 if ( props.contains( QStringLiteral( "reference_point2_iscentroid" ) ) )
609 refPoint2IsCentroid = props[QStringLiteral( "reference_point2_iscentroid" )].toInt();
610 if ( props.contains( QStringLiteral( "angle" ) ) )
611 angle = props[QStringLiteral( "angle" )].toDouble();
612
613 if ( props.contains( QStringLiteral( "offset" ) ) )
614 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
615
616 //attempt to create color ramp from props
617 QgsColorRamp *gradientRamp = nullptr;
618 if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
619 {
620 gradientRamp = QgsCptCityColorRamp::create( props );
621 }
622 else
623 {
624 gradientRamp = QgsGradientColorRamp::create( props );
625 }
626
627 //create a new gradient fill layer with desired properties
628 std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( color, color2, colorType, type, coordinateMode, gradientSpread );
629 sl->setOffset( offset );
630 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
631 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
632 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
633 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
634 sl->setReferencePoint1( referencePoint1 );
635 sl->setReferencePoint1IsCentroid( refPoint1IsCentroid );
636 sl->setReferencePoint2( referencePoint2 );
637 sl->setReferencePoint2IsCentroid( refPoint2IsCentroid );
638 sl->setAngle( angle );
639 if ( gradientRamp )
640 sl->setColorRamp( gradientRamp );
641
642 sl->restoreOldDataDefinedProperties( props );
643
644 return sl.release();
645}
646
648{
649 delete mGradientRamp;
650 mGradientRamp = ramp;
651}
652
654{
655 return QStringLiteral( "GradientFill" );
656}
657
658void QgsGradientFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, const QPolygonF &points )
659{
661 {
662 //shortcut
665 return;
666 }
667
668 bool ok;
669
670 //first gradient color
671 QColor color = mColor;
673 {
676 color.setAlphaF( context.opacity() * color.alphaF() );
677 }
678
679 //second gradient color
680 QColor color2 = mColor2;
682 {
685 color2.setAlphaF( context.opacity() * color2.alphaF() );
686 }
687
688 //gradient rotation angle
689 double angle = mAngle;
691 {
694 }
695
696 //gradient type
699 {
701 if ( ok )
702 {
703 if ( currentType == QObject::tr( "linear" ) )
704 {
706 }
707 else if ( currentType == QObject::tr( "radial" ) )
708 {
710 }
711 else if ( currentType == QObject::tr( "conical" ) )
712 {
714 }
715 }
716 }
717
718 //coordinate mode
721 {
722 QString currentCoordMode = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyCoordinateMode, context.renderContext().expressionContext(), QString(), &ok );
723 if ( ok )
724 {
725 if ( currentCoordMode == QObject::tr( "feature" ) )
726 {
728 }
729 else if ( currentCoordMode == QObject::tr( "viewport" ) )
730 {
732 }
733 }
734 }
735
736 //gradient spread
739 {
741 if ( ok )
742 {
743 if ( currentSpread == QObject::tr( "pad" ) )
744 {
746 }
747 else if ( currentSpread == QObject::tr( "repeat" ) )
748 {
750 }
751 else if ( currentSpread == QObject::tr( "reflect" ) )
752 {
754 }
755 }
756 }
757
758 //reference point 1 x & y
759 double refPoint1X = mReferencePoint1.x();
761 {
762 context.setOriginalValueVariable( refPoint1X );
764 }
765 double refPoint1Y = mReferencePoint1.y();
767 {
768 context.setOriginalValueVariable( refPoint1Y );
770 }
771 bool refPoint1IsCentroid = mReferencePoint1IsCentroid;
773 {
774 context.setOriginalValueVariable( refPoint1IsCentroid );
776 }
777
778 //reference point 2 x & y
779 double refPoint2X = mReferencePoint2.x();
781 {
782 context.setOriginalValueVariable( refPoint2X );
784 }
785 double refPoint2Y = mReferencePoint2.y();
787 {
788 context.setOriginalValueVariable( refPoint2Y );
790 }
791 bool refPoint2IsCentroid = mReferencePoint2IsCentroid;
793 {
794 context.setOriginalValueVariable( refPoint2IsCentroid );
796 }
797
798 if ( refPoint1IsCentroid || refPoint2IsCentroid )
799 {
800 //either the gradient is starting or ending at a centroid, so calculate it
802 //centroid coordinates need to be scaled to a range [0, 1] relative to polygon bounds
803 QRectF bbox = points.boundingRect();
804 double centroidX = ( centroid.x() - bbox.left() ) / bbox.width();
805 double centroidY = ( centroid.y() - bbox.top() ) / bbox.height();
806
807 if ( refPoint1IsCentroid )
808 {
809 refPoint1X = centroidX;
810 refPoint1Y = centroidY;
811 }
812 if ( refPoint2IsCentroid )
813 {
814 refPoint2X = centroidX;
815 refPoint2Y = centroidY;
816 }
817 }
818
819 //update gradient with data defined values
821 spread, QPointF( refPoint1X, refPoint1Y ), QPointF( refPoint2X, refPoint2Y ), angle );
822}
823
824QPointF QgsGradientFillSymbolLayer::rotateReferencePoint( QPointF refPoint, double angle )
825{
826 //rotate a reference point by a specified angle around the point (0.5, 0.5)
827
828 //create a line from the centrepoint of a rectangle bounded by (0, 0) and (1, 1) to the reference point
829 QLineF refLine = QLineF( QPointF( 0.5, 0.5 ), refPoint );
830 //rotate this line by the current rotation angle
831 refLine.setAngle( refLine.angle() + angle );
832 //get new end point of line
833 QPointF rotatedReferencePoint = refLine.p2();
834 //make sure coords of new end point is within [0, 1]
835 if ( rotatedReferencePoint.x() > 1 )
836 rotatedReferencePoint.setX( 1 );
837 if ( rotatedReferencePoint.x() < 0 )
838 rotatedReferencePoint.setX( 0 );
839 if ( rotatedReferencePoint.y() > 1 )
840 rotatedReferencePoint.setY( 1 );
841 if ( rotatedReferencePoint.y() < 0 )
842 rotatedReferencePoint.setY( 0 );
843
844 return rotatedReferencePoint;
845}
846
847void QgsGradientFillSymbolLayer::applyGradient( const QgsSymbolRenderContext &context, QBrush &brush,
848 const QColor &color, const QColor &color2, Qgis::GradientColorSource gradientColorType,
849 QgsColorRamp *gradientRamp, Qgis::GradientType gradientType,
850 Qgis::SymbolCoordinateReference coordinateMode, Qgis::GradientSpread gradientSpread,
851 QPointF referencePoint1, QPointF referencePoint2, const double angle )
852{
853 //update alpha of gradient colors
854 QColor fillColor = color;
855 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
856 QColor fillColor2 = color2;
857 fillColor2.setAlphaF( context.opacity() * fillColor2.alphaF() );
858
859 //rotate reference points
860 QPointF rotatedReferencePoint1 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint1, angle ) : referencePoint1;
861 QPointF rotatedReferencePoint2 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint2, angle ) : referencePoint2;
862
863 //create a QGradient with the desired properties
864 QGradient gradient;
865 switch ( gradientType )
866 {
868 gradient = QLinearGradient( rotatedReferencePoint1, rotatedReferencePoint2 );
869 break;
871 gradient = QRadialGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).length() );
872 break;
874 gradient = QConicalGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).angle() );
875 break;
876 }
877 switch ( coordinateMode )
878 {
880 gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
881 break;
883 gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
884 break;
885 }
886 switch ( gradientSpread )
887 {
889 gradient.setSpread( QGradient::PadSpread );
890 break;
892 gradient.setSpread( QGradient::ReflectSpread );
893 break;
895 gradient.setSpread( QGradient::RepeatSpread );
896 break;
897 }
898
899 //add stops to gradient
901 ( gradientRamp->type() == QgsGradientColorRamp::typeString() || gradientRamp->type() == QgsCptCityColorRamp::typeString() ) )
902 {
903 //color ramp gradient
904 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( gradientRamp );
905 gradRamp->addStopsToGradient( &gradient, context.opacity() );
906 }
907 else
908 {
909 //two color gradient
910 gradient.setColorAt( 0.0, fillColor );
911 gradient.setColorAt( 1.0, fillColor2 );
912 }
913
914 //update QBrush use gradient
915 brush = QBrush( gradient );
916}
917
919{
920 QColor selColor = context.renderContext().selectionColor();
921 if ( ! SELECTION_IS_OPAQUE )
922 selColor.setAlphaF( context.opacity() );
923 mSelBrush = QBrush( selColor );
924}
925
927{
928 Q_UNUSED( context )
929}
930
931void QgsGradientFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
932{
933 QPainter *p = context.renderContext().painter();
934 if ( !p )
935 {
936 return;
937 }
938
939 applyDataDefinedSymbology( context, points );
940
941 p->setBrush( context.selected() ? mSelBrush : mBrush );
942 p->setPen( Qt::NoPen );
943
944 QPointF offset = mOffset;
946 {
948 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
949 bool ok = false;
950 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
951 if ( ok )
952 offset = res;
953 }
954
955 if ( !offset.isNull() )
956 {
959 p->translate( offset );
960 }
961
962 _renderPolygon( p, points, rings, context );
963
964 if ( !offset.isNull() )
965 {
966 p->translate( -offset );
967 }
968}
969
971{
972 QVariantMap map;
973 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
974 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
975 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
976 map[QStringLiteral( "type" )] = QString::number( static_cast<int>( mGradientType ) );
977 map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
978 map[QStringLiteral( "spread" )] = QString::number( static_cast< int >( mGradientSpread ) );
979 map[QStringLiteral( "reference_point1" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint1 );
980 map[QStringLiteral( "reference_point1_iscentroid" )] = QString::number( mReferencePoint1IsCentroid );
981 map[QStringLiteral( "reference_point2" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint2 );
982 map[QStringLiteral( "reference_point2_iscentroid" )] = QString::number( mReferencePoint2IsCentroid );
983 map[QStringLiteral( "angle" )] = QString::number( mAngle );
984 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
985 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
986 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
987 if ( mGradientRamp )
988 {
989#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
990 map.unite( mGradientRamp->properties() );
991#else
992 map.insert( mGradientRamp->properties() );
993#endif
994 }
995 return map;
996}
997
999{
1000 std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( mColor, mColor2, mGradientColorType, mGradientType, mCoordinateMode, mGradientSpread );
1001 if ( mGradientRamp )
1002 sl->setColorRamp( mGradientRamp->clone() );
1003 sl->setReferencePoint1( mReferencePoint1 );
1004 sl->setReferencePoint1IsCentroid( mReferencePoint1IsCentroid );
1005 sl->setReferencePoint2( mReferencePoint2 );
1006 sl->setReferencePoint2IsCentroid( mReferencePoint2IsCentroid );
1007 sl->setAngle( mAngle );
1008 sl->setOffset( mOffset );
1009 sl->setOffsetUnit( mOffsetUnit );
1010 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1011 copyDataDefinedProperties( sl.get() );
1012 copyPaintEffect( sl.get() );
1013 return sl.release();
1014}
1015
1017{
1018 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1019 return offsetBleed;
1020}
1021
1023{
1024 return true;
1025}
1026
1028{
1029 mOffsetUnit = unit;
1030}
1031
1033{
1034 return mOffsetUnit;
1035}
1036
1038{
1040}
1041
1043{
1044 mOffsetMapUnitScale = scale;
1045}
1046
1048{
1049 return mOffsetMapUnitScale;
1050}
1051
1052//QgsShapeburstFillSymbolLayer
1053
1055 int blurRadius, bool useWholeShape, double maxDistance )
1056 : mBlurRadius( blurRadius )
1057 , mUseWholeShape( useWholeShape )
1058 , mMaxDistance( maxDistance )
1059 , mColorType( colorType )
1060 , mColor2( color2 )
1061{
1062 mColor = color;
1063}
1064
1066
1068{
1069 //default to a two-color gradient
1071 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
1072 int blurRadius = 0;
1073 bool useWholeShape = true;
1074 double maxDistance = 5;
1075 QPointF offset;
1076
1077 //update fill properties from props
1078 if ( props.contains( QStringLiteral( "color_type" ) ) )
1079 {
1080 colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
1081 }
1082 if ( props.contains( QStringLiteral( "shapeburst_color" ) ) )
1083 {
1084 //pre 2.5 projects used "shapeburst_color"
1085 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color" )].toString() );
1086 }
1087 else if ( props.contains( QStringLiteral( "color" ) ) )
1088 {
1089 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
1090 }
1091
1092 if ( props.contains( QStringLiteral( "shapeburst_color2" ) ) )
1093 {
1094 //pre 2.5 projects used "shapeburst_color2"
1095 color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color2" )].toString() );
1096 }
1097 else if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
1098 {
1099 color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
1100 }
1101 if ( props.contains( QStringLiteral( "blur_radius" ) ) )
1102 {
1103 blurRadius = props[QStringLiteral( "blur_radius" )].toInt();
1104 }
1105 if ( props.contains( QStringLiteral( "use_whole_shape" ) ) )
1106 {
1107 useWholeShape = props[QStringLiteral( "use_whole_shape" )].toInt();
1108 }
1109 if ( props.contains( QStringLiteral( "max_distance" ) ) )
1110 {
1111 maxDistance = props[QStringLiteral( "max_distance" )].toDouble();
1112 }
1113 if ( props.contains( QStringLiteral( "offset" ) ) )
1114 {
1115 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
1116 }
1117
1118 //attempt to create color ramp from props
1119 QgsColorRamp *gradientRamp = nullptr;
1120 if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
1121 {
1122 gradientRamp = QgsCptCityColorRamp::create( props );
1123 }
1124 else
1125 {
1126 gradientRamp = QgsGradientColorRamp::create( props );
1127 }
1128
1129 //create a new shapeburst fill layer with desired properties
1130 std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( color, color2, colorType, blurRadius, useWholeShape, maxDistance );
1131 sl->setOffset( offset );
1132 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1133 {
1134 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1135 }
1136 if ( props.contains( QStringLiteral( "distance_unit" ) ) )
1137 {
1138 sl->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "distance_unit" )].toString() ) );
1139 }
1140 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1141 {
1142 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1143 }
1144 if ( props.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
1145 {
1146 sl->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
1147 }
1148 if ( props.contains( QStringLiteral( "ignore_rings" ) ) )
1149 {
1150 sl->setIgnoreRings( props[QStringLiteral( "ignore_rings" )].toInt() );
1151 }
1152 if ( gradientRamp )
1153 {
1154 sl->setColorRamp( gradientRamp );
1155 }
1156
1157 sl->restoreOldDataDefinedProperties( props );
1158
1159 return sl.release();
1160}
1161
1163{
1164 return QStringLiteral( "ShapeburstFill" );
1165}
1166
1168{
1169 if ( mGradientRamp.get() == ramp )
1170 return;
1171
1172 mGradientRamp.reset( ramp );
1173}
1174
1175void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
1176 double &maxDistance, bool &ignoreRings )
1177{
1178 //first gradient color
1179 color = mColor;
1181 {
1184 }
1185
1186 //second gradient color
1187 color2 = mColor2;
1189 {
1192 }
1193
1194 //blur radius
1195 blurRadius = mBlurRadius;
1197 {
1198 context.setOriginalValueVariable( mBlurRadius );
1200 }
1201
1202 //use whole shape
1203 useWholeShape = mUseWholeShape;
1205 {
1206 context.setOriginalValueVariable( mUseWholeShape );
1208 }
1209
1210 //max distance
1211 maxDistance = mMaxDistance;
1213 {
1214 context.setOriginalValueVariable( mMaxDistance );
1216 }
1217
1218 //ignore rings
1219 ignoreRings = mIgnoreRings;
1221 {
1222 context.setOriginalValueVariable( mIgnoreRings );
1224 }
1225
1226}
1227
1229{
1230 //TODO - check this
1231 QColor selColor = context.renderContext().selectionColor();
1232 if ( ! SELECTION_IS_OPAQUE )
1233 selColor.setAlphaF( context.opacity() );
1234 mSelBrush = QBrush( selColor );
1235}
1236
1238{
1239 Q_UNUSED( context )
1240}
1241
1242void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1243{
1244 QPainter *p = context.renderContext().painter();
1245 if ( !p )
1246 {
1247 return;
1248 }
1249
1250 if ( context.selected() )
1251 {
1252 //feature is selected, draw using selection style
1253 p->setBrush( mSelBrush );
1254 QPointF offset = mOffset;
1255
1257 {
1259 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1260 bool ok = false;
1261 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1262 if ( ok )
1263 offset = res;
1264 }
1265
1266 if ( !offset.isNull() )
1267 {
1268 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1269 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1270 p->translate( offset );
1271 }
1272 _renderPolygon( p, points, rings, context );
1273 if ( !offset.isNull() )
1274 {
1275 p->translate( -offset );
1276 }
1277 return;
1278 }
1279
1280 QColor color1, color2;
1281 int blurRadius;
1282 bool useWholeShape;
1283 double maxDistance;
1284 bool ignoreRings;
1285 //calculate data defined symbology
1286 applyDataDefinedSymbology( context, color1, color2, blurRadius, useWholeShape, maxDistance, ignoreRings );
1287
1288 //calculate max distance for shapeburst fill to extend from polygon boundary, in pixels
1289 int outputPixelMaxDist = 0;
1290 if ( !useWholeShape && !qgsDoubleNear( maxDistance, 0.0 ) )
1291 {
1292 //convert max distance to pixels
1293 outputPixelMaxDist = static_cast< int >( std::round( context.renderContext().convertToPainterUnits( maxDistance, mDistanceUnit, mDistanceMapUnitScale ) ) );
1294 }
1295
1296 //if we are using the two color mode, create a gradient ramp
1297 std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
1299 {
1300 twoColorGradientRamp = std::make_unique< QgsGradientColorRamp >( color1, color2 );
1301 }
1302
1303 //no stroke for shapeburst fills
1304 p->setPen( QPen( Qt::NoPen ) );
1305
1306 //calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
1307 int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
1308 //create a QImage to draw shapeburst in
1309 int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1310 int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1311 int imWidth = pointsWidth + ( sideBuffer * 2 );
1312 int imHeight = pointsHeight + ( sideBuffer * 2 );
1313
1314 // these are all potentially very expensive operations, so check regularly if the job is canceled and abort responsively
1315 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1316 return;
1317
1318 std::unique_ptr< QImage > fillImage = std::make_unique< QImage >( imWidth,
1319 imHeight, QImage::Format_ARGB32_Premultiplied );
1320 if ( fillImage->isNull() )
1321 {
1322 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1323 return;
1324 }
1325
1326 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1327 return;
1328
1329 //also create an image to store the alpha channel
1330 std::unique_ptr< QImage > alphaImage = std::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1331 if ( alphaImage->isNull() )
1332 {
1333 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1334 return;
1335 }
1336
1337 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1338 return;
1339
1340 //Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
1341 //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
1342 //polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
1343 fillImage->fill( Qt::black );
1344
1345 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1346 return;
1347
1348 //initially fill the alpha channel image with a transparent color
1349 alphaImage->fill( Qt::transparent );
1350
1351 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1352 return;
1353
1354 //now, draw the polygon in the alpha channel image
1355 QPainter imgPainter;
1356 imgPainter.begin( alphaImage.get() );
1357 imgPainter.setRenderHint( QPainter::Antialiasing, true );
1358 imgPainter.setBrush( QBrush( Qt::white ) );
1359 imgPainter.setPen( QPen( Qt::black ) );
1360 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1361 _renderPolygon( &imgPainter, points, rings, context );
1362 imgPainter.end();
1363
1364 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1365 return;
1366
1367 //now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
1368 //(this avoids calling _renderPolygon twice, since that can be slow)
1369 imgPainter.begin( fillImage.get() );
1370 if ( !ignoreRings )
1371 {
1372 imgPainter.drawImage( 0, 0, *alphaImage );
1373 }
1374 else
1375 {
1376 //using ignore rings mode, so the alpha image can't be used
1377 //directly as the alpha channel contains polygon rings and we need
1378 //to draw now without any rings
1379 imgPainter.setBrush( QBrush( Qt::white ) );
1380 imgPainter.setPen( QPen( Qt::black ) );
1381 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1382 _renderPolygon( &imgPainter, points, nullptr, context );
1383 }
1384 imgPainter.end();
1385
1386 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1387 return;
1388
1389 //apply distance transform to image, uses the current color ramp to calculate final pixel colors
1390 double *dtArray = distanceTransform( fillImage.get(), context.renderContext() );
1391
1392 //copy distance transform values back to QImage, shading by appropriate color ramp
1393 dtArrayToQImage( dtArray, fillImage.get(), mColorType == Qgis::GradientColorSource::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
1394 context.renderContext(), useWholeShape, outputPixelMaxDist );
1395 if ( context.opacity() < 1 )
1396 {
1397 QgsImageOperation::multiplyOpacity( *fillImage, context.opacity(), context.renderContext().feedback() );
1398 }
1399
1400 //clean up some variables
1401 delete [] dtArray;
1402
1403 //apply blur if desired
1404 if ( blurRadius > 0 )
1405 {
1406 QgsImageOperation::stackBlur( *fillImage, blurRadius, false, context.renderContext().feedback() );
1407 }
1408
1409 //apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1410 imgPainter.begin( fillImage.get() );
1411 imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
1412 imgPainter.drawImage( 0, 0, *alphaImage );
1413 imgPainter.end();
1414 //we're finished with the alpha channel image now
1415 alphaImage.reset();
1416
1417 //draw shapeburst image in correct place in the destination painter
1418
1419 QgsScopedQPainterState painterState( p );
1420 QPointF offset = mOffset;
1422 {
1424 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1425 bool ok = false;
1426 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1427 if ( ok )
1428 offset = res;
1429 }
1430 if ( !offset.isNull() )
1431 {
1432 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1433 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1434 p->translate( offset );
1435 }
1436
1437 p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
1438
1439 if ( !offset.isNull() )
1440 {
1441 p->translate( -offset );
1442 }
1443}
1444
1445//fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
1446
1447/* distance transform of a 1d function using squared distance */
1448void QgsShapeburstFillSymbolLayer::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
1449{
1450 int k = 0;
1451 v[0] = 0;
1452 z[0] = -INF;
1453 z[1] = + INF;
1454 for ( int q = 1; q <= n - 1; q++ )
1455 {
1456 double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1457 while ( s <= z[k] )
1458 {
1459 k--;
1460 s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1461 }
1462 k++;
1463 v[k] = q;
1464 z[k] = s;
1465 z[k + 1] = + INF;
1466 }
1467
1468 k = 0;
1469 for ( int q = 0; q <= n - 1; q++ )
1470 {
1471 while ( z[k + 1] < q )
1472 k++;
1473 d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
1474 }
1475}
1476
1477/* distance transform of 2d function using squared distance */
1478void QgsShapeburstFillSymbolLayer::distanceTransform2d( double *im, int width, int height, QgsRenderContext &context )
1479{
1480 int maxDimension = std::max( width, height );
1481 double *f = new double[ maxDimension ];
1482 int *v = new int[ maxDimension ];
1483 double *z = new double[ maxDimension + 1 ];
1484 double *d = new double[ maxDimension ];
1485
1486 // transform along columns
1487 for ( int x = 0; x < width; x++ )
1488 {
1489 if ( context.renderingStopped() )
1490 break;
1491
1492 for ( int y = 0; y < height; y++ )
1493 {
1494 f[y] = im[ x + y * width ];
1495 }
1496 distanceTransform1d( f, height, v, z, d );
1497 for ( int y = 0; y < height; y++ )
1498 {
1499 im[ x + y * width ] = d[y];
1500 }
1501 }
1502
1503 // transform along rows
1504 for ( int y = 0; y < height; y++ )
1505 {
1506 if ( context.renderingStopped() )
1507 break;
1508
1509 for ( int x = 0; x < width; x++ )
1510 {
1511 f[x] = im[ x + y * width ];
1512 }
1513 distanceTransform1d( f, width, v, z, d );
1514 for ( int x = 0; x < width; x++ )
1515 {
1516 im[ x + y * width ] = d[x];
1517 }
1518 }
1519
1520 delete [] d;
1521 delete [] f;
1522 delete [] v;
1523 delete [] z;
1524}
1525
1526/* distance transform of a binary QImage */
1527double *QgsShapeburstFillSymbolLayer::distanceTransform( QImage *im, QgsRenderContext &context )
1528{
1529 int width = im->width();
1530 int height = im->height();
1531
1532 double *dtArray = new double[width * height];
1533
1534 //load qImage to array
1535 QRgb tmpRgb;
1536 int idx = 0;
1537 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1538 {
1539 if ( context.renderingStopped() )
1540 break;
1541
1542 const QRgb *scanLine = reinterpret_cast< const QRgb * >( im->constScanLine( heightIndex ) );
1543 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1544 {
1545 tmpRgb = scanLine[widthIndex];
1546 if ( qRed( tmpRgb ) == 0 )
1547 {
1548 //black pixel, so zero distance
1549 dtArray[ idx ] = 0;
1550 }
1551 else
1552 {
1553 //white pixel, so initially set distance as infinite
1554 dtArray[ idx ] = INF;
1555 }
1556 idx++;
1557 }
1558 }
1559
1560 //calculate squared distance transform
1561 distanceTransform2d( dtArray, width, height, context );
1562
1563 return dtArray;
1564}
1565
1566void QgsShapeburstFillSymbolLayer::dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, QgsRenderContext &context, bool useWholeShape, int maxPixelDistance )
1567{
1568 int width = im->width();
1569 int height = im->height();
1570
1571 //find maximum distance value
1572 double maxDistanceValue;
1573
1574 if ( useWholeShape )
1575 {
1576 //no max distance specified in symbol properties, so calculate from maximum value in distance transform results
1577 double dtMaxValue = array[0];
1578 for ( int i = 1; i < ( width * height ); ++i )
1579 {
1580 if ( array[i] > dtMaxValue )
1581 {
1582 dtMaxValue = array[i];
1583 }
1584 }
1585
1586 //values in distance transform are squared
1587 maxDistanceValue = std::sqrt( dtMaxValue );
1588 }
1589 else
1590 {
1591 //use max distance set in symbol properties
1592 maxDistanceValue = maxPixelDistance;
1593 }
1594
1595 //update the pixels in the provided QImage
1596 int idx = 0;
1597 double squaredVal = 0;
1598 double pixVal = 0;
1599
1600 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1601 {
1602 if ( context.renderingStopped() )
1603 break;
1604
1605 QRgb *scanLine = reinterpret_cast< QRgb * >( im->scanLine( heightIndex ) );
1606 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1607 {
1608 //result of distance transform
1609 squaredVal = array[idx];
1610
1611 //scale result to fit in the range [0, 1]
1612 if ( maxDistanceValue > 0 )
1613 {
1614 pixVal = squaredVal > 0 ? std::min( ( std::sqrt( squaredVal ) / maxDistanceValue ), 1.0 ) : 0;
1615 }
1616 else
1617 {
1618 pixVal = 1.0;
1619 }
1620
1621 //convert value to color from ramp
1622 //premultiply ramp color since we are storing this in a ARGB32_Premultiplied QImage
1623 scanLine[widthIndex] = qPremultiply( ramp->color( pixVal ).rgba() );
1624 idx++;
1625 }
1626 }
1627}
1628
1630{
1631 QVariantMap map;
1632 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
1633 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
1634 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mColorType ) );
1635 map[QStringLiteral( "blur_radius" )] = QString::number( mBlurRadius );
1636 map[QStringLiteral( "use_whole_shape" )] = QString::number( mUseWholeShape );
1637 map[QStringLiteral( "max_distance" )] = QString::number( mMaxDistance );
1638 map[QStringLiteral( "distance_unit" )] = QgsUnitTypes::encodeUnit( mDistanceUnit );
1639 map[QStringLiteral( "distance_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale );
1640 map[QStringLiteral( "ignore_rings" )] = QString::number( mIgnoreRings );
1641 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1642 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1643 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1644 if ( mGradientRamp )
1645 {
1646#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1647 map.unite( mGradientRamp->properties() );
1648#else
1649 map.insert( mGradientRamp->properties() );
1650#endif
1651 }
1652
1653 return map;
1654}
1655
1657{
1658 std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( mColor, mColor2, mColorType, mBlurRadius, mUseWholeShape, mMaxDistance );
1659 if ( mGradientRamp )
1660 {
1661 sl->setColorRamp( mGradientRamp->clone() );
1662 }
1663 sl->setDistanceUnit( mDistanceUnit );
1664 sl->setDistanceMapUnitScale( mDistanceMapUnitScale );
1665 sl->setIgnoreRings( mIgnoreRings );
1666 sl->setOffset( mOffset );
1667 sl->setOffsetUnit( mOffsetUnit );
1668 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1669 copyDataDefinedProperties( sl.get() );
1670 copyPaintEffect( sl.get() );
1671 return sl.release();
1672}
1673
1675{
1676 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1677 return offsetBleed;
1678}
1679
1681{
1682 return true;
1683}
1684
1686{
1687 mDistanceUnit = unit;
1688 mOffsetUnit = unit;
1689}
1690
1692{
1693 if ( mDistanceUnit == mOffsetUnit )
1694 {
1695 return mDistanceUnit;
1696 }
1698}
1699
1701{
1702 return mDistanceUnit == QgsUnitTypes::RenderMapUnits || mDistanceUnit == QgsUnitTypes::RenderMetersInMapUnits
1703 || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
1704}
1705
1707{
1708 mDistanceMapUnitScale = scale;
1709 mOffsetMapUnitScale = scale;
1710}
1711
1713{
1714 if ( mDistanceMapUnitScale == mOffsetMapUnitScale )
1715 {
1716 return mDistanceMapUnitScale;
1717 }
1718 return QgsMapUnitScale();
1719}
1720
1721
1722//QgsImageFillSymbolLayer
1723
1725{
1726}
1727
1729
1730void QgsImageFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1731{
1732 QPainter *p = context.renderContext().painter();
1733 if ( !p )
1734 {
1735 return;
1736 }
1737
1739 applyDataDefinedSettings( context );
1740
1741 p->setPen( QPen( Qt::NoPen ) );
1742
1743 QTransform bkTransform = mBrush.transform();
1744 if ( applyBrushTransformFromContext( &context ) && !context.renderContext().textureOrigin().isNull() )
1745 {
1746 QPointF leftCorner = context.renderContext().textureOrigin();
1747 QTransform t = mBrush.transform();
1748 t.translate( leftCorner.x(), leftCorner.y() );
1749 mBrush.setTransform( t );
1750 }
1751 else
1752 {
1753 QTransform t = mBrush.transform();
1754 t.translate( 0, 0 );
1755 mBrush.setTransform( t );
1756 }
1757
1758 if ( context.selected() )
1759 {
1760 QColor selColor = context.renderContext().selectionColor();
1761 p->setBrush( QBrush( selColor ) );
1762 _renderPolygon( p, points, rings, context );
1763 }
1764
1765 if ( !qgsDoubleNear( mNextAngle, 0.0 ) )
1766 {
1767 QTransform t = mBrush.transform();
1768 t.rotate( mNextAngle );
1769 mBrush.setTransform( t );
1770 }
1771 p->setBrush( mBrush );
1772 _renderPolygon( p, points, rings, context );
1773
1774 mBrush.setTransform( bkTransform );
1775}
1776
1778{
1779 mStrokeWidthUnit = unit;
1780}
1781
1783{
1784 return mStrokeWidthUnit;
1785}
1786
1788{
1790}
1791
1793{
1795}
1796
1798{
1799 double width = mStrokeWidth;
1801 {
1804 }
1806}
1807
1809{
1810 return Qt::SolidLine;
1811#if 0
1812 if ( !mStroke )
1813 {
1814 return Qt::SolidLine;
1815 }
1816 else
1817 {
1818 return mStroke->dxfPenStyle();
1819 }
1820#endif //0
1821}
1822
1824{
1825 QVariantMap map;
1826 map.insert( QStringLiteral( "coordinate_reference" ), QgsSymbolLayerUtils::encodeCoordinateReference( mCoordinateReference ) );
1827 return map;
1828}
1829
1831{
1832 //coordinate reference
1835 {
1836 bool ok = false;
1838 if ( ok )
1839 {
1841 if ( !ok )
1843 }
1844 }
1845
1847}
1848
1849
1850//QgsSVGFillSymbolLayer
1851
1852QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString &svgFilePath, double width, double angle )
1854 , mPatternWidth( width )
1855{
1856 mStrokeWidth = 0.3;
1857 mAngle = angle;
1858 mColor = QColor( 255, 255, 255 );
1860}
1861
1862QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray &svgData, double width, double angle )
1864 , mPatternWidth( width )
1865 , mSvgData( svgData )
1866{
1867 storeViewBox();
1868 mStrokeWidth = 0.3;
1869 mAngle = angle;
1870 mColor = QColor( 255, 255, 255 );
1871 setDefaultSvgParams();
1872}
1873
1875
1877{
1879 mPatternWidthUnit = unit;
1880 mSvgStrokeWidthUnit = unit;
1881 mStrokeWidthUnit = unit;
1882 if ( mStroke )
1883 mStroke->setOutputUnit( unit );
1884}
1885
1887{
1889 if ( mPatternWidthUnit != unit || mSvgStrokeWidthUnit != unit || mStrokeWidthUnit != unit )
1890 {
1892 }
1893 return unit;
1894}
1895
1897{
1899 mPatternWidthMapUnitScale = scale;
1900 mSvgStrokeWidthMapUnitScale = scale;
1901}
1902
1904{
1905 if ( QgsImageFillSymbolLayer::mapUnitScale() == mPatternWidthMapUnitScale &&
1906 mPatternWidthMapUnitScale == mSvgStrokeWidthMapUnitScale &&
1907 mSvgStrokeWidthMapUnitScale == mStrokeWidthMapUnitScale )
1908 {
1909 return mPatternWidthMapUnitScale;
1910 }
1911 return QgsMapUnitScale();
1912}
1913
1914void QgsSVGFillSymbolLayer::setSvgFilePath( const QString &svgPath )
1915{
1916 mSvgData = QgsApplication::svgCache()->getImageData( svgPath );
1917 storeViewBox();
1918
1919 mSvgFilePath = svgPath;
1920 setDefaultSvgParams();
1921}
1922
1923QgsSymbolLayer *QgsSVGFillSymbolLayer::create( const QVariantMap &properties )
1924{
1925 QByteArray data;
1926 double width = 20;
1927 QString svgFilePath;
1928 double angle = 0.0;
1929
1930 if ( properties.contains( QStringLiteral( "width" ) ) )
1931 {
1932 width = properties[QStringLiteral( "width" )].toDouble();
1933 }
1934 if ( properties.contains( QStringLiteral( "svgFile" ) ) )
1935 {
1936 svgFilePath = properties[QStringLiteral( "svgFile" )].toString();
1937 }
1938 if ( properties.contains( QStringLiteral( "angle" ) ) )
1939 {
1940 angle = properties[QStringLiteral( "angle" )].toDouble();
1941 }
1942
1943 std::unique_ptr< QgsSVGFillSymbolLayer > symbolLayer;
1944 if ( !svgFilePath.isEmpty() )
1945 {
1946 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( svgFilePath, width, angle );
1947 }
1948 else
1949 {
1950 if ( properties.contains( QStringLiteral( "data" ) ) )
1951 {
1952 data = QByteArray::fromHex( properties[QStringLiteral( "data" )].toString().toLocal8Bit() );
1953 }
1954 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( data, width, angle );
1955 }
1956
1957 //svg parameters
1958 if ( properties.contains( QStringLiteral( "svgFillColor" ) ) )
1959 {
1960 //pre 2.5 projects used "svgFillColor"
1961 symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgFillColor" )].toString() ) );
1962 }
1963 else if ( properties.contains( QStringLiteral( "color" ) ) )
1964 {
1965 symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
1966 }
1967 if ( properties.contains( QStringLiteral( "svgOutlineColor" ) ) )
1968 {
1969 //pre 2.5 projects used "svgOutlineColor"
1970 symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgOutlineColor" )].toString() ) );
1971 }
1972 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
1973 {
1974 symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() ) );
1975 }
1976 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
1977 {
1978 symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() ) );
1979 }
1980 if ( properties.contains( QStringLiteral( "svgOutlineWidth" ) ) )
1981 {
1982 //pre 2.5 projects used "svgOutlineWidth"
1983 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "svgOutlineWidth" )].toDouble() );
1984 }
1985 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
1986 {
1987 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "outline_width" )].toDouble() );
1988 }
1989 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
1990 {
1991 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "line_width" )].toDouble() );
1992 }
1993
1994 //units
1995 if ( properties.contains( QStringLiteral( "pattern_width_unit" ) ) )
1996 {
1997 symbolLayer->setPatternWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "pattern_width_unit" )].toString() ) );
1998 }
1999 if ( properties.contains( QStringLiteral( "pattern_width_map_unit_scale" ) ) )
2000 {
2001 symbolLayer->setPatternWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "pattern_width_map_unit_scale" )].toString() ) );
2002 }
2003 if ( properties.contains( QStringLiteral( "svg_outline_width_unit" ) ) )
2004 {
2005 symbolLayer->setSvgStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "svg_outline_width_unit" )].toString() ) );
2006 }
2007 if ( properties.contains( QStringLiteral( "svg_outline_width_map_unit_scale" ) ) )
2008 {
2009 symbolLayer->setSvgStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "svg_outline_width_map_unit_scale" )].toString() ) );
2010 }
2011 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2012 {
2013 symbolLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2014 }
2015 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2016 {
2017 symbolLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2018 }
2019
2020 if ( properties.contains( QStringLiteral( "parameters" ) ) )
2021 {
2022 const QVariantMap parameters = properties[QStringLiteral( "parameters" )].toMap();
2023 symbolLayer->setParameters( QgsProperty::variantMapToPropertyMap( parameters ) );
2024 }
2025
2026 symbolLayer->restoreOldDataDefinedProperties( properties );
2027
2028 return symbolLayer.release();
2029}
2030
2031void QgsSVGFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2032{
2033 QVariantMap::iterator it = properties.find( QStringLiteral( "svgFile" ) );
2034 if ( it != properties.end() )
2035 {
2036 if ( saving )
2037 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2038 else
2039 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2040 }
2041}
2042
2044{
2045 return QStringLiteral( "SVGFill" );
2046}
2047
2048void QgsSVGFillSymbolLayer::applyPattern( QBrush &brush, const QString &svgFilePath, double patternWidth, QgsUnitTypes::RenderUnit patternWidthUnit,
2049 const QColor &svgFillColor, const QColor &svgStrokeColor, double svgStrokeWidth,
2050 QgsUnitTypes::RenderUnit svgStrokeWidthUnit, const QgsSymbolRenderContext &context,
2051 const QgsMapUnitScale &patternWidthMapUnitScale, const QgsMapUnitScale &svgStrokeWidthMapUnitScale, const QgsStringMap svgParameters )
2052{
2053 if ( mSvgViewBox.isNull() )
2054 {
2055 return;
2056 }
2057
2059
2060 if ( static_cast< int >( size ) < 1.0 || 10000.0 < size )
2061 {
2062 brush.setTextureImage( QImage() );
2063 }
2064 else
2065 {
2066 bool fitsInCache = true;
2068 QImage patternImage = QgsApplication::svgCache()->svgAsImage( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2069 context.renderContext().scaleFactor(), fitsInCache, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), svgParameters );
2070 if ( !fitsInCache )
2071 {
2072 QPicture patternPict = QgsApplication::svgCache()->svgAsPicture( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2073 context.renderContext().scaleFactor(), false, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
2074 double hwRatio = 1.0;
2075 if ( patternPict.width() > 0 )
2076 {
2077 hwRatio = static_cast< double >( patternPict.height() ) / static_cast< double >( patternPict.width() );
2078 }
2079 patternImage = QImage( static_cast< int >( size ), static_cast< int >( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
2080 patternImage.fill( 0 ); // transparent background
2081
2082 QPainter p( &patternImage );
2083 p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
2084 }
2085
2086 QTransform brushTransform;
2087 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2088 {
2089 QImage transparentImage = patternImage.copy();
2090 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2091 brush.setTextureImage( transparentImage );
2092 }
2093 else
2094 {
2095 brush.setTextureImage( patternImage );
2096 }
2097 brush.setTransform( brushTransform );
2098 }
2099}
2100
2102{
2103 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2104
2105 applyPattern( mBrush, mSvgFilePath, mPatternWidth, mPatternWidthUnit, mColor, mSvgStrokeColor, mSvgStrokeWidth, mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2106
2107 if ( mStroke )
2108 {
2109 mStroke->startRender( context.renderContext(), context.fields() );
2110 }
2111}
2112
2114{
2115 if ( mStroke )
2116 {
2117 mStroke->stopRender( context.renderContext() );
2118 }
2119}
2120
2121void QgsSVGFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
2122{
2123 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
2124
2125 if ( mStroke )
2126 {
2127 mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
2128 if ( rings )
2129 {
2130 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
2131 {
2132 mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
2133 }
2134 }
2135 }
2136}
2137
2139{
2140 QVariantMap map;
2141 if ( !mSvgFilePath.isEmpty() )
2142 {
2143 map.insert( QStringLiteral( "svgFile" ), mSvgFilePath );
2144 }
2145 else
2146 {
2147 map.insert( QStringLiteral( "data" ), QString( mSvgData.toHex() ) );
2148 }
2149
2150 map.insert( QStringLiteral( "width" ), QString::number( mPatternWidth ) );
2151 map.insert( QStringLiteral( "angle" ), QString::number( mAngle ) );
2152
2153 //svg parameters
2154 map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
2155 map.insert( QStringLiteral( "outline_color" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2156 map.insert( QStringLiteral( "outline_width" ), QString::number( mSvgStrokeWidth ) );
2157
2158 //units
2159 map.insert( QStringLiteral( "pattern_width_unit" ), QgsUnitTypes::encodeUnit( mPatternWidthUnit ) );
2160 map.insert( QStringLiteral( "pattern_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mPatternWidthMapUnitScale ) );
2161 map.insert( QStringLiteral( "svg_outline_width_unit" ), QgsUnitTypes::encodeUnit( mSvgStrokeWidthUnit ) );
2162 map.insert( QStringLiteral( "svg_outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mSvgStrokeWidthMapUnitScale ) );
2163 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
2164 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
2165
2166 map[QStringLiteral( "parameters" )] = QgsProperty::propertyMapToVariantMap( mParameters );
2167
2168 return map;
2169}
2170
2172{
2173 std::unique_ptr< QgsSVGFillSymbolLayer > clonedLayer;
2174 if ( !mSvgFilePath.isEmpty() )
2175 {
2176 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgFilePath, mPatternWidth, mAngle );
2177 clonedLayer->setSvgFillColor( mColor );
2178 clonedLayer->setSvgStrokeColor( mSvgStrokeColor );
2179 clonedLayer->setSvgStrokeWidth( mSvgStrokeWidth );
2180 }
2181 else
2182 {
2183 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgData, mPatternWidth, mAngle );
2184 }
2185
2186 clonedLayer->setPatternWidthUnit( mPatternWidthUnit );
2187 clonedLayer->setPatternWidthMapUnitScale( mPatternWidthMapUnitScale );
2188 clonedLayer->setSvgStrokeWidthUnit( mSvgStrokeWidthUnit );
2189 clonedLayer->setSvgStrokeWidthMapUnitScale( mSvgStrokeWidthMapUnitScale );
2190 clonedLayer->setStrokeWidthUnit( mStrokeWidthUnit );
2191 clonedLayer->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
2192
2193 clonedLayer->setParameters( mParameters );
2194
2195 if ( mStroke )
2196 {
2197 clonedLayer->setSubSymbol( mStroke->clone() );
2198 }
2199 copyDataDefinedProperties( clonedLayer.get() );
2200 copyPaintEffect( clonedLayer.get() );
2201 return clonedLayer.release();
2202}
2203
2204void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2205{
2206 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
2207 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2208 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2209 element.appendChild( symbolizerElem );
2210
2211 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2212
2213 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
2214 symbolizerElem.appendChild( fillElem );
2215
2216 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
2217 fillElem.appendChild( graphicFillElem );
2218
2219 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2220 graphicFillElem.appendChild( graphicElem );
2221
2222 if ( !mSvgFilePath.isEmpty() )
2223 {
2224 // encode a parametric SVG reference
2225 double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
2226 double strokeWidth = QgsSymbolLayerUtils::rescaleUom( mSvgStrokeWidth, mSvgStrokeWidthUnit, props );
2227 QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgStrokeColor, strokeWidth );
2228 }
2229 else
2230 {
2231 // TODO: create svg from data
2232 // <se:InlineContent>
2233 symbolizerElem.appendChild( doc.createComment( QStringLiteral( "SVG from data not implemented yet" ) ) );
2234 }
2235
2236 // <Rotation>
2237 QString angleFunc;
2238 bool ok;
2239 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
2240 if ( !ok )
2241 {
2242 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
2243 }
2244 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2245 {
2246 angleFunc = QString::number( angle + mAngle );
2247 }
2248 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
2249
2250 if ( mStroke )
2251 {
2252 // the stroke sub symbol should be stored within the Stroke element,
2253 // but it will be stored in a separated LineSymbolizer because it could
2254 // have more than one layer
2255 mStroke->toSld( doc, element, props );
2256 }
2257}
2258
2260{
2261 return mPatternWidthUnit == QgsUnitTypes::RenderMapUnits || mPatternWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
2262 || mSvgStrokeWidthUnit == QgsUnitTypes::RenderMapUnits || mSvgStrokeWidthUnit == QgsUnitTypes::RenderMetersInMapUnits;
2263}
2264
2266{
2267 return mStroke.get();
2268}
2269
2271{
2272 if ( !symbol ) //unset current stroke
2273 {
2274 mStroke.reset( nullptr );
2275 return true;
2276 }
2277
2278 if ( symbol->type() != Qgis::SymbolType::Line )
2279 {
2280 delete symbol;
2281 return false;
2282 }
2283
2284 QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
2285 if ( lineSymbol )
2286 {
2287 mStroke.reset( lineSymbol );
2288 return true;
2289 }
2290
2291 delete symbol;
2292 return false;
2293}
2294
2296{
2297 if ( mStroke && mStroke->symbolLayer( 0 ) )
2298 {
2299 double subLayerBleed = mStroke->symbolLayer( 0 )->estimateMaxBleed( context );
2300 return subLayerBleed;
2301 }
2302 return 0;
2303}
2304
2306{
2307 Q_UNUSED( context )
2308 if ( !mStroke )
2309 {
2310 return QColor( Qt::black );
2311 }
2312 return mStroke->color();
2313}
2314
2315QSet<QString> QgsSVGFillSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2316{
2317 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2318 if ( mStroke )
2319 attr.unite( mStroke->usedAttributes( context ) );
2320 return attr;
2321}
2322
2324{
2326 return true;
2327 if ( mStroke && mStroke->hasDataDefinedProperties() )
2328 return true;
2329 return false;
2330}
2331
2333{
2334 QString path, mimeType;
2335 QColor fillColor, strokeColor;
2336 Qt::PenStyle penStyle;
2337 double size, strokeWidth;
2338
2339 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
2340 if ( fillElem.isNull() )
2341 return nullptr;
2342
2343 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
2344 if ( graphicFillElem.isNull() )
2345 return nullptr;
2346
2347 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
2348 if ( graphicElem.isNull() )
2349 return nullptr;
2350
2351 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
2352 return nullptr;
2353
2354 if ( mimeType != QLatin1String( "image/svg+xml" ) )
2355 return nullptr;
2356
2357 QgsSymbolLayerUtils::lineFromSld( graphicElem, penStyle, strokeColor, strokeWidth );
2358
2359 double scaleFactor = 1.0;
2360 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2361 QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2362 size = size * scaleFactor;
2363 strokeWidth = strokeWidth * scaleFactor;
2364
2365 double angle = 0.0;
2366 QString angleFunc;
2367 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2368 {
2369 bool ok;
2370 double d = angleFunc.toDouble( &ok );
2371 if ( ok )
2372 angle = d;
2373 }
2374
2375 std::unique_ptr< QgsSVGFillSymbolLayer > sl = std::make_unique< QgsSVGFillSymbolLayer >( path, size, angle );
2376 sl->setOutputUnit( sldUnitSize );
2377 sl->setSvgFillColor( fillColor );
2378 sl->setSvgStrokeColor( strokeColor );
2379 sl->setSvgStrokeWidth( strokeWidth );
2380
2381 // try to get the stroke
2382 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2383 if ( !strokeElem.isNull() )
2384 {
2386 if ( l )
2387 {
2388 QgsSymbolLayerList layers;
2389 layers.append( l );
2390 sl->setSubSymbol( new QgsLineSymbol( layers ) );
2391 }
2392 }
2393
2394 return sl.release();
2395}
2396
2398{
2402 {
2403 return; //no data defined settings
2404 }
2405
2407 {
2410 }
2411
2412 double width = mPatternWidth;
2414 {
2415 context.setOriginalValueVariable( mPatternWidth );
2417 }
2418 QString svgFile = mSvgFilePath;
2420 {
2421 context.setOriginalValueVariable( mSvgFilePath );
2423 context.renderContext().pathResolver() );
2424 }
2425 QColor svgFillColor = mColor;
2427 {
2430 }
2431 QColor svgStrokeColor = mSvgStrokeColor;
2433 {
2434 context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2436 }
2437 double strokeWidth = mSvgStrokeWidth;
2439 {
2440 context.setOriginalValueVariable( mSvgStrokeWidth );
2442 }
2443 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2444
2445 applyPattern( mBrush, svgFile, width, mPatternWidthUnit, svgFillColor, svgStrokeColor, strokeWidth,
2446 mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2447
2448}
2449
2450void QgsSVGFillSymbolLayer::storeViewBox()
2451{
2452 if ( !mSvgData.isEmpty() )
2453 {
2454 QSvgRenderer r( mSvgData );
2455 if ( r.isValid() )
2456 {
2457 mSvgViewBox = r.viewBoxF();
2458 return;
2459 }
2460 }
2461
2462 mSvgViewBox = QRectF();
2463}
2464
2465void QgsSVGFillSymbolLayer::setDefaultSvgParams()
2466{
2467 if ( mSvgFilePath.isEmpty() )
2468 {
2469 return;
2470 }
2471
2472 bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
2473 bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
2474 QColor defaultFillColor, defaultStrokeColor;
2475 double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
2476 QgsApplication::svgCache()->containsParams( mSvgFilePath, hasFillParam, hasDefaultFillColor, defaultFillColor,
2477 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
2478 hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2479 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
2480 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
2481
2482 double newFillOpacity = hasFillOpacityParam ? mColor.alphaF() : 1.0;
2483 double newStrokeOpacity = hasStrokeOpacityParam ? mSvgStrokeColor.alphaF() : 1.0;
2484
2485 if ( hasDefaultFillColor )
2486 {
2487 mColor = defaultFillColor;
2488 mColor.setAlphaF( newFillOpacity );
2489 }
2490 if ( hasDefaultFillOpacity )
2491 {
2492 mColor.setAlphaF( defaultFillOpacity );
2493 }
2494 if ( hasDefaultStrokeColor )
2495 {
2496 mSvgStrokeColor = defaultStrokeColor;
2497 mSvgStrokeColor.setAlphaF( newStrokeOpacity );
2498 }
2499 if ( hasDefaultStrokeOpacity )
2500 {
2501 mSvgStrokeColor.setAlphaF( defaultStrokeOpacity );
2502 }
2503 if ( hasDefaultStrokeWidth )
2504 {
2505 mSvgStrokeWidth = defaultStrokeWidth;
2506 }
2507}
2508
2509void QgsSVGFillSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2510{
2511 mParameters = parameters;
2512}
2513
2514
2517{
2518 setSubSymbol( new QgsLineSymbol() );
2519 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
2520}
2521
2523
2525{
2526 mFillLineSymbol->setWidth( w );
2527 mLineWidth = w;
2528}
2529
2531{
2532 mFillLineSymbol->setColor( c );
2533 mColor = c;
2534}
2535
2537{
2538 return mFillLineSymbol ? mFillLineSymbol->color() : mColor;
2539}
2540
2542{
2543 if ( !symbol )
2544 {
2545 return false;
2546 }
2547
2548 if ( symbol->type() == Qgis::SymbolType::Line )
2549 {
2550 mFillLineSymbol.reset( qgis::down_cast<QgsLineSymbol *>( symbol ) );
2551 return true;
2552 }
2553 delete symbol;
2554 return false;
2555}
2556
2558{
2559 return mFillLineSymbol.get();
2560}
2561
2563{
2564 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2565 if ( mFillLineSymbol )
2566 attr.unite( mFillLineSymbol->usedAttributes( context ) );
2567 return attr;
2568}
2569
2571{
2573 return true;
2574 if ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
2575 return true;
2576 return false;
2577}
2578
2580{
2581 // deliberately don't pass this on to subsymbol here
2582}
2583
2585{
2586 // deliberately don't pass this on to subsymbol here
2587}
2588
2590{
2591 return 0;
2592}
2593
2595{
2597 mDistanceUnit = unit;
2598 mLineWidthUnit = unit;
2599 mOffsetUnit = unit;
2600
2601 if ( mFillLineSymbol )
2602 mFillLineSymbol->setOutputUnit( unit );
2603}
2604
2606{
2608 if ( mDistanceUnit != unit || mLineWidthUnit != unit || ( mOffsetUnit != unit && mOffsetUnit != QgsUnitTypes::RenderPercentage ) )
2609 {
2611 }
2612 return unit;
2613}
2614
2616{
2617 return mDistanceUnit == QgsUnitTypes::RenderMapUnits || mDistanceUnit == QgsUnitTypes::RenderMetersInMapUnits
2618 || mLineWidthUnit == QgsUnitTypes::RenderMapUnits || mLineWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
2619 || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
2620}
2621
2623{
2625 mDistanceMapUnitScale = scale;
2626 mLineWidthMapUnitScale = scale;
2627 mOffsetMapUnitScale = scale;
2628}
2629
2631{
2632 if ( QgsImageFillSymbolLayer::mapUnitScale() == mDistanceMapUnitScale &&
2633 mDistanceMapUnitScale == mLineWidthMapUnitScale &&
2634 mLineWidthMapUnitScale == mOffsetMapUnitScale )
2635 {
2636 return mDistanceMapUnitScale;
2637 }
2638 return QgsMapUnitScale();
2639}
2640
2642{
2643 std::unique_ptr< QgsLinePatternFillSymbolLayer > patternLayer = std::make_unique< QgsLinePatternFillSymbolLayer >();
2644
2645 //default values
2646 double lineAngle = 45;
2647 double distance = 5;
2648 double lineWidth = 0.5;
2649 QColor color( Qt::black );
2650 double offset = 0.0;
2651
2652 if ( properties.contains( QStringLiteral( "lineangle" ) ) )
2653 {
2654 //pre 2.5 projects used "lineangle"
2655 lineAngle = properties[QStringLiteral( "lineangle" )].toDouble();
2656 }
2657 else if ( properties.contains( QStringLiteral( "angle" ) ) )
2658 {
2659 lineAngle = properties[QStringLiteral( "angle" )].toDouble();
2660 }
2661 patternLayer->setLineAngle( lineAngle );
2662
2663 if ( properties.contains( QStringLiteral( "distance" ) ) )
2664 {
2665 distance = properties[QStringLiteral( "distance" )].toDouble();
2666 }
2667 patternLayer->setDistance( distance );
2668
2669 if ( properties.contains( QStringLiteral( "linewidth" ) ) )
2670 {
2671 //pre 2.5 projects used "linewidth"
2672 lineWidth = properties[QStringLiteral( "linewidth" )].toDouble();
2673 }
2674 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2675 {
2676 lineWidth = properties[QStringLiteral( "outline_width" )].toDouble();
2677 }
2678 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2679 {
2680 lineWidth = properties[QStringLiteral( "line_width" )].toDouble();
2681 }
2682 patternLayer->setLineWidth( lineWidth );
2683
2684 if ( properties.contains( QStringLiteral( "color" ) ) )
2685 {
2686 color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() );
2687 }
2688 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2689 {
2690 color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() );
2691 }
2692 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2693 {
2694 color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() );
2695 }
2696 patternLayer->setColor( color );
2697
2698 if ( properties.contains( QStringLiteral( "offset" ) ) )
2699 {
2700 offset = properties[QStringLiteral( "offset" )].toDouble();
2701 }
2702 patternLayer->setOffset( offset );
2703
2704
2705 if ( properties.contains( QStringLiteral( "distance_unit" ) ) )
2706 {
2707 patternLayer->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_unit" )].toString() ) );
2708 }
2709 if ( properties.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
2710 {
2711 patternLayer->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
2712 }
2713 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
2714 {
2715 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
2716 }
2717 else if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2718 {
2719 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2720 }
2721 if ( properties.contains( QStringLiteral( "line_width_map_unit_scale" ) ) )
2722 {
2723 patternLayer->setLineWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "line_width_map_unit_scale" )].toString() ) );
2724 }
2725 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
2726 {
2727 patternLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
2728 }
2729 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
2730 {
2731 patternLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
2732 }
2733 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2734 {
2735 patternLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2736 }
2737 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2738 {
2739 patternLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2740 }
2741 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
2742 {
2743 patternLayer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
2744 }
2745 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
2746 {
2747 patternLayer->setClipMode( QgsSymbolLayerUtils::decodeLineClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
2748 }
2749
2750 patternLayer->restoreOldDataDefinedProperties( properties );
2751
2752 return patternLayer.release();
2753}
2754
2756{
2757 return QStringLiteral( "LinePatternFill" );
2758}
2759
2760void QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double lineAngle, double distance )
2761{
2762 mBrush.setTextureImage( QImage() ); // set empty in case we have to return
2763
2764 if ( !mFillLineSymbol )
2765 {
2766 return;
2767 }
2768 // We have to make a copy because marker intervals will have to be adjusted
2769 std::unique_ptr< QgsLineSymbol > fillLineSymbol( mFillLineSymbol->clone() );
2770 if ( !fillLineSymbol )
2771 {
2772 return;
2773 }
2774
2775 const QgsRenderContext &ctx = context.renderContext();
2776 //double strokePixelWidth = lineWidth * QgsSymbolLayerUtils::pixelSizeScaleFactor( ctx, mLineWidthUnit, mLineWidthMapUnitScale );
2777 double outputPixelDist = ctx.convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
2778 double outputPixelOffset = mOffsetUnit == QgsUnitTypes::RenderPercentage ? outputPixelDist * mOffset / 100
2779 : ctx.convertToPainterUnits( mOffset, mOffsetUnit, mOffsetMapUnitScale );
2780
2781 // NOTE: this may need to be modified if we ever change from a forced rasterized/brush approach,
2782 // because potentially we may want to allow vector based line pattern fills where the first line
2783 // is offset by a large distance
2784
2785 // fix truncated pattern with larger offsets
2786 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDist );
2787 if ( outputPixelOffset > outputPixelDist / 2.0 )
2788 outputPixelOffset -= outputPixelDist;
2789
2790 // To get all patterns into image, we have to consider symbols size (estimateMaxBleed()).
2791 // For marker lines we have to get markers interval.
2792 double outputPixelBleed = 0;
2793 double outputPixelInterval = 0; // maximum interval
2794 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2795 {
2796 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2797 double outputPixelLayerBleed = layer->estimateMaxBleed( context.renderContext() );
2798 outputPixelBleed = std::max( outputPixelBleed, outputPixelLayerBleed );
2799
2800 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2801 if ( markerLineLayer )
2802 {
2803 double outputPixelLayerInterval = ctx.convertToPainterUnits( markerLineLayer->interval(), markerLineLayer->intervalUnit(), markerLineLayer->intervalMapUnitScale() );
2804
2805 // There may be multiple marker lines with different intervals.
2806 // In theory we should find the least common multiple, but that could be too
2807 // big (multiplication of intervals in the worst case).
2808 // Because patterns without small common interval would look strange, we
2809 // believe that the longest interval should usually be sufficient.
2810 outputPixelInterval = std::max( outputPixelInterval, outputPixelLayerInterval );
2811 }
2812 }
2813
2814 if ( outputPixelInterval > 0 )
2815 {
2816 // We have to adjust marker intervals to integer pixel size to get
2817 // repeatable pattern.
2818 double intervalScale = std::round( outputPixelInterval ) / outputPixelInterval;
2819 outputPixelInterval = std::round( outputPixelInterval );
2820
2821 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2822 {
2823 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2824
2825 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2826 if ( markerLineLayer )
2827 {
2828 markerLineLayer->setInterval( intervalScale * markerLineLayer->interval() );
2829 }
2830 }
2831 }
2832
2833 //create image
2834 int height, width;
2835 lineAngle = std::fmod( lineAngle, 360 );
2836 if ( lineAngle < 0 )
2837 lineAngle += 360;
2838 if ( qgsDoubleNear( lineAngle, 0 ) || qgsDoubleNear( lineAngle, 360 ) || qgsDoubleNear( lineAngle, 180 ) )
2839 {
2840 height = outputPixelDist;
2841 width = outputPixelInterval > 0 ? outputPixelInterval : height;
2842 }
2843 else if ( qgsDoubleNear( lineAngle, 90 ) || qgsDoubleNear( lineAngle, 270 ) )
2844 {
2845 width = outputPixelDist;
2846 height = outputPixelInterval > 0 ? outputPixelInterval : width;
2847 }
2848 else
2849 {
2850 height = outputPixelDist / std::cos( lineAngle * M_PI / 180 ); //keep perpendicular distance between lines constant
2851 width = outputPixelDist / std::sin( lineAngle * M_PI / 180 );
2852
2853 // recalculate real angle and distance after rounding to pixels
2854 lineAngle = 180 * std::atan2( static_cast< double >( height ), static_cast< double >( width ) ) / M_PI;
2855 if ( lineAngle < 0 )
2856 {
2857 lineAngle += 360.;
2858 }
2859
2860 height = std::abs( height );
2861 width = std::abs( width );
2862
2863 outputPixelDist = std::abs( height * std::cos( lineAngle * M_PI / 180 ) );
2864
2865 // Round offset to correspond to one pixel height, otherwise lines may
2866 // be shifted on tile border if offset falls close to pixel center
2867 int offsetHeight = static_cast< int >( std::round( outputPixelOffset / std::cos( lineAngle * M_PI / 180 ) ) );
2868 outputPixelOffset = offsetHeight * std::cos( lineAngle * M_PI / 180 );
2869 }
2870
2871 //depending on the angle, we might need to render into a larger image and use a subset of it
2872 double dx = 0;
2873 double dy = 0;
2874
2875 // Add buffer based on bleed but keep precisely the height/width ratio (angle)
2876 // thus we add integer multiplications of width and height covering the bleed
2877 int bufferMulti = static_cast< int >( std::max( std::ceil( outputPixelBleed / width ), std::ceil( outputPixelBleed / width ) ) );
2878
2879 // Always buffer at least once so that center of line marker in upper right corner
2880 // does not fall outside due to representation error
2881 bufferMulti = std::max( bufferMulti, 1 );
2882
2883 int xBuffer = width * bufferMulti;
2884 int yBuffer = height * bufferMulti;
2885 int innerWidth = width;
2886 int innerHeight = height;
2887 width += 2 * xBuffer;
2888 height += 2 * yBuffer;
2889
2890 //protect from zero width/height image and symbol layer from eating too much memory
2891 if ( width > 10000 || height > 10000 || width == 0 || height == 0 )
2892 {
2893 return;
2894 }
2895
2896 QImage patternImage( width, height, QImage::Format_ARGB32 );
2897 patternImage.fill( 0 );
2898
2899 QPointF p1, p2, p3, p4, p5, p6;
2900 if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
2901 {
2902 p1 = QPointF( 0, yBuffer );
2903 p2 = QPointF( width, yBuffer );
2904 p3 = QPointF( 0, yBuffer + innerHeight );
2905 p4 = QPointF( width, yBuffer + innerHeight );
2906 }
2907 else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
2908 {
2909 p1 = QPointF( xBuffer, height );
2910 p2 = QPointF( xBuffer, 0 );
2911 p3 = QPointF( xBuffer + innerWidth, height );
2912 p4 = QPointF( xBuffer + innerWidth, 0 );
2913 }
2914 else if ( lineAngle > 0 && lineAngle < 90 )
2915 {
2916 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
2917 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
2918 p1 = QPointF( 0, height );
2919 p2 = QPointF( width, 0 );
2920 p3 = QPointF( -dx, height - dy );
2921 p4 = QPointF( width - dx, -dy );
2922 p5 = QPointF( dx, height + dy );
2923 p6 = QPointF( width + dx, dy );
2924 }
2925 else if ( lineAngle > 180 && lineAngle < 270 )
2926 {
2927 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
2928 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
2929 p1 = QPointF( width, 0 );
2930 p2 = QPointF( 0, height );
2931 p3 = QPointF( width - dx, -dy );
2932 p4 = QPointF( -dx, height - dy );
2933 p5 = QPointF( width + dx, dy );
2934 p6 = QPointF( dx, height + dy );
2935 }
2936 else if ( lineAngle > 90 && lineAngle < 180 )
2937 {
2938 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
2939 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
2940 p1 = QPointF( 0, 0 );
2941 p2 = QPointF( width, height );
2942 p5 = QPointF( dx, -dy );
2943 p6 = QPointF( width + dx, height - dy );
2944 p3 = QPointF( -dx, dy );
2945 p4 = QPointF( width - dx, height + dy );
2946 }
2947 else if ( lineAngle > 270 && lineAngle < 360 )
2948 {
2949 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
2950 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
2951 p1 = QPointF( width, height );
2952 p2 = QPointF( 0, 0 );
2953 p5 = QPointF( width + dx, height - dy );
2954 p6 = QPointF( dx, -dy );
2955 p3 = QPointF( width - dx, height + dy );
2956 p4 = QPointF( -dx, dy );
2957 }
2958
2959 if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
2960 {
2961 QPointF tempPt;
2962 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
2963 p3 = QPointF( tempPt.x(), tempPt.y() );
2964 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
2965 p4 = QPointF( tempPt.x(), tempPt.y() );
2966 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
2967 p5 = QPointF( tempPt.x(), tempPt.y() );
2968 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
2969 p6 = QPointF( tempPt.x(), tempPt.y() );
2970
2971 //update p1, p2 last
2972 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
2973 p1 = QPointF( tempPt.x(), tempPt.y() );
2974 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
2975 p2 = QPointF( tempPt.x(), tempPt.y() );
2976 }
2977
2978 QPainter p( &patternImage );
2979
2980#if 0
2981 // DEBUG: Draw rectangle
2982 p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
2983 QPen pen( QColor( Qt::black ) );
2984 pen.setWidthF( 0.1 );
2985 pen.setCapStyle( Qt::FlatCap );
2986 p.setPen( pen );
2987
2988 // To see this rectangle, comment buffer cut below.
2989 // Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
2990 QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 );
2991 p.drawPolygon( polygon );
2992
2993 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 );
2994 p.drawPolygon( polygon );
2995#endif
2996
2997 // Use antialiasing because without antialiasing lines are rendered to the
2998 // right and below the mathematically defined points (not symmetrical)
2999 // and such tiles become useless for are filling
3000 p.setRenderHint( QPainter::Antialiasing, true );
3001
3002 // line rendering needs context for drawing on patternImage
3003 QgsRenderContext lineRenderContext;
3004 lineRenderContext.setPainter( &p );
3005 lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3007 lineRenderContext.setMapToPixel( mtp );
3008 lineRenderContext.setForceVectorOutput( false );
3009 lineRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3011
3012 fillLineSymbol->startRender( lineRenderContext, context.fields() );
3013
3014 QVector<QPolygonF> polygons;
3015 polygons.append( QPolygonF() << p1 << p2 );
3016 polygons.append( QPolygonF() << p3 << p4 );
3017 if ( !qgsDoubleNear( lineAngle, 0 ) && !qgsDoubleNear( lineAngle, 360 ) && !qgsDoubleNear( lineAngle, 90 ) && !qgsDoubleNear( lineAngle, 180 ) && !qgsDoubleNear( lineAngle, 270 ) )
3018 {
3019 polygons.append( QPolygonF() << p5 << p6 );
3020 }
3021
3022 for ( const QPolygonF &polygon : std::as_const( polygons ) )
3023 {
3024 fillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, context.selected() );
3025 }
3026
3027 fillLineSymbol->stopRender( lineRenderContext );
3028 p.end();
3029
3030 // Cut off the buffer
3031 patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
3032
3033 //set image to mBrush
3034 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3035 {
3036 QImage transparentImage = patternImage.copy();
3037 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3038 brush.setTextureImage( transparentImage );
3039 }
3040 else
3041 {
3042 brush.setTextureImage( patternImage );
3043 }
3044
3045 QTransform brushTransform;
3046 brush.setTransform( brushTransform );
3047}
3048
3050{
3051 // if we are using a vector based output, we need to render points as vectors
3052 // (OR if the line has data defined symbology, in which case we need to evaluate this line-by-line)
3053 mRenderUsingLines = context.renderContext().forceVectorOutput()
3054 || mFillLineSymbol->hasDataDefinedProperties()
3057
3058 if ( mRenderUsingLines )
3059 {
3060 if ( mFillLineSymbol )
3061 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3062 }
3063 else
3064 {
3065 // optimised render for screen only, use image based brush
3066 applyPattern( context, mBrush, mLineAngle, mDistance );
3067 }
3068}
3069
3071{
3072 if ( mRenderUsingLines && mFillLineSymbol )
3073 {
3074 mFillLineSymbol->stopRender( context.renderContext() );
3075 }
3076}
3077
3078void QgsLinePatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3079{
3080 if ( !mRenderUsingLines )
3081 {
3082 // use image based brush for speed
3083 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3084 return;
3085 }
3086
3087 // vector based output - so draw line by line!
3088 QPainter *p = context.renderContext().painter();
3089 if ( !p )
3090 {
3091 return;
3092 }
3093
3094 double lineAngle = mLineAngle;
3096 {
3097 context.setOriginalValueVariable( mLineAngle );
3099 }
3100
3101 double distance = mDistance;
3103 {
3104 context.setOriginalValueVariable( mDistance );
3106 }
3107 const double outputPixelDistance = context.renderContext().convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
3108
3109 double offset = mOffset;
3110 double outputPixelOffset = mOffsetUnit == QgsUnitTypes::RenderPercentage ? outputPixelDistance * offset / 100
3111 : context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3112
3113 // fix truncated pattern with larger offsets
3114 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDistance );
3115 if ( outputPixelOffset > outputPixelDistance / 2.0 )
3116 outputPixelOffset -= outputPixelDistance;
3117
3118 p->setPen( QPen( Qt::NoPen ) );
3119
3120 if ( context.selected() )
3121 {
3122 QColor selColor = context.renderContext().selectionColor();
3123 p->setBrush( QBrush( selColor ) );
3124 _renderPolygon( p, points, rings, context );
3125 }
3126
3127 // if invalid parameters, skip out
3128 if ( qgsDoubleNear( distance, 0 ) )
3129 return;
3130
3131 p->save();
3132
3133 Qgis::LineClipMode clipMode = mClipMode;
3135 {
3137 bool ok = false;
3138 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyLineClipping, context.renderContext().expressionContext(), QString(), &ok );
3139 if ( ok )
3140 {
3141 Qgis::LineClipMode decodedMode = QgsSymbolLayerUtils::decodeLineClipMode( valueString, &ok );
3142 if ( ok )
3143 clipMode = decodedMode;
3144 }
3145 }
3146
3147 std::unique_ptr< QgsPolygon > shapePolygon;
3148 std::unique_ptr< QgsGeometryEngine > shapeEngine;
3149 switch ( clipMode )
3150 {
3152 break;
3153
3155 {
3156 shapePolygon = std::make_unique< QgsPolygon >();
3157 shapePolygon->setExteriorRing( QgsLineString::fromQPolygonF( points ) );
3158 if ( rings )
3159 {
3160 for ( const QPolygonF &ring : *rings )
3161 {
3162 shapePolygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
3163 }
3164 }
3165 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3166 shapeEngine->prepareGeometry();
3167 break;
3168 }
3169
3171 {
3172 QPainterPath path;
3173 path.addPolygon( points );
3174 if ( rings )
3175 {
3176 for ( const QPolygonF &ring : *rings )
3177 {
3178 path.addPolygon( ring );
3179 }
3180 }
3181 p->setClipPath( path, Qt::IntersectClip );
3182 break;
3183 }
3184 }
3185
3186 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3187 const QRectF boundingRect = points.boundingRect();
3188
3189 QTransform invertedRotateTransform;
3190 double left;
3191 double top;
3192 double right;
3193 double bottom;
3194
3195 QTransform transform;
3196 if ( applyBrushTransform )
3197 {
3198 // rotation applies around center of feature
3199 transform.translate( -boundingRect.center().x(),
3200 -boundingRect.center().y() );
3201 transform.rotate( lineAngle );
3202 transform.translate( boundingRect.center().x(),
3203 boundingRect.center().y() );
3204 }
3205 else
3206 {
3207 // rotation applies around top of viewport
3208 transform.rotate( lineAngle );
3209 }
3210
3211 const QRectF transformedBounds = transform.map( points ).boundingRect();
3212
3213 // bounds are expanded out a bit to account for maximum line width
3214 const double buffer = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFillLineSymbol.get(), context.renderContext() );
3215 left = transformedBounds.left() - buffer * 2;
3216 top = transformedBounds.top() - buffer * 2;
3217 right = transformedBounds.right() + buffer * 2;
3218 bottom = transformedBounds.bottom() + buffer * 2;
3219 invertedRotateTransform = transform.inverted();
3220
3221 if ( !applyBrushTransform )
3222 {
3223 top -= transformedBounds.top() - ( outputPixelDistance * std::floor( transformedBounds.top() / outputPixelDistance ) );
3224 }
3225
3227 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
3228 const bool needsExpressionContext = mFillLineSymbol->hasDataDefinedProperties();
3229
3230 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3232
3233 int currentLine = 0;
3234 for ( double currentY = top; currentY <= bottom; currentY += outputPixelDistance )
3235 {
3236 if ( context.renderContext().renderingStopped() )
3237 break;
3238
3239 if ( needsExpressionContext )
3240 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_line_number" ), ++currentLine, true ) );
3241
3242 double x1 = left;
3243 double y1 = currentY;
3244 double x2 = left;
3245 double y2 = currentY;
3246 invertedRotateTransform.map( left, currentY - outputPixelOffset, &x1, &y1 );
3247 invertedRotateTransform.map( right, currentY - outputPixelOffset, &x2, &y2 );
3248
3249 if ( shapeEngine )
3250 {
3251 QgsLineString ls( QgsPoint( x1, y1 ), QgsPoint( x2, y2 ) );
3252 std::unique_ptr< QgsAbstractGeometry > intersection( shapeEngine->intersection( &ls ) );
3253 for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
3254 {
3255 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( *it ) )
3256 {
3257 mFillLineSymbol->renderPolyline( ls->asQPolygonF(), context.feature(), context.renderContext() );
3258 }
3259 }
3260 }
3261 else
3262 {
3263 mFillLineSymbol->renderPolyline( QPolygonF() << QPointF( x1, y1 ) << QPointF( x2, y2 ), context.feature(), context.renderContext() );
3264 }
3265 }
3266
3267 p->restore();
3268
3270}
3271
3273{
3274 QVariantMap map = QgsImageFillSymbolLayer::properties();
3275 map.insert( QStringLiteral( "angle" ), QString::number( mLineAngle ) );
3276 map.insert( QStringLiteral( "distance" ), QString::number( mDistance ) );
3277 map.insert( QStringLiteral( "line_width" ), QString::number( mLineWidth ) );
3278 map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
3279 map.insert( QStringLiteral( "offset" ), QString::number( mOffset ) );
3280 map.insert( QStringLiteral( "distance_unit" ), QgsUnitTypes::encodeUnit( mDistanceUnit ) );
3281 map.insert( QStringLiteral( "line_width_unit" ), QgsUnitTypes::encodeUnit( mLineWidthUnit ) );
3282 map.insert( QStringLiteral( "offset_unit" ), QgsUnitTypes::encodeUnit( mOffsetUnit ) );
3283 map.insert( QStringLiteral( "distance_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale ) );
3284 map.insert( QStringLiteral( "line_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineWidthMapUnitScale ) );
3285 map.insert( QStringLiteral( "offset_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ) );
3286 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3287 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3288 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeLineClipMode( mClipMode ) );
3289 return map;
3290}
3291
3293{
3295 if ( mFillLineSymbol )
3296 {
3297 clonedLayer->setSubSymbol( mFillLineSymbol->clone() );
3298 }
3299 copyPaintEffect( clonedLayer );
3300 copyDataDefinedProperties( clonedLayer );
3301 return clonedLayer;
3302}
3303
3304void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3305{
3306 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
3307 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
3308 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
3309 element.appendChild( symbolizerElem );
3310
3311 // <Geometry>
3312 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
3313
3314 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
3315 symbolizerElem.appendChild( fillElem );
3316
3317 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
3318 fillElem.appendChild( graphicFillElem );
3319
3320 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
3321 graphicFillElem.appendChild( graphicElem );
3322
3323 //line properties must be inside the graphic definition
3324 QColor lineColor = mFillLineSymbol ? mFillLineSymbol->color() : QColor();
3325 double lineWidth = mFillLineSymbol ? mFillLineSymbol->width() : 0.0;
3326 lineWidth = QgsSymbolLayerUtils::rescaleUom( lineWidth, mLineWidthUnit, props );
3327 double distance = QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, props );
3328 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "horline" ), QColor(), lineColor, Qt::SolidLine, lineWidth, distance );
3329
3330 // <Rotation>
3331 QString angleFunc;
3332 bool ok;
3333 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
3334 if ( !ok )
3335 {
3336 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mLineAngle );
3337 }
3338 else if ( !qgsDoubleNear( angle + mLineAngle, 0.0 ) )
3339 {
3340 angleFunc = QString::number( angle + mLineAngle );
3341 }
3342 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
3343
3344 // <se:Displacement>
3345 QPointF lineOffset( std::sin( mLineAngle ) * mOffset, std::cos( mLineAngle ) * mOffset );
3346 lineOffset = QgsSymbolLayerUtils::rescaleUom( lineOffset, mOffsetUnit, props );
3347 QgsSymbolLayerUtils::createDisplacementElement( doc, graphicElem, lineOffset );
3348}
3349
3350QString QgsLinePatternFillSymbolLayer::ogrFeatureStyleWidth( double widthScaleFactor ) const
3351{
3352 QString featureStyle;
3353 featureStyle.append( "Brush(" );
3354 featureStyle.append( QStringLiteral( "fc:%1" ).arg( mColor.name() ) );
3355 featureStyle.append( QStringLiteral( ",bc:%1" ).arg( QLatin1String( "#00000000" ) ) ); //transparent background
3356 featureStyle.append( ",id:\"ogr-brush-2\"" );
3357 featureStyle.append( QStringLiteral( ",a:%1" ).arg( mLineAngle ) );
3358 featureStyle.append( QStringLiteral( ",s:%1" ).arg( mLineWidth * widthScaleFactor ) );
3359 featureStyle.append( ",dx:0mm" );
3360 featureStyle.append( QStringLiteral( ",dy:%1mm" ).arg( mDistance * widthScaleFactor ) );
3361 featureStyle.append( ')' );
3362 return featureStyle;
3363}
3364
3366{
3368 && ( !mFillLineSymbol || !mFillLineSymbol->hasDataDefinedProperties() ) )
3369 {
3370 return; //no data defined settings
3371 }
3372
3373 double lineAngle = mLineAngle;
3375 {
3376 context.setOriginalValueVariable( mLineAngle );
3378 }
3379 double distance = mDistance;
3381 {
3382 context.setOriginalValueVariable( mDistance );
3384 }
3385 applyPattern( context, mBrush, lineAngle, distance );
3386}
3387
3389{
3390 QString name;
3391 QColor fillColor, lineColor;
3392 double size, lineWidth;
3393 Qt::PenStyle lineStyle;
3394
3395 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
3396 if ( fillElem.isNull() )
3397 return nullptr;
3398
3399 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
3400 if ( graphicFillElem.isNull() )
3401 return nullptr;
3402
3403 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
3404 if ( graphicElem.isNull() )
3405 return nullptr;
3406
3407 if ( !QgsSymbolLayerUtils::wellKnownMarkerFromSld( graphicElem, name, fillColor, lineColor, lineStyle, lineWidth, size ) )
3408 return nullptr;
3409
3410 if ( name != QLatin1String( "horline" ) )
3411 return nullptr;
3412
3413 double angle = 0.0;
3414 QString angleFunc;
3415 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3416 {
3417 bool ok;
3418 double d = angleFunc.toDouble( &ok );
3419 if ( ok )
3420 angle = d;
3421 }
3422
3423 double offset = 0.0;
3424 QPointF vectOffset;
3425 if ( QgsSymbolLayerUtils::displacementFromSldElement( graphicElem, vectOffset ) )
3426 {
3427 offset = std::sqrt( std::pow( vectOffset.x(), 2 ) + std::pow( vectOffset.y(), 2 ) );
3428 }
3429
3430 double scaleFactor = 1.0;
3431 const QString uom = element.attribute( QStringLiteral( "uom" ) );
3432 QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
3433 size = size * scaleFactor;
3434 lineWidth = lineWidth * scaleFactor;
3435
3436 std::unique_ptr< QgsLinePatternFillSymbolLayer > sl = std::make_unique< QgsLinePatternFillSymbolLayer >();
3437 sl->setOutputUnit( sldUnitSize );
3438 sl->setColor( lineColor );
3439 sl->setLineWidth( lineWidth );
3440 sl->setLineAngle( angle );
3441 sl->setOffset( offset );
3442 sl->setDistance( size );
3443
3444 // try to get the stroke
3445 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
3446 if ( !strokeElem.isNull() )
3447 {
3449 if ( l )
3450 {
3451 QgsSymbolLayerList layers;
3452 layers.append( l );
3453 sl->setSubSymbol( new QgsLineSymbol( layers ) );
3454 }
3455 }
3456
3457 return sl.release();
3458}
3459
3460
3462
3465{
3467 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
3468}
3469
3471
3473{
3475 mDistanceXUnit = unit;
3476 mDistanceYUnit = unit;
3477 // don't change "percentage" units -- since they adapt directly to whatever other unit is set
3479 mDisplacementXUnit = unit;
3481 mDisplacementYUnit = unit;
3483 mOffsetXUnit = unit;
3485 mOffsetYUnit = unit;
3487 mRandomDeviationXUnit = unit;
3489 mRandomDeviationYUnit = unit;
3490
3491 if ( mMarkerSymbol )
3492 {
3493 mMarkerSymbol->setOutputUnit( unit );
3494 }
3495}
3496
3498{
3500 if ( mDistanceXUnit != unit ||
3501 mDistanceYUnit != unit ||
3508 {
3510 }
3511 return unit;
3512}
3513
3515{
3524}
3525
3527{
3529 mDistanceXMapUnitScale = scale;
3530 mDistanceYMapUnitScale = scale;
3533 mOffsetXMapUnitScale = scale;
3534 mOffsetYMapUnitScale = scale;
3537}
3538
3540{
3549 {
3551 }
3552 return QgsMapUnitScale();
3553}
3554
3556{
3557 std::unique_ptr< QgsPointPatternFillSymbolLayer > layer = std::make_unique< QgsPointPatternFillSymbolLayer >();
3558 if ( properties.contains( QStringLiteral( "distance_x" ) ) )
3559 {
3560 layer->setDistanceX( properties[QStringLiteral( "distance_x" )].toDouble() );
3561 }
3562 if ( properties.contains( QStringLiteral( "distance_y" ) ) )
3563 {
3564 layer->setDistanceY( properties[QStringLiteral( "distance_y" )].toDouble() );
3565 }
3566 if ( properties.contains( QStringLiteral( "displacement_x" ) ) )
3567 {
3568 layer->setDisplacementX( properties[QStringLiteral( "displacement_x" )].toDouble() );
3569 }
3570 if ( properties.contains( QStringLiteral( "displacement_y" ) ) )
3571 {
3572 layer->setDisplacementY( properties[QStringLiteral( "displacement_y" )].toDouble() );
3573 }
3574 if ( properties.contains( QStringLiteral( "offset_x" ) ) )
3575 {
3576 layer->setOffsetX( properties[QStringLiteral( "offset_x" )].toDouble() );
3577 }
3578 if ( properties.contains( QStringLiteral( "offset_y" ) ) )
3579 {
3580 layer->setOffsetY( properties[QStringLiteral( "offset_y" )].toDouble() );
3581 }
3582
3583 if ( properties.contains( QStringLiteral( "distance_x_unit" ) ) )
3584 {
3585 layer->setDistanceXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_x_unit" )].toString() ) );
3586 }
3587 if ( properties.contains( QStringLiteral( "distance_x_map_unit_scale" ) ) )
3588 {
3589 layer->setDistanceXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_x_map_unit_scale" )].toString() ) );
3590 }
3591 if ( properties.contains( QStringLiteral( "distance_y_unit" ) ) )
3592 {
3593 layer->setDistanceYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_y_unit" )].toString() ) );
3594 }
3595 if ( properties.contains( QStringLiteral( "distance_y_map_unit_scale" ) ) )
3596 {
3597 layer->setDistanceYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_y_map_unit_scale" )].toString() ) );
3598 }
3599 if ( properties.contains( QStringLiteral( "displacement_x_unit" ) ) )
3600 {
3601 layer->setDisplacementXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_x_unit" )].toString() ) );
3602 }
3603 if ( properties.contains( QStringLiteral( "displacement_x_map_unit_scale" ) ) )
3604 {
3605 layer->setDisplacementXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_x_map_unit_scale" )].toString() ) );
3606 }
3607 if ( properties.contains( QStringLiteral( "displacement_y_unit" ) ) )
3608 {
3609 layer->setDisplacementYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_y_unit" )].toString() ) );
3610 }
3611 if ( properties.contains( QStringLiteral( "displacement_y_map_unit_scale" ) ) )
3612 {
3613 layer->setDisplacementYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_y_map_unit_scale" )].toString() ) );
3614 }
3615 if ( properties.contains( QStringLiteral( "offset_x_unit" ) ) )
3616 {
3617 layer->setOffsetXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_x_unit" )].toString() ) );
3618 }
3619 if ( properties.contains( QStringLiteral( "offset_x_map_unit_scale" ) ) )
3620 {
3621 layer->setOffsetXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_x_map_unit_scale" )].toString() ) );
3622 }
3623 if ( properties.contains( QStringLiteral( "offset_y_unit" ) ) )
3624 {
3625 layer->setOffsetYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_y_unit" )].toString() ) );
3626 }
3627 if ( properties.contains( QStringLiteral( "offset_y_map_unit_scale" ) ) )
3628 {
3629 layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_y_map_unit_scale" )].toString() ) );
3630 }
3631
3632 if ( properties.contains( QStringLiteral( "random_deviation_x" ) ) )
3633 {
3634 layer->setMaximumRandomDeviationX( properties[QStringLiteral( "random_deviation_x" )].toDouble() );
3635 }
3636 if ( properties.contains( QStringLiteral( "random_deviation_y" ) ) )
3637 {
3638 layer->setMaximumRandomDeviationY( properties[QStringLiteral( "random_deviation_y" )].toDouble() );
3639 }
3640 if ( properties.contains( QStringLiteral( "random_deviation_x_unit" ) ) )
3641 {
3642 layer->setRandomDeviationXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_x_unit" )].toString() ) );
3643 }
3644 if ( properties.contains( QStringLiteral( "random_deviation_x_map_unit_scale" ) ) )
3645 {
3646 layer->setRandomDeviationXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_x_map_unit_scale" )].toString() ) );
3647 }
3648 if ( properties.contains( QStringLiteral( "random_deviation_y_unit" ) ) )
3649 {
3650 layer->setRandomDeviationYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_y_unit" )].toString() ) );
3651 }
3652 if ( properties.contains( QStringLiteral( "random_deviation_y_map_unit_scale" ) ) )
3653 {
3654 layer->setRandomDeviationYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_y_map_unit_scale" )].toString() ) );
3655 }
3656 unsigned long seed = 0;
3657 if ( properties.contains( QStringLiteral( "seed" ) ) )
3658 seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
3659 else
3660 {
3661 // if we a creating a new point pattern fill from scratch, we default to a random seed
3662 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
3663 std::random_device rd;
3664 std::mt19937 mt( seed == 0 ? rd() : seed );
3665 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
3666 seed = uniformDist( mt );
3667 }
3668 layer->setSeed( seed );
3669
3670 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
3671 {
3672 layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
3673 }
3674 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
3675 {
3676 layer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
3677 }
3678 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
3679 {
3680 layer->setClipMode( QgsSymbolLayerUtils::decodeMarkerClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
3681 }
3682 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
3683 {
3684 layer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
3685 }
3686
3687 if ( properties.contains( QStringLiteral( "angle" ) ) )
3688 {
3689 layer->setAngle( properties[QStringLiteral( "angle" )].toDouble() );
3690 }
3691
3692 layer->restoreOldDataDefinedProperties( properties );
3693
3694 return layer.release();
3695}
3696
3698{
3699 return QStringLiteral( "PointPatternFill" );
3700}
3701
3702void QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double distanceX, double distanceY,
3703 double displacementX, double displacementY, double offsetX, double offsetY )
3704{
3705 //render 3 rows and columns in one go to easily incorporate displacement
3706 const QgsRenderContext &ctx = context.renderContext();
3709
3710 double widthOffset = std::fmod(
3712 width );
3713 double heightOffset = std::fmod(
3715 height );
3716
3717 if ( width > 10000 || height > 10000 ) //protect symbol layer from eating too much memory
3718 {
3719 QImage img;
3720 brush.setTextureImage( img );
3721 return;
3722 }
3723
3724 QImage patternImage( width, height, QImage::Format_ARGB32 );
3725 patternImage.fill( 0 );
3726 if ( patternImage.isNull() )
3727 {
3728 brush.setTextureImage( QImage() );
3729 return;
3730 }
3731 if ( mMarkerSymbol )
3732 {
3733 QPainter p( &patternImage );
3734
3735 //marker rendering needs context for drawing on patternImage
3736 QgsRenderContext pointRenderContext;
3737 pointRenderContext.setRendererScale( context.renderContext().rendererScale() );
3738 pointRenderContext.setPainter( &p );
3739 pointRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3740
3743 pointRenderContext.setMapToPixel( mtp );
3744 pointRenderContext.setForceVectorOutput( false );
3745 pointRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3747
3748 mMarkerSymbol->startRender( pointRenderContext, context.fields() );
3749
3750 //render points on distance grid
3751 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3752 {
3753 for ( double currentY = -height; currentY <= height * 2.0; currentY += height )
3754 {
3755 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset, currentY + heightOffset ), context.feature(), pointRenderContext );
3756 }
3757 }
3758
3759 //render displaced points
3760 double displacementPixelX = mDisplacementXUnit == QgsUnitTypes::RenderPercentage
3761 ? ( width * displacementX / 200 )
3763 double displacementPixelY = mDisplacementYUnit == QgsUnitTypes::RenderPercentage
3764 ? ( height * displacementY / 200 )
3766 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3767 {
3768 for ( double currentY = -height / 2.0; currentY <= height * 2.0; currentY += height )
3769 {
3770 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + displacementPixelX, currentY + heightOffset ), context.feature(), pointRenderContext );
3771 }
3772 }
3773
3774 for ( double currentX = -width / 2.0; currentX <= width * 2.0; currentX += width )
3775 {
3776 for ( double currentY = -height; currentY <= height * 2.0; currentY += height / 2.0 )
3777 {
3778 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + ( std::fmod( currentY, height ) != 0 ? displacementPixelX : 0 ), currentY + heightOffset - displacementPixelY ), context.feature(), pointRenderContext );
3779 }
3780 }
3781
3782 mMarkerSymbol->stopRender( pointRenderContext );
3783 }
3784
3785 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3786 {
3787 QImage transparentImage = patternImage.copy();
3788 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3789 brush.setTextureImage( transparentImage );
3790 }
3791 else
3792 {
3793 brush.setTextureImage( patternImage );
3794 }
3795 QTransform brushTransform;
3796 brush.setTransform( brushTransform );
3797}
3798
3800{
3801 // if we are using a vector based output, we need to render points as vectors
3802 // (OR if the marker has data defined symbology, in which case we need to evaluate this point-by-point)
3803 mRenderUsingMarkers = context.renderContext().forceVectorOutput()
3804 || mMarkerSymbol->hasDataDefinedProperties()
3808 || mClipMode != Qgis::MarkerClipMode::Shape
3811 || !qgsDoubleNear( mAngle, 0 )
3813
3814 if ( mRenderUsingMarkers )
3815 {
3816 mMarkerSymbol->startRender( context.renderContext() );
3817 }
3818 else
3819 {
3820 // optimised render for screen only, use image based brush
3822 }
3823}
3824
3826{
3827 if ( mRenderUsingMarkers )
3828 {
3829 mMarkerSymbol->stopRender( context.renderContext() );
3830 }
3831}
3832
3834{
3835 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3836 // Otherwise generators used in the subsymbol will only render a single point per feature (they
3837 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
3838}
3839
3841{
3842 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3843 // Otherwise generators used in the subsymbol will only render a single point per feature (they
3844 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
3845}
3846
3847void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3848{
3849 if ( !mRenderUsingMarkers )
3850 {
3851 // use image based brush for speed
3852 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3853 return;
3854 }
3855
3856 // vector based output - so draw dot by dot!
3857 QPainter *p = context.renderContext().painter();
3858 if ( !p )
3859 {
3860 return;
3861 }
3862
3863 double angle = mAngle;
3865 {
3868 }
3869
3870 double distanceX = mDistanceX;
3872 {
3875 }
3877
3878 double distanceY = mDistanceY;
3880 {
3883 }
3885
3886 double offsetX = mOffsetX;
3888 {
3891 }
3892 const double widthOffset = std::fmod(
3894 ? ( offsetX * width / 100 )
3896 width );
3897
3898 double offsetY = mOffsetY;
3900 {
3903 }
3904 const double heightOffset = std::fmod(
3906 ? ( offsetY * height / 100 )
3908 height );
3909
3912 {
3915 }
3916 const double displacementPixelX = mDisplacementXUnit == QgsUnitTypes::RenderPercentage
3917 ? ( displacementX * width / 100 )
3919
3922 {
3925 }
3926 const double displacementPixelY = mDisplacementYUnit == QgsUnitTypes::RenderPercentage
3927 ? ( displacementY * height / 100 )
3929
3930 p->setPen( QPen( Qt::NoPen ) );
3931
3932 if ( context.selected() )
3933 {
3934 QColor selColor = context.renderContext().selectionColor();
3935 p->setBrush( QBrush( selColor ) );
3936 _renderPolygon( p, points, rings, context );
3937 }
3938
3939 // if invalid parameters, skip out
3940 if ( qgsDoubleNear( width, 0 ) || qgsDoubleNear( height, 0 ) || width < 0 || height < 0 )
3941 return;
3942
3943 p->save();
3944
3945 Qgis::MarkerClipMode clipMode = mClipMode;
3947 {
3949 bool ok = false;
3950 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyMarkerClipping, context.renderContext().expressionContext(), QString(), &ok );
3951 if ( ok )
3952 {
3953 Qgis::MarkerClipMode decodedMode = QgsSymbolLayerUtils::decodeMarkerClipMode( valueString, &ok );
3954 if ( ok )
3955 clipMode = decodedMode;
3956 }
3957 }
3958
3959 std::unique_ptr< QgsPolygon > shapePolygon;
3960 std::unique_ptr< QgsGeometryEngine > shapeEngine;
3961 switch ( clipMode )
3962 {
3966 {
3967 shapePolygon = std::make_unique< QgsPolygon >();
3968 shapePolygon->setExteriorRing( QgsLineString::fromQPolygonF( points ) );
3969 if ( rings )
3970 {
3971 for ( const QPolygonF &ring : *rings )
3972 {
3973 shapePolygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
3974 }
3975 }
3976 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3977 shapeEngine->prepareGeometry();
3978 break;
3979 }
3980
3982 {
3983 QPainterPath path;
3984 path.addPolygon( points );
3985 if ( rings )
3986 {
3987 for ( const QPolygonF &ring : *rings )
3988 {
3989 path.addPolygon( ring );
3990 }
3991 }
3992 p->setClipPath( path, Qt::IntersectClip );
3993 break;
3994 }
3995 }
3996
3997 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3998 const QRectF boundingRect = points.boundingRect();
3999
4000 QTransform invertedRotateTransform;
4001 double left;
4002 double top;
4003 double right;
4004 double bottom;
4005
4006 if ( !qgsDoubleNear( angle, 0 ) )
4007 {
4008 QTransform transform;
4009 if ( applyBrushTransform )
4010 {
4011 // rotation applies around center of feature
4012 transform.translate( -boundingRect.center().x(),
4013 -boundingRect.center().y() );
4014 transform.rotate( -angle );
4015 transform.translate( boundingRect.center().x(),
4016 boundingRect.center().y() );
4017 }
4018 else
4019 {
4020 // rotation applies around top of viewport
4021 transform.rotate( -angle );
4022 }
4023
4024 const QRectF transformedBounds = transform.map( points ).boundingRect();
4025 left = transformedBounds.left() - 2 * width;
4026 top = transformedBounds.top() - 2 * height;
4027 right = transformedBounds.right() + 2 * width;
4028 bottom = transformedBounds.bottom() + 2 * height;
4029 invertedRotateTransform = transform.inverted();
4030
4031 if ( !applyBrushTransform )
4032 {
4033 left -= transformedBounds.left() - ( width * std::floor( transformedBounds.left() / width ) );
4034 top -= transformedBounds.top() - ( height * std::floor( transformedBounds.top() / height ) );
4035 }
4036 }
4037 else
4038 {
4039 left = boundingRect.left() - 2 * width;
4040 top = boundingRect.top() - 2 * height;
4041 right = boundingRect.right() + 2 * width;
4042 bottom = boundingRect.bottom() + 2 * height;
4043
4044 if ( !applyBrushTransform )
4045 {
4046 left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
4047 top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
4048 }
4049 }
4050
4051 unsigned long seed = mSeed;
4053 {
4054 context.renderContext().expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
4056 }
4057
4058 double maxRandomDeviationX = mRandomDeviationX;
4060 {
4061 context.setOriginalValueVariable( maxRandomDeviationX );
4062 maxRandomDeviationX = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetX, context.renderContext().expressionContext(), maxRandomDeviationX );
4063 }
4064 const double maxRandomDeviationPixelX = mRandomDeviationXUnit == QgsUnitTypes::RenderPercentage ? ( maxRandomDeviationX * width / 100 )
4066
4067 double maxRandomDeviationY = mRandomDeviationY;
4069 {
4070 context.setOriginalValueVariable( maxRandomDeviationY );
4071 maxRandomDeviationY = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetY, context.renderContext().expressionContext(), maxRandomDeviationY );
4072 }
4073 const double maxRandomDeviationPixelY = mRandomDeviationYUnit == QgsUnitTypes::RenderPercentage ? ( maxRandomDeviationY * height / 100 )
4075
4076 std::random_device rd;
4077 std::mt19937 mt( seed == 0 ? rd() : seed );
4078 std::uniform_real_distribution<> uniformDist( 0, 1 );
4079 const bool useRandomShift = !qgsDoubleNear( maxRandomDeviationPixelX, 0 ) || !qgsDoubleNear( maxRandomDeviationPixelY, 0 );
4080
4082 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
4083 int pointNum = 0;
4084 const bool needsExpressionContext = mMarkerSymbol->hasDataDefinedProperties();
4085
4086 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4088
4089 const double prevOpacity = mMarkerSymbol->opacity();
4090 mMarkerSymbol->setOpacity( mMarkerSymbol->opacity() * context.opacity() );
4091
4092 bool alternateColumn = false;
4093 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
4094 for ( double currentX = left; currentX <= right; currentX += width, alternateColumn = !alternateColumn )
4095 {
4096 if ( context.renderContext().renderingStopped() )
4097 break;
4098
4099 if ( needsExpressionContext )
4100 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_column" ), ++currentCol, true ) );
4101
4102 bool alternateRow = false;
4103 const double columnX = currentX + widthOffset;
4104 int currentRow = -3;
4105 for ( double currentY = top; currentY <= bottom; currentY += height, alternateRow = !alternateRow )
4106 {
4107 if ( context.renderContext().renderingStopped() )
4108 break;
4109
4110 double y = currentY + heightOffset;
4111 double x = columnX;
4112 if ( alternateRow )
4113 x += displacementPixelX;
4114
4115 if ( !alternateColumn )
4116 y -= displacementPixelY;
4117
4118 if ( !qgsDoubleNear( angle, 0 ) )
4119 {
4120 double xx = x;
4121 double yy = y;
4122 invertedRotateTransform.map( xx, yy, &x, &y );
4123 }
4124
4125 if ( useRandomShift )
4126 {
4127 x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
4128 y += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelY;
4129 }
4130
4131 if ( needsExpressionContext )
4132 {
4134 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_row" ), ++currentRow, true ) );
4135 }
4136
4137 if ( shapeEngine )
4138 {
4139 bool renderPoint = true;
4140 switch ( clipMode )
4141 {
4143 {
4144 // 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
4145 const QgsRectangle markerRect = QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) );
4146 QgsPoint p( markerRect.center() );
4147 renderPoint = shapeEngine->intersects( &p );
4148 break;
4149 }
4150
4153 {
4154 const QgsGeometry markerBounds = QgsGeometry::fromRect( QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) ) );
4155
4157 renderPoint = shapeEngine->contains( markerBounds.constGet() );
4158 else
4159 renderPoint = shapeEngine->intersects( markerBounds.constGet() );
4160 break;
4161 }
4162
4164 break;
4165 }
4166
4167 if ( !renderPoint )
4168 continue;
4169 }
4170
4171 mMarkerSymbol->renderPoint( QPointF( x, y ), context.feature(), context.renderContext() );
4172 }
4173 }
4174
4175 mMarkerSymbol->setOpacity( prevOpacity );
4176
4177 p->restore();
4178
4180}
4181
4183{
4184 QVariantMap map = QgsImageFillSymbolLayer::properties();
4185 map.insert( QStringLiteral( "distance_x" ), QString::number( mDistanceX ) );
4186 map.insert( QStringLiteral( "distance_y" ), QString::number( mDistanceY ) );
4187 map.insert( QStringLiteral( "displacement_x" ), QString::number( mDisplacementX ) );
4188 map.insert( QStringLiteral( "displacement_y" ), QString::number( mDisplacementY ) );
4189 map.insert( QStringLiteral( "offset_x" ), QString::number( mOffsetX ) );
4190 map.insert( QStringLiteral( "offset_y" ), QString::number( mOffsetY ) );
4191 map.insert( QStringLiteral( "distance_x_unit" ), QgsUnitTypes::encodeUnit( mDistanceXUnit ) );
4192 map.insert( QStringLiteral( "distance_y_unit" ), QgsUnitTypes::encodeUnit( mDistanceYUnit ) );
4193 map.insert( QStringLiteral( "displacement_x_unit" ), QgsUnitTypes::encodeUnit( mDisplacementXUnit ) );
4194 map.insert( QStringLiteral( "displacement_y_unit" ), QgsUnitTypes::encodeUnit( mDisplacementYUnit ) );
4195 map.insert( QStringLiteral( "offset_x_unit" ), QgsUnitTypes::encodeUnit( mOffsetXUnit ) );
4196 map.insert( QStringLiteral( "offset_y_unit" ), QgsUnitTypes::encodeUnit( mOffsetYUnit ) );
4197 map.insert( QStringLiteral( "distance_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceXMapUnitScale ) );
4198 map.insert( QStringLiteral( "distance_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceYMapUnitScale ) );
4199 map.insert( QStringLiteral( "displacement_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementXMapUnitScale ) );
4200 map.insert( QStringLiteral( "displacement_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementYMapUnitScale ) );
4201 map.insert( QStringLiteral( "offset_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetXMapUnitScale ) );
4202 map.insert( QStringLiteral( "offset_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetYMapUnitScale ) );
4203 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
4204 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
4205 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeMarkerClipMode( mClipMode ) );
4206 map.insert( QStringLiteral( "random_deviation_x" ), QString::number( mRandomDeviationX ) );
4207 map.insert( QStringLiteral( "random_deviation_y" ), QString::number( mRandomDeviationY ) );
4208 map.insert( QStringLiteral( "random_deviation_x_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationXUnit ) );
4209 map.insert( QStringLiteral( "random_deviation_y_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationYUnit ) );
4210 map.insert( QStringLiteral( "random_deviation_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationXMapUnitScale ) );
4211 map.insert( QStringLiteral( "random_deviation_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationYMapUnitScale ) );
4212 map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
4213 map.insert( QStringLiteral( "angle" ), mAngle );
4214 return map;
4215}
4216
4218{
4220 if ( mMarkerSymbol )
4221 {
4222 clonedLayer->setSubSymbol( mMarkerSymbol->clone() );
4223 }
4224 clonedLayer->setClipMode( mClipMode );
4225 copyDataDefinedProperties( clonedLayer );
4226 copyPaintEffect( clonedLayer );
4227 return clonedLayer;
4228}
4229
4230void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4231{
4232 for ( int i = 0; i < mMarkerSymbol->symbolLayerCount(); i++ )
4233 {
4234 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
4235 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
4236 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
4237 element.appendChild( symbolizerElem );
4238
4239 // <Geometry>
4240 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
4241
4242 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
4243 symbolizerElem.appendChild( fillElem );
4244
4245 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
4246 fillElem.appendChild( graphicFillElem );
4247
4248 // store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
4251 QString dist = QgsSymbolLayerUtils::encodePoint( QPointF( dx, dy ) );
4252 QDomElement distanceElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "distance" ), dist );
4253 symbolizerElem.appendChild( distanceElem );
4254
4255 QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( i );
4256 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
4257 {
4258 markerLayer->writeSldMarker( doc, graphicFillElem, props );
4259 }
4260 else if ( layer )
4261 {
4262 QString errorMsg = QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() );
4263 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4264 }
4265 else
4266 {
4267 QString errorMsg = QStringLiteral( "Missing point pattern symbol layer. Skip it." );
4268 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4269 }
4270 }
4271}
4272
4274{
4275 Q_UNUSED( element )
4276 return nullptr;
4277}
4278
4280{
4281 if ( !symbol )
4282 {
4283 return false;
4284 }
4285
4286 if ( symbol->type() == Qgis::SymbolType::Marker )
4287 {
4288 QgsMarkerSymbol *markerSymbol = static_cast<QgsMarkerSymbol *>( symbol );
4289 mMarkerSymbol.reset( markerSymbol );
4290 }
4291 return true;
4292}
4293
4295{
4296 return mMarkerSymbol.get();
4297}
4298
4300{
4304 && ( !mMarkerSymbol || !mMarkerSymbol->hasDataDefinedProperties() ) )
4305 {
4306 return;
4307 }
4308
4309 double distanceX = mDistanceX;
4311 {
4314 }
4315 double distanceY = mDistanceY;
4317 {
4320 }
4323 {
4326 }
4329 {
4332 }
4333 double offsetX = mOffsetX;
4335 {
4338 }
4339 double offsetY = mOffsetY;
4341 {
4344 }
4345 applyPattern( context, mBrush, distanceX, distanceY, displacementX, displacementY, offsetX, offsetY );
4346}
4347
4349{
4350 return 0;
4351}
4352
4354{
4355 QSet<QString> attributes = QgsImageFillSymbolLayer::usedAttributes( context );
4356
4357 if ( mMarkerSymbol )
4358 attributes.unite( mMarkerSymbol->usedAttributes( context ) );
4359
4360 return attributes;
4361}
4362
4364{
4366 return true;
4367 if ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
4368 return true;
4369 return false;
4370}
4371
4373{
4374 mColor = c;
4375 if ( mMarkerSymbol )
4376 mMarkerSymbol->setColor( c );
4377}
4378
4380{
4381 return mMarkerSymbol ? mMarkerSymbol->color() : mColor;
4382}
4383
4385
4386
4388{
4390}
4391
4393
4395{
4396 std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4397
4398 if ( properties.contains( QStringLiteral( "point_on_surface" ) ) )
4399 sl->setPointOnSurface( properties[QStringLiteral( "point_on_surface" )].toInt() != 0 );
4400 if ( properties.contains( QStringLiteral( "point_on_all_parts" ) ) )
4401 sl->setPointOnAllParts( properties[QStringLiteral( "point_on_all_parts" )].toInt() != 0 );
4402 if ( properties.contains( QStringLiteral( "clip_points" ) ) )
4403 sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() != 0 );
4404 if ( properties.contains( QStringLiteral( "clip_on_current_part_only" ) ) )
4405 sl->setClipOnCurrentPartOnly( properties[QStringLiteral( "clip_on_current_part_only" )].toInt() != 0 );
4406
4407 sl->restoreOldDataDefinedProperties( properties );
4408
4409 return sl.release();
4410}
4411
4413{
4414 return QStringLiteral( "CentroidFill" );
4415}
4416
4417void QgsCentroidFillSymbolLayer::setColor( const QColor &color )
4418{
4419 mMarker->setColor( color );
4420 mColor = color;
4421}
4422
4424{
4425 return mMarker ? mMarker->color() : mColor;
4426}
4427
4429{
4430 mMarker->startRender( context.renderContext(), context.fields() );
4431}
4432
4434{
4435 mMarker->stopRender( context.renderContext() );
4436}
4437
4438void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4439{
4440 Part part;
4441 part.exterior = points;
4442 if ( rings )
4443 part.rings = *rings;
4444
4445 if ( mRenderingFeature )
4446 {
4447 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
4448 // until after we've received the final part
4449 mFeatureSymbolOpacity = context.opacity();
4450 mCurrentParts << part;
4451 }
4452 else
4453 {
4454 // not rendering a feature, so we can just render the polygon immediately
4455 const double prevOpacity = mMarker->opacity();
4456 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
4457 render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), context.selected() );
4458 mMarker->setOpacity( prevOpacity );
4459 }
4460}
4461
4463{
4464 mRenderingFeature = true;
4465 mCurrentParts.clear();
4466}
4467
4469{
4470 mRenderingFeature = false;
4471
4472 const double prevOpacity = mMarker->opacity();
4473 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
4474
4475 render( context, mCurrentParts, feature, false );
4477 mMarker->setOpacity( prevOpacity );
4478}
4479
4480void QgsCentroidFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsCentroidFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
4481{
4484 bool clipPoints = mClipPoints;
4486
4487 // TODO add expressions support
4488
4489 QVector< QgsGeometry > geometryParts;
4490 geometryParts.reserve( parts.size() );
4491 QPainterPath globalPath;
4492
4493 int maxArea = 0;
4494 int maxAreaPartIdx = 0;
4495
4496 for ( int i = 0; i < parts.size(); i++ )
4497 {
4498 const Part part = parts[i];
4499 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
4500
4501 if ( !geom.isNull() && !part.rings.empty() )
4502 {
4503 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
4504
4505 if ( !pointOnAllParts )
4506 {
4507 int area = poly->area();
4508
4509 if ( area > maxArea )
4510 {
4511 maxArea = area;
4512 maxAreaPartIdx = i;
4513 }
4514 }
4515 }
4516
4518 {
4519 globalPath.addPolygon( part.exterior );
4520 for ( const QPolygonF &ring : part.rings )
4521 {
4522 globalPath.addPolygon( ring );
4523 }
4524 }
4525 }
4526
4527 for ( int i = 0; i < parts.size(); i++ )
4528 {
4529 if ( !pointOnAllParts && i != maxAreaPartIdx )
4530 continue;
4531
4532 const Part part = parts[i];
4533
4534 if ( clipPoints )
4535 {
4536 QPainterPath path;
4537
4539 {
4540 path.addPolygon( part.exterior );
4541 for ( const QPolygonF &ring : part.rings )
4542 {
4543 path.addPolygon( ring );
4544 }
4545 }
4546 else
4547 {
4548 path = globalPath;
4549 }
4550
4551 context.painter()->save();
4552 context.painter()->setClipPath( path );
4553 }
4554
4555 QPointF centroid = pointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( part.exterior, &part.rings ) : QgsSymbolLayerUtils::polygonCentroid( part.exterior );
4556
4557 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4559 mMarker->renderPoint( centroid, feature.isValid() ? &feature : nullptr, context, -1, selected );
4560 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
4561
4562 if ( clipPoints )
4563 {
4564 context.painter()->restore();
4565 }
4566 }
4567}
4568
4570{
4571 QVariantMap map;
4572 map[QStringLiteral( "point_on_surface" )] = QString::number( mPointOnSurface );
4573 map[QStringLiteral( "point_on_all_parts" )] = QString::number( mPointOnAllParts );
4574 map[QStringLiteral( "clip_points" )] = QString::number( mClipPoints );
4575 map[QStringLiteral( "clip_on_current_part_only" )] = QString::number( mClipOnCurrentPartOnly );
4576 return map;
4577}
4578
4580{
4581 std::unique_ptr< QgsCentroidFillSymbolLayer > x = std::make_unique< QgsCentroidFillSymbolLayer >();
4582 x->mAngle = mAngle;
4583 x->mColor = mColor;
4584 x->setSubSymbol( mMarker->clone() );
4585 x->setPointOnSurface( mPointOnSurface );
4586 x->setPointOnAllParts( mPointOnAllParts );
4587 x->setClipPoints( mClipPoints );
4588 x->setClipOnCurrentPartOnly( mClipOnCurrentPartOnly );
4589 copyDataDefinedProperties( x.get() );
4590 copyPaintEffect( x.get() );
4591 return x.release();
4592}
4593
4594void QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4595{
4596 // SLD 1.0 specs says: "if a line, polygon, or raster geometry is
4597 // used with PointSymbolizer, then the semantic is to use the centroid
4598 // of the geometry, or any similar representative point.
4599 mMarker->toSld( doc, element, props );
4600}
4601
4603{
4605 if ( !l )
4606 return nullptr;
4607
4608 QgsSymbolLayerList layers;
4609 layers.append( l );
4610 std::unique_ptr< QgsMarkerSymbol > marker( new QgsMarkerSymbol( layers ) );
4611
4612 std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4613 sl->setSubSymbol( marker.release() );
4614 sl->setPointOnAllParts( false );
4615 return sl.release();
4616}
4617
4618
4620{
4621 return mMarker.get();
4622}
4623
4625{
4626 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
4627 {
4628 delete symbol;
4629 return false;
4630 }
4631
4632 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
4633 mColor = mMarker->color();
4634 return true;
4635}
4636
4638{
4639 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
4640
4641 if ( mMarker )
4642 attributes.unite( mMarker->usedAttributes( context ) );
4643
4644 return attributes;
4645}
4646
4648{
4650 return true;
4651 if ( mMarker && mMarker->hasDataDefinedProperties() )
4652 return true;
4653 return false;
4654}
4655
4657{
4658 return true;
4659}
4660
4662{
4663 if ( mMarker )
4664 {
4665 mMarker->setOutputUnit( unit );
4666 }
4667}
4668
4670{
4671 if ( mMarker )
4672 {
4673 return mMarker->outputUnit();
4674 }
4675 return QgsUnitTypes::RenderUnknownUnit; //mOutputUnit;
4676}
4677
4679{
4680 if ( mMarker )
4681 {
4682 return mMarker->usesMapUnits();
4683 }
4684 return false;
4685}
4686
4688{
4689 if ( mMarker )
4690 {
4691 mMarker->setMapUnitScale( scale );
4692 }
4693}
4694
4696{
4697 if ( mMarker )
4698 {
4699 return mMarker->mapUnitScale();
4700 }
4701 return QgsMapUnitScale();
4702}
4703
4704
4705
4706
4709 , mImageFilePath( imageFilePath )
4710{
4711 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //disable sub symbol
4713}
4714
4716
4717QgsSymbolLayer *QgsRasterFillSymbolLayer::create( const QVariantMap &properties )
4718{
4720 double alpha = 1.0;
4721 QPointF offset;
4722 double angle = 0.0;
4723 double width = 0.0;
4724
4725 QString imagePath;
4726 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
4727 {
4728 imagePath = properties[QStringLiteral( "imageFile" )].toString();
4729 }
4730 if ( properties.contains( QStringLiteral( "coordinate_mode" ) ) )
4731 {
4732 mode = static_cast< Qgis::SymbolCoordinateReference >( properties[QStringLiteral( "coordinate_mode" )].toInt() );
4733 }
4734 if ( properties.contains( QStringLiteral( "alpha" ) ) )
4735 {
4736 alpha = properties[QStringLiteral( "alpha" )].toDouble();
4737 }
4738 if ( properties.contains( QStringLiteral( "offset" ) ) )
4739 {
4740 offset = QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "offset" )].toString() );
4741 }
4742 if ( properties.contains( QStringLiteral( "angle" ) ) )
4743 {
4744 angle = properties[QStringLiteral( "angle" )].toDouble();
4745 }
4746 if ( properties.contains( QStringLiteral( "width" ) ) )
4747 {
4748 width = properties[QStringLiteral( "width" )].toDouble();
4749 }
4750 std::unique_ptr< QgsRasterFillSymbolLayer > symbolLayer = std::make_unique< QgsRasterFillSymbolLayer >( imagePath );
4751 symbolLayer->setCoordinateMode( mode );
4752 symbolLayer->setOpacity( alpha );
4753 symbolLayer->setOffset( offset );
4754 symbolLayer->setAngle( angle );
4755 symbolLayer->setWidth( width );
4756 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
4757 {
4758 symbolLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
4759 }
4760 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
4761 {
4762 symbolLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
4763 }
4764 if ( properties.contains( QStringLiteral( "width_unit" ) ) )
4765 {
4766 symbolLayer->setWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "width_unit" )].toString() ) );
4767 }
4768 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
4769 {
4770 symbolLayer->setWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
4771 }
4772
4773 symbolLayer->restoreOldDataDefinedProperties( properties );
4774
4775 return symbolLayer.release();
4776}
4777
4778void QgsRasterFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
4779{
4780 QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
4781 if ( it != properties.end() )
4782 {
4783 if ( saving )
4784 it.value() = pathResolver.writePath( it.value().toString() );
4785 else
4786 it.value() = pathResolver.readPath( it.value().toString() );
4787 }
4788}
4789
4791{
4792 Q_UNUSED( symbol )
4793 return true;
4794}
4795
4797{
4798 return QStringLiteral( "RasterFill" );
4799}
4800
4801void QgsRasterFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4802{
4803 QPainter *p = context.renderContext().painter();
4804 if ( !p )
4805 {
4806 return;
4807 }
4808
4809 QPointF offset = mOffset;
4811 {
4813 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
4814 bool ok = false;
4815 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
4816 if ( ok )
4817 offset = res;
4818 }
4819 if ( !offset.isNull() )
4820 {
4821 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
4822 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
4823 p->translate( offset );
4824 }
4825 if ( mCoordinateMode == Qgis::SymbolCoordinateReference::Feature )
4826 {
4827 QRectF boundingRect = points.boundingRect();
4828 mBrush.setTransform( mBrush.transform().translate( boundingRect.left() - mBrush.transform().dx(),
4829 boundingRect.top() - mBrush.transform().dy() ) );
4830 }
4831
4832 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
4833 if ( !offset.isNull() )
4834 {
4835 p->translate( -offset );
4836 }
4837}
4838
4840{
4841 applyPattern( mBrush, mImageFilePath, mWidth, mOpacity * context.opacity(), context );
4842}
4843
4845{
4846 Q_UNUSED( context )
4847}
4848
4850{
4851 QVariantMap map;
4852 map[QStringLiteral( "imageFile" )] = mImageFilePath;
4853 map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
4854 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
4855 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
4856 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
4857 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
4858 map[QStringLiteral( "angle" )] = QString::number( mAngle );
4859 map[QStringLiteral( "width" )] = QString::number( mWidth );
4860 map[QStringLiteral( "width_unit" )] = QgsUnitTypes::encodeUnit( mWidthUnit );
4861 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mWidthMapUnitScale );
4862 return map;
4863}
4864
4866{
4867 std::unique_ptr< QgsRasterFillSymbolLayer > sl = std::make_unique< QgsRasterFillSymbolLayer >( mImageFilePath );
4868 sl->setCoordinateMode( mCoordinateMode );
4869 sl->setOpacity( mOpacity );
4870 sl->setOffset( mOffset );
4871 sl->setOffsetUnit( mOffsetUnit );
4872 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
4873 sl->setAngle( mAngle );
4874 sl->setWidth( mWidth );
4875 sl->setWidthUnit( mWidthUnit );
4876 sl->setWidthMapUnitScale( mWidthMapUnitScale );
4877 copyDataDefinedProperties( sl.get() );
4878 copyPaintEffect( sl.get() );
4879 return sl.release();
4880}
4881
4883{
4884 return context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
4885}
4886
4888{
4889 return mWidthUnit == QgsUnitTypes::RenderMapUnits || mWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
4890 || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
4891}
4892
4894{
4895 return QColor();
4896}
4897
4899{
4901 mOffsetUnit = unit;
4902 mWidthUnit = unit;
4903}
4904
4905void QgsRasterFillSymbolLayer::setImageFilePath( const QString &imagePath )
4906{
4907 mImageFilePath = imagePath;
4908}
4909
4911{
4912 mCoordinateMode = mode;
4913}
4914
4915void QgsRasterFillSymbolLayer::setOpacity( const double opacity )
4916{
4917 mOpacity = opacity;
4918}
4919
4921{
4922 if ( !dataDefinedProperties().hasActiveProperties() )
4923 return; // shortcut
4924
4929
4930 if ( !hasWidthExpression && !hasAngleExpression && !hasOpacityExpression && !hasFileExpression )
4931 {
4932 return; //no data defined settings
4933 }
4934
4935 bool ok;
4936 if ( hasAngleExpression )
4937 {
4940 if ( ok )
4941 mNextAngle = nextAngle;
4942 }
4943
4944 if ( !hasWidthExpression && !hasOpacityExpression && !hasFileExpression )
4945 {
4946 return; //nothing further to do
4947 }
4948
4949 double width = mWidth;
4950 if ( hasWidthExpression )
4951 {
4952 context.setOriginalValueVariable( mWidth );
4954 }
4955 double opacity = mOpacity;
4956 if ( hasOpacityExpression )
4957 {
4958 context.setOriginalValueVariable( mOpacity );
4960 }
4961 QString file = mImageFilePath;
4962 if ( hasFileExpression )
4963 {
4964 context.setOriginalValueVariable( mImageFilePath );
4966 }
4967 applyPattern( mBrush, file, width, opacity, context );
4968}
4969
4971{
4972 return false;
4973}
4974
4975void QgsRasterFillSymbolLayer::applyPattern( QBrush &brush, const QString &imageFilePath, const double width, const double alpha, const QgsSymbolRenderContext &context )
4976{
4977 QSize size;
4978 if ( width > 0 )
4979 {
4980 if ( mWidthUnit != QgsUnitTypes::RenderPercentage )
4981 {
4982 size.setWidth( context.renderContext().convertToPainterUnits( width, mWidthUnit, mWidthMapUnitScale ) );
4983 }
4984 else
4985 {
4986 // RenderPercentage Unit Type takes original image size
4988 if ( size.isEmpty() )
4989 return;
4990
4991 size.setWidth( ( width * size.width() ) / 100.0 );
4992
4993 // don't render symbols with size below one or above 10,000 pixels
4994 if ( static_cast< int >( size.width() ) < 1 || 10000.0 < size.width() )
4995 return;
4996 }
4997
4998 size.setHeight( 0 );
4999 }
5000
5001 bool cached;
5002 QImage img = QgsApplication::imageCache()->pathAsImage( imageFilePath, size, true, alpha, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
5003 if ( img.isNull() )
5004 return;
5005
5006 brush.setTextureImage( img );
5007}
5008
5009
5010//
5011// QgsRandomMarkerFillSymbolLayer
5012//
5013
5014QgsRandomMarkerFillSymbolLayer::QgsRandomMarkerFillSymbolLayer( int pointCount, Qgis::PointCountMethod method, double densityArea, unsigned long seed )
5015 : mCountMethod( method )
5016 , mPointCount( pointCount )
5017 , mDensityArea( densityArea )
5018 , mSeed( seed )
5019{
5021}
5022
5024
5026{
5027 const Qgis::PointCountMethod countMethod = static_cast< Qgis::PointCountMethod >( properties.value( QStringLiteral( "count_method" ), QStringLiteral( "0" ) ).toInt() );
5028 const int pointCount = properties.value( QStringLiteral( "point_count" ), QStringLiteral( "10" ) ).toInt();
5029 const double densityArea = properties.value( QStringLiteral( "density_area" ), QStringLiteral( "250.0" ) ).toDouble();
5030
5031 unsigned long seed = 0;
5032 if ( properties.contains( QStringLiteral( "seed" ) ) )
5033 seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
5034 else
5035 {
5036 // if we a creating a new random marker fill from scratch, we default to a random seed
5037 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
5038 std::random_device rd;
5039 std::mt19937 mt( seed == 0 ? rd() : seed );
5040 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
5041 seed = uniformDist( mt );
5042 }
5043
5044 std::unique_ptr< QgsRandomMarkerFillSymbolLayer > sl = std::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, countMethod, densityArea, seed );
5045
5046 if ( properties.contains( QStringLiteral( "density_area_unit" ) ) )
5047 sl->setDensityAreaUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "density_area_unit" )].toString() ) );
5048 if ( properties.contains( QStringLiteral( "density_area_unit_scale" ) ) )
5049 sl->setDensityAreaUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "density_area_unit_scale" )].toString() ) );
5050
5051 if ( properties.contains( QStringLiteral( "clip_points" ) ) )
5052 {
5053 sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() );
5054 }
5055
5056 return sl.release();
5057}
5058
5060{
5061 return QStringLiteral( "RandomMarkerFill" );
5062}
5063
5065{
5066 mMarker->setColor( color );
5067 mColor = color;
5068}
5069
5071{
5072 return mMarker ? mMarker->color() : mColor;
5073}
5074
5076{
5077 mMarker->startRender( context.renderContext(), context.fields() );
5078}
5079
5081{
5082 mMarker->stopRender( context.renderContext() );
5083}
5084
5085void QgsRandomMarkerFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5086{
5087 Part part;
5088 part.exterior = points;
5089 if ( rings )
5090 part.rings = *rings;
5091
5092 if ( mRenderingFeature )
5093 {
5094 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
5095 // until after we've received the final part
5096 mFeatureSymbolOpacity = context.opacity();
5097 mCurrentParts << part;
5098 }
5099 else
5100 {
5101 // not rendering a feature, so we can just render the polygon immediately
5102 const double prevOpacity = mMarker->opacity();
5103 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
5104 render( context.renderContext(), QVector< Part>() << part, context.feature() ? *context.feature() : QgsFeature(), context.selected() );
5105 mMarker->setOpacity( prevOpacity );
5106 }
5107}
5108
5109void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsRandomMarkerFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
5110{
5111 bool clipPoints = mClipPoints;
5113 {
5116 }
5117
5118 QVector< QgsGeometry > geometryParts;
5119 geometryParts.reserve( parts.size() );
5120 QPainterPath path;
5121
5122 for ( const Part &part : parts )
5123 {
5124 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
5125 if ( !geom.isNull() && !part.rings.empty() )
5126 {
5127 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
5128 for ( const QPolygonF &ring : part.rings )
5129 {
5131 }
5132 }
5133 if ( !geom.isGeosValid() )
5134 {
5135 geom = geom.buffer( 0, 0 );
5136 }
5137 geometryParts << geom;
5138
5139 if ( clipPoints )
5140 {
5141 path.addPolygon( part.exterior );
5142 for ( const QPolygonF &ring : part.rings )
5143 {
5144 path.addPolygon( ring );
5145 }
5146 }
5147 }
5148
5149 const QgsGeometry geom = geometryParts.count() != 1 ? QgsGeometry::unaryUnion( geometryParts ) : geometryParts.at( 0 );
5150
5151 if ( clipPoints )
5152 {
5153 context.painter()->save();
5154 context.painter()->setClipPath( path );
5155 }
5156
5157
5158 int count = mPointCount;
5160 {
5163 }
5164
5165 switch ( mCountMethod )
5166 {
5167 case Qgis::PointCountMethod::DensityBased:
5168 {
5169 double densityArea = mDensityArea;
5171 {
5174 }
5175 densityArea = context.convertToPainterUnits( std::sqrt( densityArea ), mDensityAreaUnit, mDensityAreaUnitScale );
5176 densityArea = std::pow( densityArea, 2 );
5177 count = std::max( 0.0, std::ceil( count * ( geom.area() / densityArea ) ) );
5178 break;
5179 }
5180 case Qgis::PointCountMethod::Absolute:
5181 break;
5182 }
5183
5184 unsigned long seed = mSeed;
5186 {
5187 context.expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
5189 }
5190
5191 QVector< QgsPointXY > randomPoints = geom.randomPointsInPolygon( count, seed );
5192#if 0
5193 // in some cases rendering from top to bottom is nice (e.g. randomised tree markers), but in other cases it's not wanted..
5194 // TODO consider exposing this as an option
5195 std::sort( randomPoints.begin(), randomPoints.end(), []( const QgsPointXY & a, const QgsPointXY & b )->bool
5196 {
5197 return a.y() < b.y();
5198 } );
5199#endif
5201 QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scope );
5202 int pointNum = 0;
5203 const bool needsExpressionContext = mMarker->hasDataDefinedProperties();
5204
5205 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
5207
5208 for ( const QgsPointXY &p : std::as_const( randomPoints ) )
5209 {
5210 if ( needsExpressionContext )
5212 mMarker->renderPoint( QPointF( p.x(), p.y() ), feature.isValid() ? &feature : nullptr, context, -1, selected );
5213 }
5214
5215 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
5216
5217 if ( clipPoints )
5218 {
5219 context.painter()->restore();
5220 }
5221}
5222
5224{
5225 QVariantMap map;
5226 map.insert( QStringLiteral( "count_method" ), QString::number( static_cast< int >( mCountMethod ) ) );
5227 map.insert( QStringLiteral( "point_count" ), QString::number( mPointCount ) );
5228 map.insert( QStringLiteral( "density_area" ), QString::number( mDensityArea ) );
5229 map.insert( QStringLiteral( "density_area_unit" ), QgsUnitTypes::encodeUnit( mDensityAreaUnit ) );
5230 map.insert( QStringLiteral( "density_area_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDensityAreaUnitScale ) );
5231 map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
5232 map.insert( QStringLiteral( "clip_points" ), QString::number( mClipPoints ) );
5233 return map;
5234}
5235
5237{
5238 std::unique_ptr< QgsRandomMarkerFillSymbolLayer > res = std::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mCountMethod, mDensityArea, mSeed );
5239 res->mAngle = mAngle;
5240 res->mColor = mColor;
5241 res->setDensityAreaUnit( mDensityAreaUnit );
5242 res->setDensityAreaUnitScale( mDensityAreaUnitScale );
5243 res->mClipPoints = mClipPoints;
5244 res->setSubSymbol( mMarker->clone() );
5245 copyDataDefinedProperties( res.get() );
5246 copyPaintEffect( res.get() );
5247 return res.release();
5248}
5249
5251{
5252 return true;
5253}
5254
5256{
5257 return mMarker.get();
5258}
5259
5261{
5262 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5263 {
5264 delete symbol;
5265 return false;
5266 }
5267
5268 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5269 mColor = mMarker->color();
5270 return true;
5271}
5272
5274{
5275 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5276
5277 if ( mMarker )
5278 attributes.unite( mMarker->usedAttributes( context ) );
5279
5280 return attributes;
5281}
5282
5284{
5286 return true;
5287 if ( mMarker && mMarker->hasDataDefinedProperties() )
5288 return true;
5289 return false;
5290}
5291
5293{
5294 return mPointCount;
5295}
5296
5298{
5299 mPointCount = pointCount;
5300}
5301
5303{
5304 return mSeed;
5305}
5306
5308{
5309 mSeed = seed;
5310}
5311
5313{
5314 return mClipPoints;
5315}
5316
5318{
5319 mClipPoints = clipPoints;
5320}
5321
5323{
5324 return mCountMethod;
5325}
5326
5328{
5329 mCountMethod = method;
5330}
5331
5333{
5334 return mDensityArea;
5335}
5336
5338{
5339 mDensityArea = area;
5340}
5341
5343{
5344 mRenderingFeature = true;
5345 mCurrentParts.clear();
5346}
5347
5349{
5350 mRenderingFeature = false;
5351
5352 const double prevOpacity = mMarker->opacity();
5353 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
5354
5355 render( context, mCurrentParts, feature, false );
5356
5357 mFeatureSymbolOpacity = 1;
5358 mMarker->setOpacity( prevOpacity );
5359}
5360
5361
5363{
5364 mDensityAreaUnit = unit;
5365 if ( mMarker )
5366 {
5367 mMarker->setOutputUnit( unit );
5368 }
5369}
5370
5372{
5373 if ( mMarker )
5374 {
5375 return mMarker->outputUnit();
5376 }
5377 return QgsUnitTypes::RenderUnknownUnit; //mOutputUnit;
5378}
5379
5381{
5382 if ( mMarker )
5383 {
5384 return mMarker->usesMapUnits();
5385 }
5386 return false;
5387}
5388
5390{
5391 if ( mMarker )
5392 {
5393 mMarker->setMapUnitScale( scale );
5394 }
5395}
5396
5398{
5399 if ( mMarker )
5400 {
5401 return mMarker->mapUnitScale();
5402 }
5403 return QgsMapUnitScale();
5404}
5405
MarkerClipMode
Marker clipping modes.
Definition: qgis.h:1674
@ CompletelyWithin
Render complete markers wherever the completely fall within the polygon shape.
@ NoClipping
No clipping, render complete markers.
@ Shape
Clip to polygon shape.
@ CentroidWithin
Render complete markers wherever their centroid falls within the polygon shape.
LineClipMode
Line clipping modes.
Definition: qgis.h:1688
@ NoClipping
Lines are not clipped, will extend to shape's bounding box.
@ ClipPainterOnly
Applying clipping on the painter only (i.e. line endpoints will coincide with polygon bounding box,...
@ ClipToIntersection
Clip lines to intersection with polygon shape (slower) (i.e. line endpoints will coincide with polygo...
GradientColorSource
Gradient color sources.
Definition: qgis.h:1603
@ ColorRamp
Gradient color ramp.
@ SimpleTwoColor
Simple two color gradient.
GradientSpread
Gradient spread options, which control how gradients are rendered outside of their start and end poin...
Definition: qgis.h:1647
@ Repeat
Repeat gradient.
@ Reflect
Reflect gradient.
@ Pad
Pad out gradient using colors at endpoint of gradient.
PointCountMethod
Methods which define the number of points randomly filling a polygon.
Definition: qgis.h:1662
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
GradientType
Gradient types.
Definition: qgis.h:1617
@ Linear
Linear gradient.
@ Conical
Conical (polar) gradient.
@ Radial
Radial (circular) gradient.
@ Marker
Marker symbol.
@ Line
Line symbol.
SymbolCoordinateReference
Symbol coordinate reference modes.
Definition: qgis.h:1632
@ Feature
Relative to feature/shape being rendered.
@ Viewport
Relative to the whole viewport/output device.
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color.
int valueAsInt(int key, const QgsExpressionContext &context, int defaultValue=0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an integer.
bool valueAsBool(int key, const QgsExpressionContext &context, bool defaultValue=false, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an boolean.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QgsSymbolLayer * createFromSld(QDomElement &element)
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
void setMapUnitScale(const QgsMapUnitScale &scale) override
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
QgsMapUnitScale mapUnitScale() const override
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
std::unique_ptr< QgsMarkerSymbol > mMarker
QString layerType() const override
Returns a string that represents this layer type.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
bool pointOnAllParts() const
Returns whether a point is drawn for all parts or only on the biggest part of multi-part features.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
bool clipPoints() const
Returns true if point markers should be clipped to the polygon boundary.
QgsCentroidFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsCentroidFillSymbolLayer using the specified properties map containing symbol propert...
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
~QgsCentroidFillSymbolLayer() override
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QColor color() const override
Returns the "representative" color of the symbol layer.
bool clipOnCurrentPartOnly() const
Returns true if point markers should be clipped to the current part boundary only.
Abstract base class for color ramps.
Definition: qgscolorramp.h:30
virtual QColor color(double value) const =0
Returns the color corresponding to a specified value.
virtual QVariantMap properties() const =0
Returns a string map containing all the color ramp's properties.
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
virtual QString type() const =0
Returns a string representing the color ramp type.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
double area() const override SIP_HOLDGIL
Returns the planar, 2-dimensional area of the geometry.
Exports QGIS layers to the DXF format.
Definition: qgsdxfexport.h:65
static double mapUnitScaleFactor(double scale, QgsUnitTypes::RenderUnit symbolUnits, QgsUnitTypes::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
QgsUnitTypes::DistanceUnit mapUnits() const
Retrieve map units.
double symbologyScale() const
Returns the reference scale for output.
Definition: qgsdxfexport.h:229
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
static const QString EXPR_GEOMETRY_POINT_NUM
Inbuilt variable name for point number variable.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:219
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void _renderPolygon(QPainter *p, const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context)
Default method to render polygon.
double angle() const
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
QVector< QgsPointXY > randomPointsInPolygon(int count, const std::function< bool(const QgsPointXY &) > &acceptPoint, unsigned long seed=0, QgsFeedback *feedback=nullptr, int maxTriesPerPoint=0) const
Returns a list of count random points generated inside a (multi)polygon geometry (if acceptPoint is s...
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
Q_GADGET bool isNull
Definition: qgsgeometry.h:166
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
bool isGeosValid(Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Checks validity of the geometry using GEOS.
double area() const
Returns the planar, 2-dimensional area of the geometry.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine representing the specified geometry.
QgsGeometry buffer(double distance, int segments) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsColorRamp from a map of properties.
static QString typeString()
Returns the string identifier for QgsGradientColorRamp.
void addStopsToGradient(QGradient *gradient, double opacity=1) const
Copy color ramp stops to a QGradient.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used for the gradient fill.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit mOffsetUnit
QColor color2() const
Returns the color for endpoint of gradient, only used if the gradient color type is set to SimpleTwoC...
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
QgsGradientFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
QgsMapUnitScale mapUnitScale() const override
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
Qgis::SymbolCoordinateReference coordinateMode() const
Returns the coordinate mode for gradient, which controls how the gradient stops are positioned.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
Qgis::SymbolCoordinateReference mCoordinateMode
QgsGradientFillSymbolLayer(const QColor &color=DEFAULT_SIMPLEFILL_COLOR, const QColor &color2=Qt::white, Qgis::GradientColorSource gradientColorType=Qgis::GradientColorSource::SimpleTwoColor, Qgis::GradientType gradientType=Qgis::GradientType::Linear, Qgis::SymbolCoordinateReference coordinateMode=Qgis::SymbolCoordinateReference::Feature, Qgis::GradientSpread gradientSpread=Qgis::GradientSpread::Pad)
Constructor for QgsGradientFillSymbolLayer.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsGradientFillSymbolLayer using the specified properties map containing symbol propert...
void setMapUnitScale(const QgsMapUnitScale &scale) override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
Qgis::GradientSpread mGradientSpread
Qgis::GradientType mGradientType
QPointF referencePoint1() const
Returns the starting point of gradient fill, in the range [0,0] - [1,1].
Qgis::GradientSpread gradientSpread() const
Returns the gradient spread mode, which controls how the gradient behaves outside of the predefined s...
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
Qgis::GradientColorSource gradientColorType() const
Returns the gradient color mode, which controls how gradient color stops are created.
QPointF offset() const
Returns the offset by which polygons will be translated during rendering.
Qgis::GradientColorSource mGradientColorType
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QString layerType() const override
Returns a string that represents this layer type.
Qgis::GradientType gradientType() const
Returns the type of gradient, e.g., linear or radial.
QPointF referencePoint2() const
Returns the end point of gradient fill, in the range [0,0] - [1,1].
QSize originalSize(const QString &path, bool blocking=false) const
Returns the original size (in pixels) of the image at the specified path.
QImage pathAsImage(const QString &path, const QSize size, const bool keepAspectRatio, const double opacity, bool &fitsInCache, bool blocking=false, double targetDpi=96, int frameNumber=-1, bool *isMissing=nullptr)
Returns the specified path rendered as an image.
Base class for polygon renderers generating texture images.
QgsMapUnitScale mStrokeWidthMapUnitScale
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
Qgis::SymbolCoordinateReference coordinateReference() const
Returns the coordinate reference mode for fill which controls how the top left corner of the image fi...
double mStrokeWidth
Stroke width.
Qgis::SymbolCoordinateReference mCoordinateReference
double dxfWidth(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets line width.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsMapUnitScale mapUnitScale() const override
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
void setMapUnitScale(const QgsMapUnitScale &scale) override
virtual void applyDataDefinedSettings(QgsSymbolRenderContext &context)
Applies data defined settings prior to generating the fill symbol brush.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
QgsUnitTypes::RenderUnit mStrokeWidthUnit
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
~QgsImageFillSymbolLayer() override
virtual bool applyBrushTransformFromContext(QgsSymbolRenderContext *context=nullptr) const
Returns true if the image brush should be transformed using the render context's texture origin.
static void multiplyOpacity(QImage &image, double factor, QgsFeedback *feedback=nullptr)
Multiplies opacity of image pixel values by a factor.
static void stackBlur(QImage &image, int radius, bool alphaOnly=false, QgsFeedback *feedback=nullptr)
Performs a stack blur on an image.
A symbol fill consisting of repeated parallel lines.
QgsMapUnitScale mapUnitScale() const override
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QString layerType() const override
Returns a string that represents this layer type.
QgsLinePatternFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
QColor color() const override
Returns the "representative" color of the symbol layer.
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
double lineWidth() const
Returns the width of the line subsymbol used to render the parallel lines in the fill.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void setMapUnitScale(const QgsMapUnitScale &scale) override
Qgis::LineClipMode clipMode() const
Returns the line clipping mode, which defines how lines are clipped at the edges of shapes.
double lineAngle() const
Returns the angle for the parallel lines used to fill the symbol.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setLineWidth(double w)
Sets the width of the line subsymbol used to render the parallel lines in the fill.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
Applies data defined settings prior to generating the fill symbol brush.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
double offset() const
Returns the offset distance for lines within the fill, which is the distance to offset the parallel l...
double distance() const
Returns the distance between lines in the fill pattern.
QString ogrFeatureStyleWidth(double widthScaleFactor) const
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
~QgsLinePatternFillSymbolLayer() override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsLinePatternFillSymbolLayer from a SLD element.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsLinePatternFillSymbolLayer from a properties map.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
QPolygonF asQPolygonF() const override
Returns a QPolygonF representing the points.
static QgsLineString * fromQPolygonF(const QPolygonF &polygon)
Returns a new linestring from a QPolygonF polygon input.
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgslinesymbol.h:30
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns the current map units per pixel.
Struct for storing maximum and minimum scales for measurements in map units.
Line symbol layer type which draws repeating marker symbols along a line feature.
Abstract base class for marker symbol layers.
A marker symbol type, for rendering Point and MultiPoint geometries.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
A fill symbol layer which fills polygon shapes with repeating marker symbols.
QgsMapUnitScale mapUnitScale() const override
QgsUnitTypes::RenderUnit mRandomDeviationYUnit
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
double distanceX() const
Returns the horizontal distance between rendered markers in the fill.
QgsUnitTypes::RenderUnit mDisplacementXUnit
double displacementY() const
Returns the vertical displacement for odd numbered columns in the pattern.
QgsUnitTypes::RenderUnit mDistanceYUnit
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
QgsUnitTypes::RenderUnit mOffsetXUnit
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsPointPatternFillSymbolLayer using the specified properties map containing symbol pro...
unsigned long seed() const
Returns the random number seed to use when randomly shifting points, or 0 if a truly random sequence ...
Qgis::MarkerClipMode clipMode() const
Returns the marker clipping mode, which defines how markers are clipped at the edges of shapes.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
double offsetY() const
Returns the vertical offset values for points in the pattern.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
void setMapUnitScale(const QgsMapUnitScale &scale) override
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QString layerType() const override
Returns a string that represents this layer type.
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
Applies data defined settings prior to generating the fill symbol brush.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QgsUnitTypes::RenderUnit mRandomDeviationXUnit
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
QColor color() const override
Returns the "representative" color of the symbol layer.
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
double offsetX() const
Returns the horizontal offset values for points in the pattern.
std::unique_ptr< QgsMarkerSymbol > mMarkerSymbol
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
QgsPointPatternFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
QgsUnitTypes::RenderUnit mDistanceXUnit
QgsUnitTypes::RenderUnit mOffsetYUnit
double displacementX() const
Returns the horizontal displacement for odd numbered rows in the pattern.
double angle() const
Returns the rotation angle of the pattern, in degrees clockwise.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
~QgsPointPatternFillSymbolLayer() override
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
double distanceY() const
Returns the vertical distance between rendered markers in the fill.
QgsUnitTypes::RenderUnit mDisplacementYUnit
void setClipMode(Qgis::MarkerClipMode mode)
Sets the marker clipping mode, which defines how markers are clipped at the edges of shapes.
static QgsSymbolLayer * createFromSld(QDomElement &element)
A class to represent a 2D point.
Definition: qgspointxy.h:59
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
Q_GADGET double x
Definition: qgspoint.h:52
double y
Definition: qgspoint.h:53
Polygon geometry type.
Definition: qgspolygon.h:34
void addInteriorRing(QgsCurve *ring) override
Adds an interior ring to the geometry (takes ownership)
Definition: qgspolygon.cpp:188
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
static QVariantMap propertyMapToVariantMap(const QMap< QString, QgsProperty > &propertyMap)
Convert a map of QgsProperty to a map of QVariant This is useful to save a map of properties.
static QMap< QString, QgsProperty > variantMapToPropertyMap(const QVariantMap &variantMap)
Convert a map of QVariant to a map of QgsProperty This is useful to restore a map of properties.
A fill symbol layer which places markers at random locations within polygons.
~QgsRandomMarkerFillSymbolLayer() override
int pointCount() const
Returns the count of random points to render in the fill.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void startFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called before the layer will be rendered for a particular feature.
QgsRandomMarkerFillSymbolLayer(int pointCount=10, Qgis::PointCountMethod method=Qgis::PointCountMethod::Absolute, double densityArea=250.0, unsigned long seed=0)
Constructor for QgsRandomMarkerFillSymbolLayer, with the specified pointCount.
unsigned long seed() const
Returns the random number seed to use when generating points, or 0 if a truly random sequence will be...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsRandomMarkerFillSymbolLayer using the specified properties map containing symbol pro...
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QString layerType() const override
Returns a string that represents this layer type.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
QgsRandomMarkerFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
void setMapUnitScale(const QgsMapUnitScale &scale) override
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
void setCountMethod(Qgis::PointCountMethod method)
Sets the count method used to randomly fill the polygon.
bool clipPoints() const
Returns true if point markers should be clipped to the polygon boundary.
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
void setClipPoints(bool clipped)
Sets whether point markers should be clipped to the polygon boundary.
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QColor color() const override
Returns the "representative" color of the symbol layer.
void setSeed(unsigned long seed)
Sets the random number seed to use when generating points, or 0 if a truly random sequence will be us...
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
void setPointCount(int count)
Sets the count of random points to render in the fill.
Qgis::PointCountMethod countMethod() const
Returns the count method used to randomly fill the polygon.
double densityArea() const
Returns the density area used to count the number of points to randomly fill the polygon.
void setColor(const QColor &color) override
Sets the "representative" color for the symbol layer.
void setDensityArea(double area)
Sets the density area used to count the number of points to randomly fill the polygon.
QgsMapUnitScale mapUnitScale() const override
A class for filling symbols with a repeated raster image.
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
Applies data defined settings prior to generating the fill symbol brush.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
double width() const
Returns the width used for scaling the image used in the fill.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsRasterFillSymbolLayer from a properties map.
QString layerType() const override
Returns a string that represents this layer type.
QgsRasterFillSymbolLayer(const QString &imageFilePath=QString())
Constructor for QgsRasterFillSymbolLayer, using a raster fill from the specified imageFilePath.
double opacity() const
Returns the opacity for the raster image used in the fill.
~QgsRasterFillSymbolLayer() override
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setOpacity(double opacity)
Sets the opacity for the raster image used in the fill.
QColor color() const override
Returns the "representative" color of the symbol layer.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QString imageFilePath() const
The path to the raster image used for the fill.
void setImageFilePath(const QString &imagePath)
Sets the path to the raster image used for the fill.
static void resolvePaths(QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving)
Turns relative paths in properties map to absolute when reading and vice versa when writing.
QPointF offset() const
Returns the offset for the fill.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QgsRasterFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
bool applyBrushTransformFromContext(QgsSymbolRenderContext *context=nullptr) const override
Returns true if the image brush should be transformed using the render context's texture origin.
void setCoordinateMode(Qgis::SymbolCoordinateReference mode)
Set the coordinate mode for fill.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
double rendererScale() const
Returns the renderer map scale.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QgsExpressionContext & expressionContext()
Gets the expression context.
bool forceVectorOutput() const
Returns true if rendering operations should use vector operations instead of any faster raster shortc...
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QColor selectionColor() const
Returns the color to use when rendering selected features.
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
QPointF textureOrigin() const
Returns the texture origin, which should be used as a brush transform when rendering using QBrush obj...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
void setRendererScale(double scale)
Sets the renderer map scale.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
const QgsPathResolver & pathResolver() const
Returns the path resolver for conversion between relative and absolute paths during rendering operati...
A class for filling symbols with a repeated SVG file.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
void setParameters(const QMap< QString, QgsProperty > &parameters)
Sets the dynamic SVG parameters.
QString svgFilePath() const
Returns the path to the SVG file used to render the fill.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QColor dxfColor(QgsSymbolRenderContext &context) const override
Gets color.
static QgsSymbolLayer * createFromSld(QDomElement &element)
Creates a new QgsSVGFillSymbolLayer from a SLD element.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
bool setSubSymbol(QgsSymbol *symbol) override
Sets layer's subsymbol. takes ownership of the passed symbol.
QColor svgStrokeColor() const
Returns the stroke color used for rendering the SVG content.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QgsUnitTypes::RenderUnit svgStrokeWidthUnit() const
Returns the units for the stroke width.
const QgsMapUnitScale & svgStrokeWidthMapUnitScale() const
Returns the map unit scale for the pattern's stroke.
double svgStrokeWidth() const
Returns the stroke width used for rendering the SVG content.
QString layerType() const override
Returns a string that represents this layer type.
QMap< QString, QgsProperty > parameters() const
Returns the dynamic SVG parameters.
QgsSVGFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns the set of attributes referenced by the layer.
QgsSVGFillSymbolLayer(const QString &svgFilePath, double width=20, double rotation=0.0)
Constructor for QgsSVGFillSymbolLayer, using the SVG picture at the specified absolute file path.
void setSvgFilePath(const QString &svgPath)
Sets the path to the SVG file to render in the fill.
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSVGFillSymbolLayer from a properties map.
QColor svgFillColor() const
Returns the fill color used for rendering the SVG content.
static void resolvePaths(QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving)
Turns relative paths in properties map to absolute when reading and vice versa when writing.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
const QgsMapUnitScale & patternWidthMapUnitScale() const
Returns the map unit scale for the pattern's width.
double patternWidth() const
Returns the width of the rendered SVG content within the fill (i.e.
bool hasDataDefinedProperties() const override
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
~QgsSVGFillSymbolLayer() override
QgsUnitTypes::RenderUnit patternWidthUnit() const
Returns the units for the width of the SVG images in the pattern.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
void applyDataDefinedSettings(QgsSymbolRenderContext &context) override
Applies data defined settings prior to generating the fill symbol brush.
QgsMapUnitScale mapUnitScale() const override
QgsSymbol * subSymbol() override
Returns the symbol's sub symbol, if present.
Scoped object for saving and restoring a QPainter object's state.
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QgsShapeburstFillSymbolLayer(const QColor &color=DEFAULT_SIMPLEFILL_COLOR, const QColor &color2=Qt::white, Qgis::GradientColorSource colorType=Qgis::GradientColorSource::SimpleTwoColor, int blurRadius=0, bool useWholeShape=true, double maxDistance=5)
Constructor for QgsShapeburstFillSymbolLayer.
QgsShapeburstFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
~QgsShapeburstFillSymbolLayer() override
int blurRadius() const
Returns the blur radius, which controls the amount of blurring applied to the fill.
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QColor color2() const
Returns the color used for the endpoint of the shapeburst fill.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QgsMapUnitScale mapUnitScale() const override
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
bool canCauseArtifactsBetweenAdjacentTiles() const override
Returns true if the symbol layer rendering can cause visible artifacts across a single feature when t...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsShapeburstFillSymbolLayer using the specified properties map containing symbol prope...
QPointF offset() const
Returns the offset for the shapeburst fill.
bool useWholeShape() const
Returns whether the shapeburst fill is set to cover the entire shape.
bool ignoreRings() const
Returns whether the shapeburst fill is set to ignore polygon interior rings.
double maxDistance() const
Returns the maximum distance from the shape's boundary which is shaded.
QString layerType() const override
Returns a string that represents this layer type.
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
Qgis::GradientColorSource colorType() const
Returns the color mode used for the shapeburst fill.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used to draw the shapeburst fill.
QgsSimpleFillSymbolLayer(const QColor &color=DEFAULT_SIMPLEFILL_COLOR, Qt::BrushStyle style=DEFAULT_SIMPLEFILL_STYLE, const QColor &strokeColor=DEFAULT_SIMPLEFILL_BORDERCOLOR, Qt::PenStyle strokeStyle=DEFAULT_SIMPLEFILL_BORDERSTYLE, double strokeWidth=DEFAULT_SIMPLEFILL_BORDERWIDTH, Qt::PenJoinStyle penJoinStyle=DEFAULT_SIMPLEFILL_JOINSTYLE)
double dxfWidth(const QgsDxfExport &e, QgsSymbolRenderContext &context) const override
Gets line width.
Qt::PenJoinStyle penJoinStyle() const
QColor strokeColor() const override
Returns the stroke color for the symbol layer.
QColor dxfBrushColor(QgsSymbolRenderContext &context) const override
Gets brush/fill color.
bool usesMapUnits() const override
Returns true if the symbol layer has any components which use map unit based sizes.
Qt::BrushStyle dxfBrushStyle() const override
Gets brush/fill style.
QString ogrFeatureStyle(double mmScaleFactor, double mapUnitScaleFactor) const override
void startRender(QgsSymbolRenderContext &context) override
Called before a set of rendering operations commences on the supplied render context.
QgsUnitTypes::RenderUnit mOffsetUnit
~QgsSimpleFillSymbolLayer() override
void toSld(QDomDocument &doc, QDomElement &element, const QVariantMap &props) const override
Saves the symbol layer as SLD.
double estimateMaxBleed(const QgsRenderContext &context) const override
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
QgsUnitTypes::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
QColor dxfColor(QgsSymbolRenderContext &context) const override
Gets color.
QColor fillColor() const override
Returns the fill color for the symbol layer.
Qt::PenStyle strokeStyle() const
QString layerType() const override
Returns a string that represents this layer type.
double dxfAngle(QgsSymbolRenderContext &context) const override
Gets angle.
QgsMapUnitScale mOffsetMapUnitScale
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QPointF offset() const
Returns the offset by which polygons will be translated during rendering.
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
QgsMapUnitScale mStrokeWidthMapUnitScale
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
QgsSimpleFillSymbolLayer * clone() const override
Shall be reimplemented by subclasses to create a deep copy of the instance.
QgsMapUnitScale mapUnitScale() const override
Qt::PenJoinStyle mPenJoinStyle
void setOutputUnit(QgsUnitTypes::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
QVariantMap properties() const override
Should be reimplemented by subclasses to return a string map that contains the configuration informat...
static QgsSymbolLayer * create(const QVariantMap &properties=QVariantMap())
Creates a new QgsSimpleFillSymbolLayer using the specified properties map containing symbol propertie...
static QgsSymbolLayer * createFromSld(QDomElement &element)
QgsUnitTypes::RenderUnit mStrokeWidthUnit
QByteArray getImageData(const QString &path, bool blocking=false) const
Gets the SVG content corresponding to the given path.
QPicture svgAsPicture(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool forceVectorOutput=false, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >())
Returns an SVG drawing as a QPicture.
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor, bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking=false) const
Tests if an SVG file contains parameters for fill, stroke color, stroke width.
QImage svgAsImage(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >())
Returns an SVG drawing as a QImage.
static bool rotationFromSldElement(QDomElement &element, QString &rotationFunc)
static Qgis::MarkerClipMode decodeMarkerClipMode(const QString &string, bool *ok=nullptr)
Decodes a string representing a marker clip mode.
static QString encodePenStyle(Qt::PenStyle style)
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QgsStringMap evaluatePropertiesMap(const QMap< QString, QgsProperty > &propertiesMap, const QgsExpressionContext &context)
Evaluates a map of properties using the given context and returns a variant map with evaluated expres...
static bool displacementFromSldElement(QDomElement &element, QPointF &offset)
static QColor decodeColor(const QString &str)
static QPointF polygonCentroid(const QPolygonF &points)
Calculate the centroid point of a QPolygonF.
static QString encodeBrushStyle(Qt::BrushStyle style)
static QString svgSymbolPathToName(const QString &path, const QgsPathResolver &pathResolver)
Determines an SVG symbol's name from its path.
static QPointF polygonPointOnSurface(const QPolygonF &points, const QVector< QPolygonF > *rings=nullptr)
Calculate a point on the surface of a QPolygonF.
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
static double rescaleUom(double size, QgsUnitTypes::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
static void multiplyImageOpacity(QImage *image, qreal opacity)
Multiplies opacity of image pixel values with a (global) transparency value.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QString ogrFeatureStylePen(double width, double mmScaleFactor, double mapUnitsScaleFactor, const QColor &c, Qt::PenJoinStyle joinStyle=Qt::MiterJoin, Qt::PenCapStyle capStyle=Qt::FlatCap, double offset=0.0, const QVector< qreal > *dashPattern=nullptr)
Create ogr feature style string for pen.
static QgsUnitTypes::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
static bool externalGraphicFromSld(QDomElement &element, QString &path, QString &mime, QColor &color, double &size)
static void parametricSvgToSld(QDomDocument &doc, QDomElement &graphicElem, const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth)
Encodes a reference to a parametric SVG into SLD, as a succession of parametric SVG using URL paramet...
static bool lineFromSld(QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle=nullptr, Qt::PenCapStyle *penCapStyle=nullptr, QVector< qreal > *customDashPattern=nullptr, double *dashOffset=nullptr)
static QString ogrFeatureStyleBrush(const QColor &fillColr)
Create ogr feature style string for brush.
static QString encodeLineClipMode(Qgis::LineClipMode mode)
Encodes a line clip mode to a string.
static Qgis::LineClipMode decodeLineClipMode(const QString &string, bool *ok=nullptr)
Decodes a string representing a line clip mode.
static QPointF pointOnLineWithDistance(QPointF startPoint, QPointF directionPoint, double distance)
Returns a point on the line from startPoint to directionPoint that is a certain distance away from th...
static Qt::BrushStyle decodeBrushStyle(const QString &str)
static void lineToSld(QDomDocument &doc, QDomElement &element, Qt::PenStyle penStyle, const QColor &color, double width=-1, const Qt::PenJoinStyle *penJoinStyle=nullptr, const Qt::PenCapStyle *penCapStyle=nullptr, const QVector< qreal > *customDashPattern=nullptr, double dashOffset=0.0)
static QDomElement createVendorOptionElement(QDomDocument &doc, const QString &name, const QString &value)
static bool wellKnownMarkerFromSld(QDomElement &element, QString &name, QColor &color, QColor &strokeColor, Qt::PenStyle &strokeStyle, double &strokeWidth, double &size)
static void createDisplacementElement(QDomDocument &doc, QDomElement &element, QPointF offset)
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodeColor(const QColor &color)
static bool fillFromSld(QDomElement &element, Qt::BrushStyle &brushStyle, QColor &color)
static void fillToSld(QDomDocument &doc, QDomElement &element, Qt::BrushStyle brushStyle, const QColor &color=QColor())
static void createGeometryElement(QDomDocument &doc, QDomElement &element, const QString &geomFunc)
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
static void wellKnownMarkerToSld(QDomDocument &doc, QDomElement &element, const QString &name, const QColor &color, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth=-1, double size=-1)
static Qt::PenStyle decodePenStyle(const QString &str)
static void createRotationElement(QDomDocument &doc, QDomElement &element, const QString &rotationFunc)
static Qgis::SymbolCoordinateReference decodeCoordinateReference(const QString &string, bool *ok=nullptr)
Decodes a string representing a symbol coordinate reference mode.
static QString encodePoint(QPointF point)
Encodes a QPointF to a string.
static QgsSymbolLayer * createMarkerLayerFromSld(QDomElement &element)
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QPointF decodePoint(const QString &string)
Decodes a QSizeF from a string.
static QgsSymbolLayer * createLineLayerFromSld(QDomElement &element)
static QString encodeCoordinateReference(Qgis::SymbolCoordinateReference coordinateReference)
Encodes a symbol coordinate reference mode to a string.
static QString encodeMarkerClipMode(Qgis::MarkerClipMode mode)
Encodes a marker clip mode to a string.
@ PropertyGradientReference1X
Gradient reference point 1 x.
@ PropertyShapeburstIgnoreRings
Shapeburst ignore rings.
@ PropertyGradientReference2X
Gradient reference point 2 x.
@ PropertyStrokeStyle
Stroke style (eg solid, dashed)
@ PropertyDistanceX
Horizontal distance between points.
@ PropertyFile
Filename, eg for svg files.
@ PropertyGradientType
Gradient fill type.
@ PropertyAngle
Symbol angle.
@ PropertyLineClipping
Line clipping mode (since QGIS 3.24)
@ PropertyDistanceY
Vertical distance between points.
@ PropertyDisplacementX
Horizontal displacement.
@ PropertyGradientSpread
Gradient spread mode.
@ PropertyOffsetY
Vertical offset.
@ PropertyGradientReference1Y
Gradient reference point 1 y.
@ PropertyLineDistance
Distance between lines, or length of lines for hash line symbols.
@ PropertyBlurRadius
Shapeburst blur radius.
@ PropertyGradientReference2Y
Gradient reference point 2 y.
@ PropertyMarkerClipping
Marker clipping mode (since QGIS 3.24)
@ PropertyDensityArea
Density area.
@ PropertyGradientReference1IsCentroid
Gradient reference point 1 is centroid.
@ PropertyShapeburstUseWholeShape
Shapeburst use whole shape.
@ PropertyOffsetX
Horizontal offset.
@ PropertyJoinStyle
Line join style.
@ PropertyOpacity
Opacity.
@ PropertySecondaryColor
Secondary color (eg for gradient fills)
@ PropertyCoordinateMode
Gradient coordinate mode.
@ PropertyRandomOffsetY
Random offset Y (since QGIS 3.24)
@ PropertyLineAngle
Line angle, or angle of hash lines for hash line symbols.
@ PropertyShapeburstMaxDistance
Shapeburst fill from edge distance.
@ PropertyOffset
Symbol offset.
@ PropertyStrokeWidth
Stroke width.
@ PropertyFillColor
Fill color.
@ PropertyClipPoints
Whether markers should be clipped to polygon boundaries.
@ PropertyPointCount
Point count.
@ PropertyRandomSeed
Random number seed.
@ PropertyRandomOffsetX
Random offset X (since QGIS 3.24)
@ PropertyFillStyle
Fill style (eg solid, dots)
@ PropertyDisplacementY
Vertical displacement.
@ PropertyStrokeColor
Stroke color.
@ PropertyGradientReference2IsCentroid
Gradient reference point 2 is centroid.
@ PropertyWidth
Symbol width.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
Qgis::SymbolType type() const
virtual QColor fillColor() const
Returns the fill color for the symbol layer.
static const bool SELECTION_IS_OPAQUE
Whether styles for selected features ignore symbol alpha.
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the set of attributes referenced by the layer.
void copyDataDefinedProperties(QgsSymbolLayer *destLayer) const
Copies all data defined properties of this layer to another symbol layer.
virtual double estimateMaxBleed(const QgsRenderContext &context) const
Returns the estimated maximum distance which the layer style will bleed outside the drawn shape when ...
static const bool SELECT_FILL_BORDER
Whether fill styles for selected features also highlight symbol stroke.
virtual QString layerType() const =0
Returns a string that represents this layer type.
virtual QColor color() const
Returns the "representative" color of the symbol layer.
virtual QColor strokeColor() const
Returns the stroke color for the symbol layer.
void copyPaintEffect(QgsSymbolLayer *destLayer) const
Copies paint effect of this layer to another symbol layer.
QgsPropertyCollection mDataDefinedProperties
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the symbol layer's property collection, used for data defined overrides.
static const bool SELECT_FILL_STYLE
Whether fill styles for selected features uses symbol layer style.
virtual bool hasDataDefinedProperties() const
Returns true if the symbol layer (or any of its sub-symbols) contains data defined properties.
const QgsFeature * feature() const
Returns the current feature being rendered.
QgsFields fields() const
Fields of the layer.
bool selected() const
Returns true if symbols should be rendered using the selected symbol coloring and style.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
qreal opacity() const
Returns the opacity for the symbol.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:152
double interval() const
Returns the interval between individual symbols.
const QgsMapUnitScale & intervalMapUnitScale() const
Returns the map unit scale for the interval between symbols.
void setInterval(double interval)
Sets the interval between individual symbols.
QgsUnitTypes::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE QgsUnitTypes::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:168
@ RenderUnknownUnit
Mixed or unknown units.
Definition: qgsunittypes.h:175
@ RenderMetersInMapUnits
Meters value as Map units.
Definition: qgsunittypes.h:176
@ RenderPercentage
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:172
@ RenderMapUnits
Map units.
Definition: qgsunittypes.h:170
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cpp:786
CORE_EXPORT QgsMeshVertex centroid(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns the centroid of the face.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527
QMap< QString, QString > QgsStringMap
Definition: qgis.h:3022
#define DEFAULT_SIMPLEFILL_JOINSTYLE
#define DEFAULT_SIMPLEFILL_COLOR
#define DEFAULT_SIMPLEFILL_STYLE
#define DEFAULT_SIMPLEFILL_BORDERSTYLE
#define DEFAULT_SIMPLEFILL_BORDERCOLOR
#define DEFAULT_SIMPLEFILL_BORDERWIDTH
#define INF
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:29
Single variable definition for use within a QgsExpressionContextScope.