QGIS API Documentation 3.41.0-Master (cea29feecf2)
Loading...
Searching...
No Matches
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 "qgsfileutils.h"
17#include "qgsfillsymbollayer.h"
18#include "qgslinesymbollayer.h"
19#include "qgssldexportcontext.h"
20#include "qgssymbollayerutils.h"
21#include "qgsdxfexport.h"
22#include "qgsgeometry.h"
23#include "qgsimagecache.h"
24#include "qgsrendercontext.h"
25#include "qgsproject.h"
26#include "qgssvgcache.h"
27#include "qgscolorramp.h"
28#include "qgscolorrampimpl.h"
29#include "qgsunittypes.h"
30#include "qgsmessagelog.h"
31#include "qgsapplication.h"
32#include "qgsimageoperation.h"
33#include "qgspolygon.h"
34#include "qgslinestring.h"
36#include "qgssymbol.h"
37#include "qgsmarkersymbol.h"
38#include "qgslinesymbol.h"
39#include "qgsfeedback.h"
40#include "qgsgeometryengine.h"
41#include "qgscolorutils.h"
42
43#include <QPainter>
44#include <QPagedPaintDevice>
45#include <QFile>
46#include <QSvgRenderer>
47#include <QDomDocument>
48#include <QDomElement>
49#include <QtMath>
50#include <random>
51
52
53QgsSimpleFillSymbolLayer::QgsSimpleFillSymbolLayer( const QColor &color, Qt::BrushStyle style, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth,
54 Qt::PenJoinStyle penJoinStyle )
55 : mBrushStyle( style )
56 , mStrokeColor( strokeColor )
57 , mStrokeStyle( strokeStyle )
58 , mStrokeWidth( strokeWidth )
59 , mPenJoinStyle( penJoinStyle )
60{
61 mColor = color;
62}
63
65
71
73{
75 if ( mOffsetUnit != unit )
76 {
78 }
79 return unit;
80}
81
87
93
102
103void QgsSimpleFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QBrush &brush, QPen &pen, QPen &selPen )
104{
105 if ( !dataDefinedProperties().hasActiveProperties() )
106 return; // shortcut
107
108 bool ok;
109
111 {
114 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
115 brush.setColor( fillColor );
116 }
118 {
121 if ( !QgsVariantUtils::isNull( exprVal ) )
122 brush.setStyle( QgsSymbolLayerUtils::decodeBrushStyle( exprVal.toString() ) );
123 }
125 {
128 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
129 pen.setColor( penColor );
130 }
132 {
135 if ( !QgsVariantUtils::isNull( exprVal ) )
136 {
137 double width = exprVal.toDouble( &ok );
138 if ( ok )
139 {
141 pen.setWidthF( width );
142 selPen.setWidthF( width );
143 }
144 }
145 }
147 {
150 if ( ok )
151 {
152 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
153 selPen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
154 }
155 }
157 {
160 if ( ok )
161 {
162 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
163 selPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
164 }
165 }
166}
167
168
170{
172 Qt::BrushStyle style = DEFAULT_SIMPLEFILL_STYLE;
177 QPointF offset;
178
179 if ( props.contains( QStringLiteral( "color" ) ) )
180 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
181 if ( props.contains( QStringLiteral( "style" ) ) )
182 style = QgsSymbolLayerUtils::decodeBrushStyle( props[QStringLiteral( "style" )].toString() );
183 if ( props.contains( QStringLiteral( "color_border" ) ) )
184 {
185 //pre 2.5 projects used "color_border"
186 strokeColor = QgsColorUtils::colorFromString( props[QStringLiteral( "color_border" )].toString() );
187 }
188 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
189 {
190 strokeColor = QgsColorUtils::colorFromString( props[QStringLiteral( "outline_color" )].toString() );
191 }
192 else if ( props.contains( QStringLiteral( "line_color" ) ) )
193 {
194 strokeColor = QgsColorUtils::colorFromString( props[QStringLiteral( "line_color" )].toString() );
195 }
196
197 if ( props.contains( QStringLiteral( "style_border" ) ) )
198 {
199 //pre 2.5 projects used "style_border"
200 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "style_border" )].toString() );
201 }
202 else if ( props.contains( QStringLiteral( "outline_style" ) ) )
203 {
204 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
205 }
206 else if ( props.contains( QStringLiteral( "line_style" ) ) )
207 {
208 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
209 }
210 if ( props.contains( QStringLiteral( "width_border" ) ) )
211 {
212 //pre 2.5 projects used "width_border"
213 strokeWidth = props[QStringLiteral( "width_border" )].toDouble();
214 }
215 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
216 {
217 strokeWidth = props[QStringLiteral( "outline_width" )].toDouble();
218 }
219 else if ( props.contains( QStringLiteral( "line_width" ) ) )
220 {
221 strokeWidth = props[QStringLiteral( "line_width" )].toDouble();
222 }
223 if ( props.contains( QStringLiteral( "offset" ) ) )
224 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
225 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
226 penJoinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() );
227
228 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, style, strokeColor, strokeStyle, strokeWidth, penJoinStyle );
229 sl->setOffset( offset );
230 if ( props.contains( QStringLiteral( "border_width_unit" ) ) )
231 {
232 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "border_width_unit" )].toString() ) );
233 }
234 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
235 {
236 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
237 }
238 else if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
239 {
240 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
241 }
242 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
243 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
244
245 if ( props.contains( QStringLiteral( "border_width_map_unit_scale" ) ) )
246 sl->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "border_width_map_unit_scale" )].toString() ) );
247 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
248 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
249
250 sl->restoreOldDataDefinedProperties( props );
251
252 return sl.release();
253}
254
255
257{
258 return QStringLiteral( "SimpleFill" );
259}
260
265
267{
268 QColor fillColor = mColor;
269 fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
270 mBrush = QBrush( fillColor, mBrushStyle );
271
272 QColor selColor = context.renderContext().selectionColor();
273 QColor selPenColor = selColor == mColor ? selColor : mStrokeColor;
274 if ( ! SELECTION_IS_OPAQUE )
275 selColor.setAlphaF( context.opacity() );
276 mSelBrush = QBrush( selColor );
277 // N.B. unless a "selection line color" is implemented in addition to the "selection color" option
278 // this would mean symbols with "no fill" look the same whether or not they are selected
279 if ( SELECT_FILL_STYLE )
280 mSelBrush.setStyle( mBrushStyle );
281
282 QColor strokeColor = mStrokeColor;
283 strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
284 mPen = QPen( strokeColor );
285 mSelPen = QPen( selPenColor );
286 mPen.setStyle( mStrokeStyle );
288 mPen.setJoinStyle( mPenJoinStyle );
289}
290
292{
293 Q_UNUSED( context )
294}
295
296void QgsSimpleFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
297{
298 QPainter *p = context.renderContext().painter();
299 if ( !p )
300 {
301 return;
302 }
303
304 QColor fillColor = mColor;
305 fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
306 mBrush.setColor( fillColor );
307 QColor strokeColor = mStrokeColor;
308 strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
309 mPen.setColor( strokeColor );
310
311 applyDataDefinedSymbology( context, mBrush, mPen, mSelPen );
312
313 QPointF offset = mOffset;
314
316 {
319 bool ok = false;
320 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
321 if ( ok )
322 offset = res;
323 }
324
325 if ( !offset.isNull() )
326 {
329 p->translate( offset );
330 }
331
332 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
333
334 if ( mBrush.style() == Qt::SolidPattern || mBrush.style() == Qt::NoBrush || !dynamic_cast<QPagedPaintDevice *>( p->device() ) )
335 {
336 p->setPen( useSelectedColor ? mSelPen : mPen );
337 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
338 _renderPolygon( p, points, rings, context );
339 }
340 else
341 {
342 // workaround upstream issue https://github.com/qgis/QGIS/issues/36580
343 // when a non-solid brush is set with opacity, the opacity incorrectly applies to the pen
344 // when exporting to PDF/print devices
345 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
346 p->setPen( Qt::NoPen );
347 _renderPolygon( p, points, rings, context );
348
349 p->setPen( useSelectedColor ? mSelPen : mPen );
350 p->setBrush( Qt::NoBrush );
351 _renderPolygon( p, points, rings, context );
352 }
353
354 if ( !offset.isNull() )
355 {
356 p->translate( -offset );
357 }
358}
359
361{
362 QVariantMap map;
363 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
364 map[QStringLiteral( "style" )] = QgsSymbolLayerUtils::encodeBrushStyle( mBrushStyle );
365 map[QStringLiteral( "outline_color" )] = QgsColorUtils::colorToString( mStrokeColor );
366 map[QStringLiteral( "outline_style" )] = QgsSymbolLayerUtils::encodePenStyle( mStrokeStyle );
367 map[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
368 map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
369 map[QStringLiteral( "border_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
370 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
371 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
372 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
373 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
374 return map;
375}
376
378{
379 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( mColor, mBrushStyle, mStrokeColor, mStrokeStyle, mStrokeWidth, mPenJoinStyle );
380 sl->setOffset( mOffset );
381 sl->setOffsetUnit( mOffsetUnit );
382 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
383 sl->setStrokeWidthUnit( mStrokeWidthUnit );
384 sl->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
385 copyDataDefinedProperties( sl.get() );
386 copyPaintEffect( sl.get() );
387 return sl.release();
388}
389
390void QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
391{
392 if ( mBrushStyle == Qt::NoBrush && mStrokeStyle == Qt::NoPen )
393 return;
394
395 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
396 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
397 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
398 element.appendChild( symbolizerElem );
399
400 // <Geometry>
401 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
402
403 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
404
405
406 // Export to PNG
407 bool exportOk { false };
408 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
409 {
410 const QImage image { toTiledPatternImage( ) };
411 if ( ! image.isNull() )
412 {
413 // <Fill>
414 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
415 symbolizerElem.appendChild( fillElem );
416 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
417 fillElem.appendChild( graphicFillElem );
418 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
419 graphicFillElem.appendChild( graphicElem );
420 QgsRenderContext renderContext;
421 const QFileInfo info { context.exportFilePath() };
422 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
423 pngPath = QgsFileUtils::uniquePath( pngPath );
424 image.save( pngPath );
425 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
426 exportOk = true;
427 }
428 }
429
430 if ( ! exportOk )
431 {
432 if ( mBrushStyle != Qt::NoBrush )
433 {
434
435 QColor color { mColor };
436
437 // Apply alpha from symbol
438 bool ok;
439 const double alpha { props.value( QStringLiteral( "alpha" ), QVariant() ).toDouble( &ok ) };
440 if ( ok )
441 {
442 color.setAlphaF( color.alphaF() * alpha );
443 }
444 // <Fill>
445 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
446 symbolizerElem.appendChild( fillElem );
448 }
449
450 if ( mStrokeStyle != Qt::NoPen )
451 {
452 // <Stroke>
453 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
454 symbolizerElem.appendChild( strokeElem );
456 // Apply alpha from symbol
457 bool ok;
458 const double alpha { props.value( QStringLiteral( "alpha" ), QVariant() ).toDouble( &ok ) };
459 QColor strokeColor { mStrokeColor };
460 if ( ok )
461 {
462 strokeColor.setAlphaF( strokeColor.alphaF() * alpha );
463 }
465 }
466 }
467
468 // <se:Displacement>
471}
472
473QString QgsSimpleFillSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
474{
475 //brush
476 QString symbolStyle;
477 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStyleBrush( mColor ) );
478 symbolStyle.append( ';' );
479 //pen
480 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStylePen( mStrokeWidth, mmScaleFactor, mapUnitScaleFactor, mStrokeColor, mPenJoinStyle ) );
481 return symbolStyle;
482}
483
485{
486 QColor color, strokeColor;
487 Qt::BrushStyle fillStyle;
488 Qt::PenStyle strokeStyle;
489 double strokeWidth;
490
491 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
492 QgsSymbolLayerUtils::fillFromSld( fillElem, fillStyle, color );
493
494 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
496
497 QPointF offset;
499
500 double scaleFactor = 1.0;
501 const QString uom = element.attribute( QStringLiteral( "uom" ) );
502 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
503 offset.setX( offset.x() * scaleFactor );
504 offset.setY( offset.y() * scaleFactor );
505 strokeWidth = strokeWidth * scaleFactor;
506
507 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, fillStyle, strokeColor, strokeStyle, strokeWidth );
508 sl->setOutputUnit( sldUnitSize );
509 sl->setOffset( offset );
510 return sl.release();
511}
512
514{
515 double penBleed = context.convertToPainterUnits( mStrokeStyle == Qt::NoPen ? 0 : ( mStrokeWidth / 2.0 ), mStrokeWidthUnit, mStrokeWidthMapUnitScale );
516 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
517 return penBleed + offsetBleed;
518}
519
530
541
552
554{
555 return mStrokeStyle;
556}
557
567
569{
570 return mBrushStyle;
571}
572
574{
575 QPixmap pixmap( QSize( 32, 32 ) );
576 pixmap.fill( Qt::transparent );
577 QPainter painter;
578 painter.begin( &pixmap );
579 painter.setRenderHint( QPainter::Antialiasing );
580 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
584 renderContext.setForceVectorOutput( true );
585 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
586
587 std::unique_ptr< QgsSimpleFillSymbolLayer > layerClone( clone() );
588 layerClone->setStrokeStyle( Qt::PenStyle::NoPen );
589 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
590 painter.end();
591 return pixmap.toImage();
592}
593
594//QgsGradientFillSymbolLayer
595
596QgsGradientFillSymbolLayer::QgsGradientFillSymbolLayer( const QColor &color, const QColor &color2,
597 Qgis::GradientColorSource colorType, Qgis::GradientType gradientType,
599 : mGradientColorType( colorType )
600 , mGradientType( gradientType )
601 , mCoordinateMode( coordinateMode )
602 , mGradientSpread( spread )
603 , mReferencePoint1( QPointF( 0.5, 0 ) )
604 , mReferencePoint2( QPointF( 0.5, 1 ) )
605{
606 mColor = color;
607 mColor2 = color2;
608}
609
614
616{
617 //default to a two-color, linear gradient with feature mode and pad spreading
622 //default to gradient from the default fill color to white
623 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
624 QPointF referencePoint1 = QPointF( 0.5, 0 );
625 bool refPoint1IsCentroid = false;
626 QPointF referencePoint2 = QPointF( 0.5, 1 );
627 bool refPoint2IsCentroid = false;
628 double angle = 0;
629 QPointF offset;
630
631 //update gradient properties from props
632 if ( props.contains( QStringLiteral( "type" ) ) )
633 type = static_cast< Qgis::GradientType >( props[QStringLiteral( "type" )].toInt() );
634 if ( props.contains( QStringLiteral( "coordinate_mode" ) ) )
635 coordinateMode = static_cast< Qgis::SymbolCoordinateReference >( props[QStringLiteral( "coordinate_mode" )].toInt() );
636 if ( props.contains( QStringLiteral( "spread" ) ) )
637 gradientSpread = static_cast< Qgis::GradientSpread >( props[QStringLiteral( "spread" )].toInt() );
638 if ( props.contains( QStringLiteral( "color_type" ) ) )
639 colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
640 if ( props.contains( QStringLiteral( "gradient_color" ) ) )
641 {
642 //pre 2.5 projects used "gradient_color"
643 color = QgsColorUtils::colorFromString( props[QStringLiteral( "gradient_color" )].toString() );
644 }
645 else if ( props.contains( QStringLiteral( "color" ) ) )
646 {
647 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
648 }
649 if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
650 {
651 color2 = QgsColorUtils::colorFromString( props[QStringLiteral( "gradient_color2" )].toString() );
652 }
653
654 if ( props.contains( QStringLiteral( "reference_point1" ) ) )
655 referencePoint1 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point1" )].toString() );
656 if ( props.contains( QStringLiteral( "reference_point1_iscentroid" ) ) )
657 refPoint1IsCentroid = props[QStringLiteral( "reference_point1_iscentroid" )].toInt();
658 if ( props.contains( QStringLiteral( "reference_point2" ) ) )
659 referencePoint2 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point2" )].toString() );
660 if ( props.contains( QStringLiteral( "reference_point2_iscentroid" ) ) )
661 refPoint2IsCentroid = props[QStringLiteral( "reference_point2_iscentroid" )].toInt();
662 if ( props.contains( QStringLiteral( "angle" ) ) )
663 angle = props[QStringLiteral( "angle" )].toDouble();
664
665 if ( props.contains( QStringLiteral( "offset" ) ) )
666 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
667
668 //attempt to create color ramp from props
669 QgsColorRamp *gradientRamp = nullptr;
670 if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
671 {
672 gradientRamp = QgsCptCityColorRamp::create( props );
673 }
674 else
675 {
676 gradientRamp = QgsGradientColorRamp::create( props );
677 }
678
679 //create a new gradient fill layer with desired properties
680 std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( color, color2, colorType, type, coordinateMode, gradientSpread );
681 sl->setOffset( offset );
682 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
683 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
684 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
685 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
686 sl->setReferencePoint1( referencePoint1 );
687 sl->setReferencePoint1IsCentroid( refPoint1IsCentroid );
688 sl->setReferencePoint2( referencePoint2 );
689 sl->setReferencePoint2IsCentroid( refPoint2IsCentroid );
690 sl->setAngle( angle );
691 if ( gradientRamp )
692 sl->setColorRamp( gradientRamp );
693
694 sl->restoreOldDataDefinedProperties( props );
695
696 return sl.release();
697}
698
703
709
711{
712 return QStringLiteral( "GradientFill" );
713}
714
715void QgsGradientFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, const QPolygonF &points )
716{
718 {
719 //shortcut
722 return;
723 }
724
725 bool ok;
726
727 //first gradient color
728 QColor color = mColor;
730 {
733 color.setAlphaF( context.opacity() * color.alphaF() );
734 }
735
736 //second gradient color
737 QColor color2 = mColor2;
739 {
742 color2.setAlphaF( context.opacity() * color2.alphaF() );
743 }
744
745 //gradient rotation angle
746 double angle = mAngle;
748 {
751 }
752
753 //gradient type
756 {
758 if ( ok )
759 {
760 if ( currentType == QObject::tr( "linear" ) )
761 {
763 }
764 else if ( currentType == QObject::tr( "radial" ) )
765 {
767 }
768 else if ( currentType == QObject::tr( "conical" ) )
769 {
771 }
772 }
773 }
774
775 //coordinate mode
778 {
780 if ( ok )
781 {
782 if ( currentCoordMode == QObject::tr( "feature" ) )
783 {
785 }
786 else if ( currentCoordMode == QObject::tr( "viewport" ) )
787 {
789 }
790 }
791 }
792
793 //gradient spread
796 {
798 if ( ok )
799 {
800 if ( currentSpread == QObject::tr( "pad" ) )
801 {
803 }
804 else if ( currentSpread == QObject::tr( "repeat" ) )
805 {
807 }
808 else if ( currentSpread == QObject::tr( "reflect" ) )
809 {
811 }
812 }
813 }
814
815 //reference point 1 x & y
816 double refPoint1X = mReferencePoint1.x();
818 {
819 context.setOriginalValueVariable( refPoint1X );
821 }
822 double refPoint1Y = mReferencePoint1.y();
824 {
825 context.setOriginalValueVariable( refPoint1Y );
827 }
828 bool refPoint1IsCentroid = mReferencePoint1IsCentroid;
830 {
831 context.setOriginalValueVariable( refPoint1IsCentroid );
833 }
834
835 //reference point 2 x & y
836 double refPoint2X = mReferencePoint2.x();
838 {
839 context.setOriginalValueVariable( refPoint2X );
841 }
842 double refPoint2Y = mReferencePoint2.y();
844 {
845 context.setOriginalValueVariable( refPoint2Y );
847 }
848 bool refPoint2IsCentroid = mReferencePoint2IsCentroid;
850 {
851 context.setOriginalValueVariable( refPoint2IsCentroid );
853 }
854
855 if ( refPoint1IsCentroid || refPoint2IsCentroid )
856 {
857 //either the gradient is starting or ending at a centroid, so calculate it
858 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
859 //centroid coordinates need to be scaled to a range [0, 1] relative to polygon bounds
860 QRectF bbox = points.boundingRect();
861 double centroidX = ( centroid.x() - bbox.left() ) / bbox.width();
862 double centroidY = ( centroid.y() - bbox.top() ) / bbox.height();
863
864 if ( refPoint1IsCentroid )
865 {
866 refPoint1X = centroidX;
867 refPoint1Y = centroidY;
868 }
869 if ( refPoint2IsCentroid )
870 {
871 refPoint2X = centroidX;
872 refPoint2Y = centroidY;
873 }
874 }
875
876 //update gradient with data defined values
878 spread, QPointF( refPoint1X, refPoint1Y ), QPointF( refPoint2X, refPoint2Y ), angle );
879}
880
881QPointF QgsGradientFillSymbolLayer::rotateReferencePoint( QPointF refPoint, double angle )
882{
883 //rotate a reference point by a specified angle around the point (0.5, 0.5)
884
885 //create a line from the centrepoint of a rectangle bounded by (0, 0) and (1, 1) to the reference point
886 QLineF refLine = QLineF( QPointF( 0.5, 0.5 ), refPoint );
887 //rotate this line by the current rotation angle
888 refLine.setAngle( refLine.angle() + angle );
889 //get new end point of line
890 QPointF rotatedReferencePoint = refLine.p2();
891 //make sure coords of new end point is within [0, 1]
892 if ( rotatedReferencePoint.x() > 1 )
893 rotatedReferencePoint.setX( 1 );
894 if ( rotatedReferencePoint.x() < 0 )
895 rotatedReferencePoint.setX( 0 );
896 if ( rotatedReferencePoint.y() > 1 )
897 rotatedReferencePoint.setY( 1 );
898 if ( rotatedReferencePoint.y() < 0 )
899 rotatedReferencePoint.setY( 0 );
900
901 return rotatedReferencePoint;
902}
903
904void QgsGradientFillSymbolLayer::applyGradient( const QgsSymbolRenderContext &context, QBrush &brush,
905 const QColor &color, const QColor &color2, Qgis::GradientColorSource gradientColorType,
906 QgsColorRamp *gradientRamp, Qgis::GradientType gradientType,
907 Qgis::SymbolCoordinateReference coordinateMode, Qgis::GradientSpread gradientSpread,
908 QPointF referencePoint1, QPointF referencePoint2, const double angle )
909{
910 //update alpha of gradient colors
911 QColor fillColor = color;
912 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
913 QColor fillColor2 = color2;
914 fillColor2.setAlphaF( context.opacity() * fillColor2.alphaF() );
915
916 //rotate reference points
917 QPointF rotatedReferencePoint1 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint1, angle ) : referencePoint1;
918 QPointF rotatedReferencePoint2 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint2, angle ) : referencePoint2;
919
920 //create a QGradient with the desired properties
921 QGradient gradient;
922 switch ( gradientType )
923 {
925 gradient = QLinearGradient( rotatedReferencePoint1, rotatedReferencePoint2 );
926 break;
928 gradient = QRadialGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).length() );
929 break;
931 gradient = QConicalGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).angle() );
932 break;
933 }
934 switch ( coordinateMode )
935 {
937 gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
938 break;
940 gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
941 break;
942 }
943 switch ( gradientSpread )
944 {
946 gradient.setSpread( QGradient::PadSpread );
947 break;
949 gradient.setSpread( QGradient::ReflectSpread );
950 break;
952 gradient.setSpread( QGradient::RepeatSpread );
953 break;
954 }
955
956 //add stops to gradient
958 ( gradientRamp->type() == QgsGradientColorRamp::typeString() || gradientRamp->type() == QgsCptCityColorRamp::typeString() ) )
959 {
960 //color ramp gradient
961 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( gradientRamp );
962 gradRamp->addStopsToGradient( &gradient, context.opacity() );
963 }
964 else
965 {
966 //two color gradient
967 gradient.setColorAt( 0.0, fillColor );
968 gradient.setColorAt( 1.0, fillColor2 );
969 }
970
971 //update QBrush use gradient
972 brush = QBrush( gradient );
973}
974
976{
977 QColor selColor = context.renderContext().selectionColor();
978 if ( ! SELECTION_IS_OPAQUE )
979 selColor.setAlphaF( context.opacity() );
980 mSelBrush = QBrush( selColor );
981}
982
984{
985 Q_UNUSED( context )
986}
987
988void QgsGradientFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
989{
990 QPainter *p = context.renderContext().painter();
991 if ( !p )
992 {
993 return;
994 }
995
996 applyDataDefinedSymbology( context, points );
997
998 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
999 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
1000 p->setPen( Qt::NoPen );
1001
1002 QPointF offset = mOffset;
1004 {
1007 bool ok = false;
1008 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1009 if ( ok )
1010 offset = res;
1011 }
1012
1013 if ( !offset.isNull() )
1014 {
1017 p->translate( offset );
1018 }
1019
1020 _renderPolygon( p, points, rings, context );
1021
1022 if ( !offset.isNull() )
1023 {
1024 p->translate( -offset );
1025 }
1026}
1027
1029{
1030 QVariantMap map;
1031 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
1032 map[QStringLiteral( "gradient_color2" )] = QgsColorUtils::colorToString( mColor2 );
1033 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
1034 map[QStringLiteral( "type" )] = QString::number( static_cast<int>( mGradientType ) );
1035 map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
1036 map[QStringLiteral( "spread" )] = QString::number( static_cast< int >( mGradientSpread ) );
1037 map[QStringLiteral( "reference_point1" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint1 );
1038 map[QStringLiteral( "reference_point1_iscentroid" )] = QString::number( mReferencePoint1IsCentroid );
1039 map[QStringLiteral( "reference_point2" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint2 );
1040 map[QStringLiteral( "reference_point2_iscentroid" )] = QString::number( mReferencePoint2IsCentroid );
1041 map[QStringLiteral( "angle" )] = QString::number( mAngle );
1042 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1043 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1044 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1045 if ( mGradientRamp )
1046 {
1047 map.insert( mGradientRamp->properties() );
1048 }
1049 return map;
1050}
1051
1053{
1054 std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( mColor, mColor2, mGradientColorType, mGradientType, mCoordinateMode, mGradientSpread );
1055 if ( mGradientRamp )
1056 sl->setColorRamp( mGradientRamp->clone() );
1057 sl->setReferencePoint1( mReferencePoint1 );
1058 sl->setReferencePoint1IsCentroid( mReferencePoint1IsCentroid );
1059 sl->setReferencePoint2( mReferencePoint2 );
1060 sl->setReferencePoint2IsCentroid( mReferencePoint2IsCentroid );
1061 sl->setAngle( mAngle );
1062 sl->setOffset( mOffset );
1063 sl->setOffsetUnit( mOffsetUnit );
1064 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1065 copyDataDefinedProperties( sl.get() );
1066 copyPaintEffect( sl.get() );
1067 return sl.release();
1068}
1069
1071{
1072 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1073 return offsetBleed;
1074}
1075
1080
1085
1090
1095
1100
1105
1106//QgsShapeburstFillSymbolLayer
1107
1109 int blurRadius, bool useWholeShape, double maxDistance )
1110 : mBlurRadius( blurRadius )
1111 , mUseWholeShape( useWholeShape )
1112 , mMaxDistance( maxDistance )
1113 , mColorType( colorType )
1114 , mColor2( color2 )
1115{
1116 mColor = color;
1117}
1118
1120
1122{
1123 //default to a two-color gradient
1125 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
1126 int blurRadius = 0;
1127 bool useWholeShape = true;
1128 double maxDistance = 5;
1129 QPointF offset;
1130
1131 //update fill properties from props
1132 if ( props.contains( QStringLiteral( "color_type" ) ) )
1133 {
1134 colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
1135 }
1136 if ( props.contains( QStringLiteral( "shapeburst_color" ) ) )
1137 {
1138 //pre 2.5 projects used "shapeburst_color"
1139 color = QgsColorUtils::colorFromString( props[QStringLiteral( "shapeburst_color" )].toString() );
1140 }
1141 else if ( props.contains( QStringLiteral( "color" ) ) )
1142 {
1143 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
1144 }
1145
1146 if ( props.contains( QStringLiteral( "shapeburst_color2" ) ) )
1147 {
1148 //pre 2.5 projects used "shapeburst_color2"
1149 color2 = QgsColorUtils::colorFromString( props[QStringLiteral( "shapeburst_color2" )].toString() );
1150 }
1151 else if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
1152 {
1153 color2 = QgsColorUtils::colorFromString( props[QStringLiteral( "gradient_color2" )].toString() );
1154 }
1155 if ( props.contains( QStringLiteral( "blur_radius" ) ) )
1156 {
1157 blurRadius = props[QStringLiteral( "blur_radius" )].toInt();
1158 }
1159 if ( props.contains( QStringLiteral( "use_whole_shape" ) ) )
1160 {
1161 useWholeShape = props[QStringLiteral( "use_whole_shape" )].toInt();
1162 }
1163 if ( props.contains( QStringLiteral( "max_distance" ) ) )
1164 {
1165 maxDistance = props[QStringLiteral( "max_distance" )].toDouble();
1166 }
1167 if ( props.contains( QStringLiteral( "offset" ) ) )
1168 {
1169 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
1170 }
1171
1172 //attempt to create color ramp from props
1173 QgsColorRamp *gradientRamp = nullptr;
1174 if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
1175 {
1176 gradientRamp = QgsCptCityColorRamp::create( props );
1177 }
1178 else
1179 {
1180 gradientRamp = QgsGradientColorRamp::create( props );
1181 }
1182
1183 //create a new shapeburst fill layer with desired properties
1184 std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( color, color2, colorType, blurRadius, useWholeShape, maxDistance );
1185 sl->setOffset( offset );
1186 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1187 {
1188 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1189 }
1190 if ( props.contains( QStringLiteral( "distance_unit" ) ) )
1191 {
1192 sl->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "distance_unit" )].toString() ) );
1193 }
1194 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1195 {
1196 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1197 }
1198 if ( props.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
1199 {
1200 sl->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
1201 }
1202 if ( props.contains( QStringLiteral( "ignore_rings" ) ) )
1203 {
1204 sl->setIgnoreRings( props[QStringLiteral( "ignore_rings" )].toInt() );
1205 }
1206 if ( gradientRamp )
1207 {
1208 sl->setColorRamp( gradientRamp );
1209 }
1210
1211 sl->restoreOldDataDefinedProperties( props );
1212
1213 return sl.release();
1214}
1215
1217{
1218 return QStringLiteral( "ShapeburstFill" );
1219}
1220
1225
1227{
1228 if ( mGradientRamp.get() == ramp )
1229 return;
1230
1231 mGradientRamp.reset( ramp );
1232}
1233
1234void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
1235 double &maxDistance, bool &ignoreRings )
1236{
1237 //first gradient color
1238 color = mColor;
1240 {
1243 }
1244
1245 //second gradient color
1246 color2 = mColor2;
1248 {
1251 }
1252
1253 //blur radius
1254 blurRadius = mBlurRadius;
1256 {
1257 context.setOriginalValueVariable( mBlurRadius );
1259 }
1260
1261 //use whole shape
1262 useWholeShape = mUseWholeShape;
1264 {
1265 context.setOriginalValueVariable( mUseWholeShape );
1267 }
1268
1269 //max distance
1270 maxDistance = mMaxDistance;
1272 {
1273 context.setOriginalValueVariable( mMaxDistance );
1275 }
1276
1277 //ignore rings
1278 ignoreRings = mIgnoreRings;
1280 {
1281 context.setOriginalValueVariable( mIgnoreRings );
1283 }
1284
1285}
1286
1288{
1289 //TODO - check this
1290 QColor selColor = context.renderContext().selectionColor();
1291 if ( ! SELECTION_IS_OPAQUE )
1292 selColor.setAlphaF( context.opacity() );
1293 mSelBrush = QBrush( selColor );
1294}
1295
1297{
1298 Q_UNUSED( context )
1299}
1300
1301void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1302{
1303 QPainter *p = context.renderContext().painter();
1304 if ( !p )
1305 {
1306 return;
1307 }
1308
1309 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1310 if ( useSelectedColor )
1311 {
1312 //feature is selected, draw using selection style
1313 p->setBrush( mSelBrush );
1314 QPointF offset = mOffset;
1315
1317 {
1320 bool ok = false;
1321 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1322 if ( ok )
1323 offset = res;
1324 }
1325
1326 if ( !offset.isNull() )
1327 {
1328 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1329 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1330 p->translate( offset );
1331 }
1332 _renderPolygon( p, points, rings, context );
1333 if ( !offset.isNull() )
1334 {
1335 p->translate( -offset );
1336 }
1337 return;
1338 }
1339
1340 QColor color1, color2;
1341 int blurRadius;
1342 bool useWholeShape;
1343 double maxDistance;
1344 bool ignoreRings;
1345 //calculate data defined symbology
1346 applyDataDefinedSymbology( context, color1, color2, blurRadius, useWholeShape, maxDistance, ignoreRings );
1347
1348 //calculate max distance for shapeburst fill to extend from polygon boundary, in pixels
1349 int outputPixelMaxDist = 0;
1350 if ( !useWholeShape && !qgsDoubleNear( maxDistance, 0.0 ) )
1351 {
1352 //convert max distance to pixels
1353 outputPixelMaxDist = static_cast< int >( std::round( context.renderContext().convertToPainterUnits( maxDistance, mDistanceUnit, mDistanceMapUnitScale ) ) );
1354 }
1355
1356 //if we are using the two color mode, create a gradient ramp
1357 std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
1359 {
1360 twoColorGradientRamp = std::make_unique< QgsGradientColorRamp >( color1, color2 );
1361 }
1362
1363 //no stroke for shapeburst fills
1364 p->setPen( QPen( Qt::NoPen ) );
1365
1366 //calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
1367 int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
1368 //create a QImage to draw shapeburst in
1369 int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1370 int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1371 int imWidth = pointsWidth + ( sideBuffer * 2 );
1372 int imHeight = pointsHeight + ( sideBuffer * 2 );
1373
1374 // these are all potentially very expensive operations, so check regularly if the job is canceled and abort responsively
1375 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1376 return;
1377
1378 std::unique_ptr< QImage > fillImage = std::make_unique< QImage >( imWidth,
1379 imHeight, QImage::Format_ARGB32_Premultiplied );
1380 if ( fillImage->isNull() )
1381 {
1382 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1383 return;
1384 }
1385
1386 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1387 return;
1388
1389 //also create an image to store the alpha channel
1390 std::unique_ptr< QImage > alphaImage = std::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1391 if ( alphaImage->isNull() )
1392 {
1393 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1394 return;
1395 }
1396
1397 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1398 return;
1399
1400 //Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
1401 //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
1402 //polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
1403 fillImage->fill( Qt::black );
1404
1405 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1406 return;
1407
1408 //initially fill the alpha channel image with a transparent color
1409 alphaImage->fill( Qt::transparent );
1410
1411 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1412 return;
1413
1414 //now, draw the polygon in the alpha channel image
1415 QPainter imgPainter;
1416 imgPainter.begin( alphaImage.get() );
1417 imgPainter.setRenderHint( QPainter::Antialiasing, true );
1418 imgPainter.setBrush( QBrush( Qt::white ) );
1419 imgPainter.setPen( QPen( Qt::black ) );
1420 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1421 _renderPolygon( &imgPainter, points, rings, context );
1422 imgPainter.end();
1423
1424 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1425 return;
1426
1427 //now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
1428 //(this avoids calling _renderPolygon twice, since that can be slow)
1429 imgPainter.begin( fillImage.get() );
1430 if ( !ignoreRings )
1431 {
1432 imgPainter.drawImage( 0, 0, *alphaImage );
1433 }
1434 else
1435 {
1436 //using ignore rings mode, so the alpha image can't be used
1437 //directly as the alpha channel contains polygon rings and we need
1438 //to draw now without any rings
1439 imgPainter.setBrush( QBrush( Qt::white ) );
1440 imgPainter.setPen( QPen( Qt::black ) );
1441 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1442 _renderPolygon( &imgPainter, points, nullptr, context );
1443 }
1444 imgPainter.end();
1445
1446 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1447 return;
1448
1449 //apply distance transform to image, uses the current color ramp to calculate final pixel colors
1450 double *dtArray = distanceTransform( fillImage.get(), context.renderContext() );
1451
1452 //copy distance transform values back to QImage, shading by appropriate color ramp
1453 dtArrayToQImage( dtArray, fillImage.get(), mColorType == Qgis::GradientColorSource::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
1454 context.renderContext(), useWholeShape, outputPixelMaxDist );
1455 if ( context.opacity() < 1 )
1456 {
1457 QgsImageOperation::multiplyOpacity( *fillImage, context.opacity(), context.renderContext().feedback() );
1458 }
1459
1460 //clean up some variables
1461 delete [] dtArray;
1462
1463 //apply blur if desired
1464 if ( blurRadius > 0 )
1465 {
1466 QgsImageOperation::stackBlur( *fillImage, blurRadius, false, context.renderContext().feedback() );
1467 }
1468
1469 //apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1470 imgPainter.begin( fillImage.get() );
1471 imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
1472 imgPainter.drawImage( 0, 0, *alphaImage );
1473 imgPainter.end();
1474 //we're finished with the alpha channel image now
1475 alphaImage.reset();
1476
1477 //draw shapeburst image in correct place in the destination painter
1478
1479 QgsScopedQPainterState painterState( p );
1480 QPointF offset = mOffset;
1482 {
1485 bool ok = false;
1486 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1487 if ( ok )
1488 offset = res;
1489 }
1490 if ( !offset.isNull() )
1491 {
1492 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1493 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1494 p->translate( offset );
1495 }
1496
1497 p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
1498
1499 if ( !offset.isNull() )
1500 {
1501 p->translate( -offset );
1502 }
1503}
1504
1505//fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
1506
1507/* distance transform of a 1d function using squared distance */
1508void QgsShapeburstFillSymbolLayer::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
1509{
1510 int k = 0;
1511 v[0] = 0;
1512 z[0] = -INF;
1513 z[1] = + INF;
1514 for ( int q = 1; q <= n - 1; q++ )
1515 {
1516 double s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1517 while ( s <= z[k] )
1518 {
1519 k--;
1520 s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1521 }
1522 k++;
1523 v[k] = q;
1524 z[k] = s;
1525 z[k + 1] = + INF;
1526 }
1527
1528 k = 0;
1529 for ( int q = 0; q <= n - 1; q++ )
1530 {
1531 while ( z[k + 1] < q )
1532 k++;
1533 d[q] = static_cast< double >( q - v[k] ) * ( q - v[k] ) + f[v[k]];
1534 }
1535}
1536
1537/* distance transform of 2d function using squared distance */
1538void QgsShapeburstFillSymbolLayer::distanceTransform2d( double *im, int width, int height, QgsRenderContext &context )
1539{
1540 int maxDimension = std::max( width, height );
1541 double *f = new double[ maxDimension ];
1542 int *v = new int[ maxDimension ];
1543 double *z = new double[ maxDimension + 1 ];
1544 double *d = new double[ maxDimension ];
1545
1546 // transform along columns
1547 for ( int x = 0; x < width; x++ )
1548 {
1549 if ( context.renderingStopped() )
1550 break;
1551
1552 for ( int y = 0; y < height; y++ )
1553 {
1554 f[y] = im[ x + static_cast< std::size_t>( y ) * width ];
1555 }
1556 distanceTransform1d( f, height, v, z, d );
1557 for ( int y = 0; y < height; y++ )
1558 {
1559 im[ x + static_cast< std::size_t>( y ) * width ] = d[y];
1560 }
1561 }
1562
1563 // transform along rows
1564 for ( int y = 0; y < height; y++ )
1565 {
1566 if ( context.renderingStopped() )
1567 break;
1568
1569 for ( int x = 0; x < width; x++ )
1570 {
1571 f[x] = im[ x + static_cast< std::size_t>( y ) * width ];
1572 }
1573 distanceTransform1d( f, width, v, z, d );
1574 for ( int x = 0; x < width; x++ )
1575 {
1576 im[ x + static_cast< std::size_t>( y ) * width ] = d[x];
1577 }
1578 }
1579
1580 delete [] d;
1581 delete [] f;
1582 delete [] v;
1583 delete [] z;
1584}
1585
1586/* distance transform of a binary QImage */
1587double *QgsShapeburstFillSymbolLayer::distanceTransform( QImage *im, QgsRenderContext &context )
1588{
1589 int width = im->width();
1590 int height = im->height();
1591
1592 double *dtArray = new double[static_cast< std::size_t>( width ) * height];
1593
1594 //load qImage to array
1595 QRgb tmpRgb;
1596 std::size_t idx = 0;
1597 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1598 {
1599 if ( context.renderingStopped() )
1600 break;
1601
1602 const QRgb *scanLine = reinterpret_cast< const QRgb * >( im->constScanLine( heightIndex ) );
1603 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1604 {
1605 tmpRgb = scanLine[widthIndex];
1606 if ( qRed( tmpRgb ) == 0 )
1607 {
1608 //black pixel, so zero distance
1609 dtArray[ idx ] = 0;
1610 }
1611 else
1612 {
1613 //white pixel, so initially set distance as infinite
1614 dtArray[ idx ] = INF;
1615 }
1616 idx++;
1617 }
1618 }
1619
1620 //calculate squared distance transform
1621 distanceTransform2d( dtArray, width, height, context );
1622
1623 return dtArray;
1624}
1625
1626void QgsShapeburstFillSymbolLayer::dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, QgsRenderContext &context, bool useWholeShape, int maxPixelDistance )
1627{
1628 int width = im->width();
1629 int height = im->height();
1630
1631 //find maximum distance value
1632 double maxDistanceValue;
1633
1634 if ( useWholeShape )
1635 {
1636 //no max distance specified in symbol properties, so calculate from maximum value in distance transform results
1637 double dtMaxValue = array[0];
1638 for ( std::size_t i = 1; i < static_cast< std::size_t >( width ) * height; ++i )
1639 {
1640 if ( array[i] > dtMaxValue )
1641 {
1642 dtMaxValue = array[i];
1643 }
1644 }
1645
1646 //values in distance transform are squared
1647 maxDistanceValue = std::sqrt( dtMaxValue );
1648 }
1649 else
1650 {
1651 //use max distance set in symbol properties
1652 maxDistanceValue = maxPixelDistance;
1653 }
1654
1655 //update the pixels in the provided QImage
1656 std::size_t idx = 0;
1657 double squaredVal = 0;
1658 double pixVal = 0;
1659
1660 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1661 {
1662 if ( context.renderingStopped() )
1663 break;
1664
1665 QRgb *scanLine = reinterpret_cast< QRgb * >( im->scanLine( heightIndex ) );
1666 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1667 {
1668 //result of distance transform
1669 squaredVal = array[idx];
1670
1671 //scale result to fit in the range [0, 1]
1672 if ( maxDistanceValue > 0 )
1673 {
1674 pixVal = squaredVal > 0 ? std::min( ( std::sqrt( squaredVal ) / maxDistanceValue ), 1.0 ) : 0;
1675 }
1676 else
1677 {
1678 pixVal = 1.0;
1679 }
1680
1681 //convert value to color from ramp
1682 //premultiply ramp color since we are storing this in a ARGB32_Premultiplied QImage
1683 scanLine[widthIndex] = qPremultiply( ramp->color( pixVal ).rgba() );
1684 idx++;
1685 }
1686 }
1687}
1688
1690{
1691 QVariantMap map;
1692 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
1693 map[QStringLiteral( "gradient_color2" )] = QgsColorUtils::colorToString( mColor2 );
1694 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mColorType ) );
1695 map[QStringLiteral( "blur_radius" )] = QString::number( mBlurRadius );
1696 map[QStringLiteral( "use_whole_shape" )] = QString::number( mUseWholeShape );
1697 map[QStringLiteral( "max_distance" )] = QString::number( mMaxDistance );
1698 map[QStringLiteral( "distance_unit" )] = QgsUnitTypes::encodeUnit( mDistanceUnit );
1699 map[QStringLiteral( "distance_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale );
1700 map[QStringLiteral( "ignore_rings" )] = QString::number( mIgnoreRings );
1701 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1702 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1703 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1704 if ( mGradientRamp )
1705 {
1706 map.insert( mGradientRamp->properties() );
1707 }
1708
1709 return map;
1710}
1711
1713{
1714 std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( mColor, mColor2, mColorType, mBlurRadius, mUseWholeShape, mMaxDistance );
1715 if ( mGradientRamp )
1716 {
1717 sl->setColorRamp( mGradientRamp->clone() );
1718 }
1719 sl->setDistanceUnit( mDistanceUnit );
1720 sl->setDistanceMapUnitScale( mDistanceMapUnitScale );
1721 sl->setIgnoreRings( mIgnoreRings );
1722 sl->setOffset( mOffset );
1723 sl->setOffsetUnit( mOffsetUnit );
1724 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1725 copyDataDefinedProperties( sl.get() );
1726 copyPaintEffect( sl.get() );
1727 return sl.release();
1728}
1729
1731{
1732 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1733 return offsetBleed;
1734}
1735
1740
1742{
1743 mDistanceUnit = unit;
1744 mOffsetUnit = unit;
1745}
1746
1748{
1749 if ( mDistanceUnit == mOffsetUnit )
1750 {
1751 return mDistanceUnit;
1752 }
1754}
1755
1757{
1758 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
1759 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
1760}
1761
1763{
1764 mDistanceMapUnitScale = scale;
1765 mOffsetMapUnitScale = scale;
1766}
1767
1769{
1770 if ( mDistanceMapUnitScale == mOffsetMapUnitScale )
1771 {
1772 return mDistanceMapUnitScale;
1773 }
1774 return QgsMapUnitScale();
1775}
1776
1777
1778//QgsImageFillSymbolLayer
1779
1783
1785
1786void QgsImageFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1787{
1788 QPainter *p = context.renderContext().painter();
1789 if ( !p )
1790 {
1791 return;
1792 }
1793
1795 applyDataDefinedSettings( context );
1796
1797 p->setPen( QPen( Qt::NoPen ) );
1798
1799 QTransform bkTransform = mBrush.transform();
1800 if ( applyBrushTransformFromContext( &context ) && !context.renderContext().textureOrigin().isNull() )
1801 {
1802 QPointF leftCorner = context.renderContext().textureOrigin();
1803 QTransform t = mBrush.transform();
1804 t.translate( leftCorner.x(), leftCorner.y() );
1805 mBrush.setTransform( t );
1806 }
1807 else
1808 {
1809 QTransform t = mBrush.transform();
1810 t.translate( 0, 0 );
1811 mBrush.setTransform( t );
1812 }
1813
1814 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1815 if ( useSelectedColor )
1816 {
1817 QColor selColor = context.renderContext().selectionColor();
1818 p->setBrush( QBrush( selColor ) );
1819 _renderPolygon( p, points, rings, context );
1820 }
1821
1822 if ( !qgsDoubleNear( mNextAngle, 0.0 ) )
1823 {
1824 QTransform t = mBrush.transform();
1825 t.rotate( mNextAngle );
1826 mBrush.setTransform( t );
1827 }
1828 p->setBrush( mBrush );
1829 _renderPolygon( p, points, rings, context );
1830
1831 mBrush.setTransform( bkTransform );
1832}
1833
1838
1843
1848
1853
1864
1866{
1867 return Qt::SolidLine;
1868#if 0
1869 if ( !mStroke )
1870 {
1871 return Qt::SolidLine;
1872 }
1873 else
1874 {
1875 return mStroke->dxfPenStyle();
1876 }
1877#endif //0
1878}
1879
1881{
1882 QVariantMap map;
1883 map.insert( QStringLiteral( "coordinate_reference" ), QgsSymbolLayerUtils::encodeCoordinateReference( mCoordinateReference ) );
1884 return map;
1885}
1886
1905
1906
1907//QgsSVGFillSymbolLayer
1908
1909QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString &svgFilePath, double width, double angle )
1911 , mPatternWidth( width )
1912{
1913 mStrokeWidth = 0.3;
1914 mAngle = angle;
1915 mColor = QColor( 255, 255, 255 );
1917}
1918
1919QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray &svgData, double width, double angle )
1921 , mPatternWidth( width )
1922 , mSvgData( svgData )
1923{
1924 storeViewBox();
1925 mStrokeWidth = 0.3;
1926 mAngle = angle;
1927 mColor = QColor( 255, 255, 255 );
1928 setDefaultSvgParams();
1929}
1930
1932
1934{
1936 mPatternWidthUnit = unit;
1937 mSvgStrokeWidthUnit = unit;
1938 mStrokeWidthUnit = unit;
1939 if ( mStroke )
1940 mStroke->setOutputUnit( unit );
1941}
1942
1944{
1946 if ( mPatternWidthUnit != unit || mSvgStrokeWidthUnit != unit || mStrokeWidthUnit != unit )
1947 {
1949 }
1950 return unit;
1951}
1952
1954{
1956 mPatternWidthMapUnitScale = scale;
1957 mSvgStrokeWidthMapUnitScale = scale;
1958}
1959
1961{
1962 if ( QgsImageFillSymbolLayer::mapUnitScale() == mPatternWidthMapUnitScale &&
1963 mPatternWidthMapUnitScale == mSvgStrokeWidthMapUnitScale &&
1964 mSvgStrokeWidthMapUnitScale == mStrokeWidthMapUnitScale )
1965 {
1966 return mPatternWidthMapUnitScale;
1967 }
1968 return QgsMapUnitScale();
1969}
1970
1971void QgsSVGFillSymbolLayer::setSvgFilePath( const QString &svgPath )
1972{
1973 mSvgData = QgsApplication::svgCache()->getImageData( svgPath );
1974 storeViewBox();
1975
1976 mSvgFilePath = svgPath;
1977 setDefaultSvgParams();
1978}
1979
1980QgsSymbolLayer *QgsSVGFillSymbolLayer::create( const QVariantMap &properties )
1981{
1982 QByteArray data;
1983 double width = 20;
1984 QString svgFilePath;
1985 double angle = 0.0;
1986
1987 if ( properties.contains( QStringLiteral( "width" ) ) )
1988 {
1989 width = properties[QStringLiteral( "width" )].toDouble();
1990 }
1991 if ( properties.contains( QStringLiteral( "svgFile" ) ) )
1992 {
1993 svgFilePath = properties[QStringLiteral( "svgFile" )].toString();
1994 }
1995 if ( properties.contains( QStringLiteral( "angle" ) ) )
1996 {
1997 angle = properties[QStringLiteral( "angle" )].toDouble();
1998 }
1999
2000 std::unique_ptr< QgsSVGFillSymbolLayer > symbolLayer;
2001 if ( !svgFilePath.isEmpty() )
2002 {
2003 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( svgFilePath, width, angle );
2004 }
2005 else
2006 {
2007 if ( properties.contains( QStringLiteral( "data" ) ) )
2008 {
2009 data = QByteArray::fromHex( properties[QStringLiteral( "data" )].toString().toLocal8Bit() );
2010 }
2011 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( data, width, angle );
2012 }
2013
2014 //svg parameters
2015 if ( properties.contains( QStringLiteral( "svgFillColor" ) ) )
2016 {
2017 //pre 2.5 projects used "svgFillColor"
2018 symbolLayer->setSvgFillColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "svgFillColor" )].toString() ) );
2019 }
2020 else if ( properties.contains( QStringLiteral( "color" ) ) )
2021 {
2022 symbolLayer->setSvgFillColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "color" )].toString() ) );
2023 }
2024 if ( properties.contains( QStringLiteral( "svgOutlineColor" ) ) )
2025 {
2026 //pre 2.5 projects used "svgOutlineColor"
2027 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "svgOutlineColor" )].toString() ) );
2028 }
2029 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2030 {
2031 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "outline_color" )].toString() ) );
2032 }
2033 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2034 {
2035 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "line_color" )].toString() ) );
2036 }
2037 if ( properties.contains( QStringLiteral( "svgOutlineWidth" ) ) )
2038 {
2039 //pre 2.5 projects used "svgOutlineWidth"
2040 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "svgOutlineWidth" )].toDouble() );
2041 }
2042 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2043 {
2044 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "outline_width" )].toDouble() );
2045 }
2046 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2047 {
2048 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "line_width" )].toDouble() );
2049 }
2050
2051 //units
2052 if ( properties.contains( QStringLiteral( "pattern_width_unit" ) ) )
2053 {
2054 symbolLayer->setPatternWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "pattern_width_unit" )].toString() ) );
2055 }
2056 if ( properties.contains( QStringLiteral( "pattern_width_map_unit_scale" ) ) )
2057 {
2058 symbolLayer->setPatternWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "pattern_width_map_unit_scale" )].toString() ) );
2059 }
2060 if ( properties.contains( QStringLiteral( "svg_outline_width_unit" ) ) )
2061 {
2062 symbolLayer->setSvgStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "svg_outline_width_unit" )].toString() ) );
2063 }
2064 if ( properties.contains( QStringLiteral( "svg_outline_width_map_unit_scale" ) ) )
2065 {
2066 symbolLayer->setSvgStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "svg_outline_width_map_unit_scale" )].toString() ) );
2067 }
2068 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2069 {
2070 symbolLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2071 }
2072 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2073 {
2074 symbolLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2075 }
2076
2077 if ( properties.contains( QStringLiteral( "parameters" ) ) )
2078 {
2079 const QVariantMap parameters = properties[QStringLiteral( "parameters" )].toMap();
2080 symbolLayer->setParameters( QgsProperty::variantMapToPropertyMap( parameters ) );
2081 }
2082
2083 symbolLayer->restoreOldDataDefinedProperties( properties );
2084
2085 return symbolLayer.release();
2086}
2087
2088void QgsSVGFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2089{
2090 QVariantMap::iterator it = properties.find( QStringLiteral( "svgFile" ) );
2091 if ( it != properties.end() )
2092 {
2093 if ( saving )
2094 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2095 else
2096 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2097 }
2098}
2099
2101{
2102 return QStringLiteral( "SVGFill" );
2103}
2104
2105void QgsSVGFillSymbolLayer::applyPattern( QBrush &brush, const QString &svgFilePath, double patternWidth, Qgis::RenderUnit patternWidthUnit,
2106 const QColor &svgFillColor, const QColor &svgStrokeColor, double svgStrokeWidth,
2107 Qgis::RenderUnit svgStrokeWidthUnit, const QgsSymbolRenderContext &context,
2108 const QgsMapUnitScale &patternWidthMapUnitScale, const QgsMapUnitScale &svgStrokeWidthMapUnitScale, const QgsStringMap svgParameters )
2109{
2110 if ( mSvgViewBox.isNull() )
2111 {
2112 return;
2113 }
2114
2116
2117 if ( static_cast< int >( size ) < 1.0 || 10000.0 < size )
2118 {
2119 brush.setTextureImage( QImage() );
2120 }
2121 else
2122 {
2123 bool fitsInCache = true;
2125 QImage patternImage = QgsApplication::svgCache()->svgAsImage( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2126 context.renderContext().scaleFactor(), fitsInCache, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), svgParameters );
2127 if ( !fitsInCache )
2128 {
2129 QPicture patternPict = QgsApplication::svgCache()->svgAsPicture( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2130 context.renderContext().scaleFactor(), false, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
2131 double hwRatio = 1.0;
2132 if ( patternPict.width() > 0 )
2133 {
2134 hwRatio = static_cast< double >( patternPict.height() ) / static_cast< double >( patternPict.width() );
2135 }
2136 patternImage = QImage( static_cast< int >( size ), static_cast< int >( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
2137 patternImage.fill( 0 ); // transparent background
2138
2139 QPainter p( &patternImage );
2140 p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
2141 }
2142
2143 QTransform brushTransform;
2144 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2145 {
2146 QImage transparentImage = patternImage.copy();
2147 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2148 brush.setTextureImage( transparentImage );
2149 }
2150 else
2151 {
2152 brush.setTextureImage( patternImage );
2153 }
2154 brush.setTransform( brushTransform );
2155 }
2156}
2157
2159{
2160 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2161
2162 applyPattern( mBrush, mSvgFilePath, mPatternWidth, mPatternWidthUnit, mColor, mSvgStrokeColor, mSvgStrokeWidth, mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2163
2164 if ( mStroke )
2165 {
2166 mStroke->setRenderHints( mStroke->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
2167 mStroke->startRender( context.renderContext(), context.fields() );
2168 }
2169}
2170
2172{
2173 if ( mStroke )
2174 {
2175 mStroke->stopRender( context.renderContext() );
2176 }
2177}
2178
2179void QgsSVGFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
2180{
2181 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
2182
2183 if ( mStroke )
2184 {
2185 const bool useSelectedColor = SELECT_FILL_BORDER && shouldRenderUsingSelectionColor( context );
2186 mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, useSelectedColor );
2187 if ( rings )
2188 {
2189 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
2190 {
2191 mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, useSelectedColor );
2192 }
2193 }
2194 }
2195}
2196
2198{
2199 QVariantMap map;
2200 if ( !mSvgFilePath.isEmpty() )
2201 {
2202 map.insert( QStringLiteral( "svgFile" ), mSvgFilePath );
2203 }
2204 else
2205 {
2206 map.insert( QStringLiteral( "data" ), QString( mSvgData.toHex() ) );
2207 }
2208
2209 map.insert( QStringLiteral( "width" ), QString::number( mPatternWidth ) );
2210 map.insert( QStringLiteral( "angle" ), QString::number( mAngle ) );
2211
2212 //svg parameters
2213 map.insert( QStringLiteral( "color" ), QgsColorUtils::colorToString( mColor ) );
2214 map.insert( QStringLiteral( "outline_color" ), QgsColorUtils::colorToString( mSvgStrokeColor ) );
2215 map.insert( QStringLiteral( "outline_width" ), QString::number( mSvgStrokeWidth ) );
2216
2217 //units
2218 map.insert( QStringLiteral( "pattern_width_unit" ), QgsUnitTypes::encodeUnit( mPatternWidthUnit ) );
2219 map.insert( QStringLiteral( "pattern_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mPatternWidthMapUnitScale ) );
2220 map.insert( QStringLiteral( "svg_outline_width_unit" ), QgsUnitTypes::encodeUnit( mSvgStrokeWidthUnit ) );
2221 map.insert( QStringLiteral( "svg_outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mSvgStrokeWidthMapUnitScale ) );
2222 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
2223 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
2224
2225 map[QStringLiteral( "parameters" )] = QgsProperty::propertyMapToVariantMap( mParameters );
2226
2227 return map;
2228}
2229
2231{
2232 std::unique_ptr< QgsSVGFillSymbolLayer > clonedLayer;
2233 if ( !mSvgFilePath.isEmpty() )
2234 {
2235 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgFilePath, mPatternWidth, mAngle );
2236 clonedLayer->setSvgFillColor( mColor );
2237 clonedLayer->setSvgStrokeColor( mSvgStrokeColor );
2238 clonedLayer->setSvgStrokeWidth( mSvgStrokeWidth );
2239 }
2240 else
2241 {
2242 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgData, mPatternWidth, mAngle );
2243 }
2244
2245 clonedLayer->setPatternWidthUnit( mPatternWidthUnit );
2246 clonedLayer->setPatternWidthMapUnitScale( mPatternWidthMapUnitScale );
2247 clonedLayer->setSvgStrokeWidthUnit( mSvgStrokeWidthUnit );
2248 clonedLayer->setSvgStrokeWidthMapUnitScale( mSvgStrokeWidthMapUnitScale );
2249 clonedLayer->setStrokeWidthUnit( mStrokeWidthUnit );
2250 clonedLayer->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
2251
2252 clonedLayer->setParameters( mParameters );
2253
2254 if ( mStroke )
2255 {
2256 clonedLayer->setSubSymbol( mStroke->clone() );
2257 }
2258 copyDataDefinedProperties( clonedLayer.get() );
2259 copyPaintEffect( clonedLayer.get() );
2260 return clonedLayer.release();
2261}
2262
2263void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2264{
2265 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
2266 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2267 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2268 element.appendChild( symbolizerElem );
2269
2270 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2271
2272 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
2273 symbolizerElem.appendChild( fillElem );
2274
2275 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
2276 fillElem.appendChild( graphicFillElem );
2277
2278 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2279 graphicFillElem.appendChild( graphicElem );
2280
2281 if ( !mSvgFilePath.isEmpty() )
2282 {
2283 // encode a parametric SVG reference
2284 double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
2285 double strokeWidth = QgsSymbolLayerUtils::rescaleUom( mSvgStrokeWidth, mSvgStrokeWidthUnit, props );
2286 QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgStrokeColor, strokeWidth );
2287 }
2288 else
2289 {
2290 // TODO: create svg from data
2291 // <se:InlineContent>
2292 symbolizerElem.appendChild( doc.createComment( QStringLiteral( "SVG from data not implemented yet" ) ) );
2293 }
2294
2295 // <Rotation>
2296 QString angleFunc;
2297 bool ok;
2298 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
2299 if ( !ok )
2300 {
2301 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
2302 }
2303 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2304 {
2305 angleFunc = QString::number( angle + mAngle );
2306 }
2307 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
2308
2309 if ( mStroke )
2310 {
2311 // the stroke sub symbol should be stored within the Stroke element,
2312 // but it will be stored in a separated LineSymbolizer because it could
2313 // have more than one layer
2314 mStroke->toSld( doc, element, props );
2315 }
2316}
2317
2319{
2320 return mPatternWidthUnit == Qgis::RenderUnit::MapUnits || mPatternWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2321 || mSvgStrokeWidthUnit == Qgis::RenderUnit::MapUnits || mSvgStrokeWidthUnit == Qgis::RenderUnit::MetersInMapUnits;
2322}
2323
2325{
2326 return mStroke.get();
2327}
2328
2330{
2331 if ( !symbol ) //unset current stroke
2332 {
2333 mStroke.reset( nullptr );
2334 return true;
2335 }
2336
2337 if ( symbol->type() != Qgis::SymbolType::Line )
2338 {
2339 delete symbol;
2340 return false;
2341 }
2342
2343 QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
2344 if ( lineSymbol )
2345 {
2346 mStroke.reset( lineSymbol );
2347 return true;
2348 }
2349
2350 delete symbol;
2351 return false;
2352}
2353
2355{
2356 if ( mStroke && mStroke->symbolLayer( 0 ) )
2357 {
2358 double subLayerBleed = mStroke->symbolLayer( 0 )->estimateMaxBleed( context );
2359 return subLayerBleed;
2360 }
2361 return 0;
2362}
2363
2365{
2366 Q_UNUSED( context )
2367 if ( !mStroke )
2368 {
2369 return QColor( Qt::black );
2370 }
2371 return mStroke->color();
2372}
2373
2374QSet<QString> QgsSVGFillSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2375{
2376 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2377 if ( mStroke )
2378 attr.unite( mStroke->usedAttributes( context ) );
2379 return attr;
2380}
2381
2383{
2385 return true;
2386 if ( mStroke && mStroke->hasDataDefinedProperties() )
2387 return true;
2388 return false;
2389}
2390
2392{
2393 QString path, mimeType;
2394 QColor fillColor, strokeColor;
2395 Qt::PenStyle penStyle;
2396 double size, strokeWidth;
2397
2398 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
2399 if ( fillElem.isNull() )
2400 return nullptr;
2401
2402 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
2403 if ( graphicFillElem.isNull() )
2404 return nullptr;
2405
2406 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
2407 if ( graphicElem.isNull() )
2408 return nullptr;
2409
2410 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
2411 return nullptr;
2412
2413 if ( mimeType != QLatin1String( "image/svg+xml" ) )
2414 return nullptr;
2415
2416 QgsSymbolLayerUtils::lineFromSld( graphicElem, penStyle, strokeColor, strokeWidth );
2417
2418 double scaleFactor = 1.0;
2419 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2420 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2421 size = size * scaleFactor;
2422 strokeWidth = strokeWidth * scaleFactor;
2423
2424 double angle = 0.0;
2425 QString angleFunc;
2426 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2427 {
2428 bool ok;
2429 double d = angleFunc.toDouble( &ok );
2430 if ( ok )
2431 angle = d;
2432 }
2433
2434 std::unique_ptr< QgsSVGFillSymbolLayer > sl = std::make_unique< QgsSVGFillSymbolLayer >( path, size, angle );
2435 sl->setOutputUnit( sldUnitSize );
2436 sl->setSvgFillColor( fillColor );
2437 sl->setSvgStrokeColor( strokeColor );
2438 sl->setSvgStrokeWidth( strokeWidth );
2439
2440 // try to get the stroke
2441 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2442 if ( !strokeElem.isNull() )
2443 {
2445 if ( l )
2446 {
2447 QgsSymbolLayerList layers;
2448 layers.append( l );
2449 sl->setSubSymbol( new QgsLineSymbol( layers ) );
2450 }
2451 }
2452
2453 return sl.release();
2454}
2455
2457{
2461 {
2462 return; //no data defined settings
2463 }
2464
2466 {
2469 }
2470
2471 double width = mPatternWidth;
2473 {
2474 context.setOriginalValueVariable( mPatternWidth );
2476 }
2477 QString svgFile = mSvgFilePath;
2479 {
2480 context.setOriginalValueVariable( mSvgFilePath );
2482 context.renderContext().pathResolver() );
2483 }
2484 QColor svgFillColor = mColor;
2486 {
2489 }
2490 QColor svgStrokeColor = mSvgStrokeColor;
2492 {
2493 context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2495 }
2496 double strokeWidth = mSvgStrokeWidth;
2498 {
2499 context.setOriginalValueVariable( mSvgStrokeWidth );
2501 }
2502 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2503
2504 applyPattern( mBrush, svgFile, width, mPatternWidthUnit, svgFillColor, svgStrokeColor, strokeWidth,
2505 mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2506
2507}
2508
2509void QgsSVGFillSymbolLayer::storeViewBox()
2510{
2511 if ( !mSvgData.isEmpty() )
2512 {
2513 QSvgRenderer r( mSvgData );
2514 if ( r.isValid() )
2515 {
2516 mSvgViewBox = r.viewBoxF();
2517 return;
2518 }
2519 }
2520
2521 mSvgViewBox = QRectF();
2522}
2523
2524void QgsSVGFillSymbolLayer::setDefaultSvgParams()
2525{
2526 if ( mSvgFilePath.isEmpty() )
2527 {
2528 return;
2529 }
2530
2531 bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
2532 bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
2533 QColor defaultFillColor, defaultStrokeColor;
2534 double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
2535 QgsApplication::svgCache()->containsParams( mSvgFilePath, hasFillParam, hasDefaultFillColor, defaultFillColor,
2536 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
2537 hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2538 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
2539 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
2540
2541 double newFillOpacity = hasFillOpacityParam ? mColor.alphaF() : 1.0;
2542 double newStrokeOpacity = hasStrokeOpacityParam ? mSvgStrokeColor.alphaF() : 1.0;
2543
2544 if ( hasDefaultFillColor )
2545 {
2546 mColor = defaultFillColor;
2547 mColor.setAlphaF( newFillOpacity );
2548 }
2549 if ( hasDefaultFillOpacity )
2550 {
2551 mColor.setAlphaF( defaultFillOpacity );
2552 }
2553 if ( hasDefaultStrokeColor )
2554 {
2555 mSvgStrokeColor = defaultStrokeColor;
2556 mSvgStrokeColor.setAlphaF( newStrokeOpacity );
2557 }
2558 if ( hasDefaultStrokeOpacity )
2559 {
2560 mSvgStrokeColor.setAlphaF( defaultStrokeOpacity );
2561 }
2562 if ( hasDefaultStrokeWidth )
2563 {
2564 mSvgStrokeWidth = defaultStrokeWidth;
2565 }
2566}
2567
2568void QgsSVGFillSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2569{
2570 mParameters = parameters;
2571}
2572
2573
2576{
2577 mFillLineSymbol = std::make_unique<QgsLineSymbol>( );
2578 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
2579}
2580
2582
2584{
2585 mFillLineSymbol->setWidth( w );
2586 mLineWidth = w;
2587}
2588
2590{
2591 mFillLineSymbol->setColor( c );
2592 mColor = c;
2593}
2594
2596{
2597 return mFillLineSymbol ? mFillLineSymbol->color() : mColor;
2598}
2599
2601{
2602 if ( !symbol )
2603 {
2604 return false;
2605 }
2606
2607 if ( symbol->type() == Qgis::SymbolType::Line )
2608 {
2609 mFillLineSymbol.reset( qgis::down_cast<QgsLineSymbol *>( symbol ) );
2610 return true;
2611 }
2612 delete symbol;
2613 return false;
2614}
2615
2617{
2618 return mFillLineSymbol.get();
2619}
2620
2622{
2623 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2624 if ( mFillLineSymbol )
2625 attr.unite( mFillLineSymbol->usedAttributes( context ) );
2626 return attr;
2627}
2628
2630{
2632 return true;
2633 if ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
2634 return true;
2635 return false;
2636}
2637
2639{
2640 installMasks( context, true );
2641
2642 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2643}
2644
2646{
2647 removeMasks( context, true );
2648
2649 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2650}
2651
2653{
2654
2655 double lineAngleRad { qDegreesToRadians( mLineAngle ) };
2656
2657 const int quadrant { static_cast<int>( lineAngleRad / M_PI_2 ) };
2658 Q_ASSERT( quadrant >= 0 && quadrant <= 3 );
2659
2660 switch ( quadrant )
2661 {
2662 case 0:
2663 {
2664 break;
2665 }
2666 case 1:
2667 {
2668 lineAngleRad -= M_PI / 2;
2669 break;
2670 }
2671 case 2:
2672 {
2673 lineAngleRad -= M_PI;
2674 break;
2675 }
2676 case 3:
2677 {
2678 lineAngleRad -= M_PI + M_PI_2;
2679 break;
2680 }
2681 }
2682
2683
2684 double distancePx { QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, {} ) };
2685
2686 QSize size { static_cast<int>( distancePx ), static_cast<int>( distancePx ) };
2687
2688 if ( static_cast<int>( mLineAngle ) % 90 != 0 )
2689 {
2690 size = QSize( static_cast<int>( distancePx / std::sin( lineAngleRad ) ), static_cast<int>( distancePx / std::cos( lineAngleRad ) ) );
2691 }
2692
2693 QPixmap pixmap( size );
2694 pixmap.fill( Qt::transparent );
2695 QPainter painter;
2696 painter.begin( &pixmap );
2697 painter.setRenderHint( QPainter::Antialiasing );
2698 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
2702 renderContext.setForceVectorOutput( true );
2703 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
2704
2705 std::unique_ptr< QgsLinePatternFillSymbolLayer > layerClone( clone() );
2706 layerClone->setOffset( 0 );
2707 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
2708 painter.end();
2709 return pixmap.toImage();
2710}
2711
2713{
2714 return 0;
2715}
2716
2718{
2720 mDistanceUnit = unit;
2721 mLineWidthUnit = unit;
2722 mOffsetUnit = unit;
2723
2724 if ( mFillLineSymbol )
2725 mFillLineSymbol->setOutputUnit( unit );
2726}
2727
2729{
2731 if ( mDistanceUnit != unit || mLineWidthUnit != unit || ( mOffsetUnit != unit && mOffsetUnit != Qgis::RenderUnit::Percentage ) )
2732 {
2734 }
2735 return unit;
2736}
2737
2739{
2740 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
2741 || mLineWidthUnit == Qgis::RenderUnit::MapUnits || mLineWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2742 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
2743}
2744
2746{
2748 mDistanceMapUnitScale = scale;
2749 mLineWidthMapUnitScale = scale;
2750 mOffsetMapUnitScale = scale;
2751}
2752
2754{
2755 if ( QgsImageFillSymbolLayer::mapUnitScale() == mDistanceMapUnitScale &&
2756 mDistanceMapUnitScale == mLineWidthMapUnitScale &&
2757 mLineWidthMapUnitScale == mOffsetMapUnitScale )
2758 {
2759 return mDistanceMapUnitScale;
2760 }
2761 return QgsMapUnitScale();
2762}
2763
2765{
2766 std::unique_ptr< QgsLinePatternFillSymbolLayer > patternLayer = std::make_unique< QgsLinePatternFillSymbolLayer >();
2767
2768 //default values
2769 double lineAngle = 45;
2770 double distance = 5;
2771 double lineWidth = 0.5;
2772 QColor color( Qt::black );
2773 double offset = 0.0;
2774
2775 if ( properties.contains( QStringLiteral( "lineangle" ) ) )
2776 {
2777 //pre 2.5 projects used "lineangle"
2778 lineAngle = properties[QStringLiteral( "lineangle" )].toDouble();
2779 }
2780 else if ( properties.contains( QStringLiteral( "angle" ) ) )
2781 {
2782 lineAngle = properties[QStringLiteral( "angle" )].toDouble();
2783 }
2784 patternLayer->setLineAngle( lineAngle );
2785
2786 if ( properties.contains( QStringLiteral( "distance" ) ) )
2787 {
2788 distance = properties[QStringLiteral( "distance" )].toDouble();
2789 }
2790 patternLayer->setDistance( distance );
2791
2792 if ( properties.contains( QStringLiteral( "linewidth" ) ) )
2793 {
2794 //pre 2.5 projects used "linewidth"
2795 lineWidth = properties[QStringLiteral( "linewidth" )].toDouble();
2796 }
2797 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2798 {
2799 lineWidth = properties[QStringLiteral( "outline_width" )].toDouble();
2800 }
2801 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2802 {
2803 lineWidth = properties[QStringLiteral( "line_width" )].toDouble();
2804 }
2805 patternLayer->setLineWidth( lineWidth );
2806
2807 if ( properties.contains( QStringLiteral( "color" ) ) )
2808 {
2809 color = QgsColorUtils::colorFromString( properties[QStringLiteral( "color" )].toString() );
2810 }
2811 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2812 {
2813 color = QgsColorUtils::colorFromString( properties[QStringLiteral( "outline_color" )].toString() );
2814 }
2815 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2816 {
2817 color = QgsColorUtils::colorFromString( properties[QStringLiteral( "line_color" )].toString() );
2818 }
2819 patternLayer->setColor( color );
2820
2821 if ( properties.contains( QStringLiteral( "offset" ) ) )
2822 {
2823 offset = properties[QStringLiteral( "offset" )].toDouble();
2824 }
2825 patternLayer->setOffset( offset );
2826
2827
2828 if ( properties.contains( QStringLiteral( "distance_unit" ) ) )
2829 {
2830 patternLayer->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_unit" )].toString() ) );
2831 }
2832 if ( properties.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
2833 {
2834 patternLayer->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
2835 }
2836 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
2837 {
2838 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
2839 }
2840 else if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2841 {
2842 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2843 }
2844 if ( properties.contains( QStringLiteral( "line_width_map_unit_scale" ) ) )
2845 {
2846 patternLayer->setLineWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "line_width_map_unit_scale" )].toString() ) );
2847 }
2848 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
2849 {
2850 patternLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
2851 }
2852 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
2853 {
2854 patternLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
2855 }
2856 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2857 {
2858 patternLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2859 }
2860 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2861 {
2862 patternLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2863 }
2864 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
2865 {
2866 patternLayer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
2867 }
2868 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
2869 {
2870 patternLayer->setClipMode( QgsSymbolLayerUtils::decodeLineClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
2871 }
2872
2873 patternLayer->restoreOldDataDefinedProperties( properties );
2874
2875 return patternLayer.release();
2876}
2877
2879{
2880 return QStringLiteral( "LinePatternFill" );
2881}
2882
2883bool QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double lineAngle, double distance )
2884{
2885 mBrush.setTextureImage( QImage() ); // set empty in case we have to return
2886
2887 if ( !mFillLineSymbol )
2888 {
2889 return true;
2890 }
2891 // We have to make a copy because marker intervals will have to be adjusted
2892 std::unique_ptr< QgsLineSymbol > fillLineSymbol( mFillLineSymbol->clone() );
2893 if ( !fillLineSymbol )
2894 {
2895 return true;
2896 }
2897
2898 const QgsRenderContext &ctx = context.renderContext();
2899 //double strokePixelWidth = lineWidth * QgsSymbolLayerUtils::pixelSizeScaleFactor( ctx, mLineWidthUnit, mLineWidthMapUnitScale );
2900 double outputPixelDist = ctx.convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
2901 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDist * mOffset / 100
2902 : ctx.convertToPainterUnits( mOffset, mOffsetUnit, mOffsetMapUnitScale );
2903
2904 // NOTE: this may need to be modified if we ever change from a forced rasterized/brush approach,
2905 // because potentially we may want to allow vector based line pattern fills where the first line
2906 // is offset by a large distance
2907
2908 // fix truncated pattern with larger offsets
2909 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDist );
2910 if ( outputPixelOffset > outputPixelDist / 2.0 )
2911 outputPixelOffset -= outputPixelDist;
2912
2913 // To get all patterns into image, we have to consider symbols size (estimateMaxBleed()).
2914 // For marker lines we have to get markers interval.
2915 double outputPixelBleed = 0;
2916 double outputPixelInterval = 0; // maximum interval
2917 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2918 {
2919 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2920 double outputPixelLayerBleed = layer->estimateMaxBleed( context.renderContext() );
2921 outputPixelBleed = std::max( outputPixelBleed, outputPixelLayerBleed );
2922
2923 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2924 if ( markerLineLayer )
2925 {
2926 double outputPixelLayerInterval = ctx.convertToPainterUnits( markerLineLayer->interval(), markerLineLayer->intervalUnit(), markerLineLayer->intervalMapUnitScale() );
2927
2928 // There may be multiple marker lines with different intervals.
2929 // In theory we should find the least common multiple, but that could be too
2930 // big (multiplication of intervals in the worst case).
2931 // Because patterns without small common interval would look strange, we
2932 // believe that the longest interval should usually be sufficient.
2933 outputPixelInterval = std::max( outputPixelInterval, outputPixelLayerInterval );
2934 }
2935 }
2936
2937 if ( outputPixelInterval > 0 )
2938 {
2939 // We have to adjust marker intervals to integer pixel size to get
2940 // repeatable pattern.
2941 double intervalScale = std::round( outputPixelInterval ) / outputPixelInterval;
2942 outputPixelInterval = std::round( outputPixelInterval );
2943
2944 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2945 {
2946 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2947
2948 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2949 if ( markerLineLayer )
2950 {
2951 markerLineLayer->setInterval( intervalScale * markerLineLayer->interval() );
2952 }
2953 }
2954 }
2955
2956 //create image
2957 int height, width;
2958 lineAngle = std::fmod( lineAngle, 360 );
2959 if ( lineAngle < 0 )
2960 lineAngle += 360;
2961 if ( qgsDoubleNear( lineAngle, 0 ) || qgsDoubleNear( lineAngle, 360 ) || qgsDoubleNear( lineAngle, 180 ) )
2962 {
2963 height = outputPixelDist;
2964 width = outputPixelInterval > 0 ? outputPixelInterval : height;
2965 }
2966 else if ( qgsDoubleNear( lineAngle, 90 ) || qgsDoubleNear( lineAngle, 270 ) )
2967 {
2968 width = outputPixelDist;
2969 height = outputPixelInterval > 0 ? outputPixelInterval : width;
2970 }
2971 else
2972 {
2973 height = outputPixelDist / std::cos( lineAngle * M_PI / 180 ); //keep perpendicular distance between lines constant
2974 width = outputPixelDist / std::sin( lineAngle * M_PI / 180 );
2975
2976 // recalculate real angle and distance after rounding to pixels
2977 lineAngle = 180 * std::atan2( static_cast< double >( height ), static_cast< double >( width ) ) / M_PI;
2978 if ( lineAngle < 0 )
2979 {
2980 lineAngle += 360.;
2981 }
2982
2983 height = std::abs( height );
2984 width = std::abs( width );
2985
2986 outputPixelDist = std::abs( height * std::cos( lineAngle * M_PI / 180 ) );
2987
2988 // Round offset to correspond to one pixel height, otherwise lines may
2989 // be shifted on tile border if offset falls close to pixel center
2990 int offsetHeight = static_cast< int >( std::round( outputPixelOffset / std::cos( lineAngle * M_PI / 180 ) ) );
2991 outputPixelOffset = offsetHeight * std::cos( lineAngle * M_PI / 180 );
2992 }
2993
2994 //depending on the angle, we might need to render into a larger image and use a subset of it
2995 double dx = 0;
2996 double dy = 0;
2997
2998 // Add buffer based on bleed but keep precisely the height/width ratio (angle)
2999 // thus we add integer multiplications of width and height covering the bleed
3000 int bufferMulti = static_cast< int >( std::max( std::ceil( outputPixelBleed / width ), std::ceil( outputPixelBleed / width ) ) );
3001
3002 // Always buffer at least once so that center of line marker in upper right corner
3003 // does not fall outside due to representation error
3004 bufferMulti = std::max( bufferMulti, 1 );
3005
3006 int xBuffer = width * bufferMulti;
3007 int yBuffer = height * bufferMulti;
3008 int innerWidth = width;
3009 int innerHeight = height;
3010 width += 2 * xBuffer;
3011 height += 2 * yBuffer;
3012
3013 //protect from zero width/height image and symbol layer from eating too much memory
3014 if ( width > 2000 || height > 2000 || width == 0 || height == 0 )
3015 {
3016 return false;
3017 }
3018
3019 QImage patternImage( width, height, QImage::Format_ARGB32 );
3020 patternImage.fill( 0 );
3021
3022 QPointF p1, p2, p3, p4, p5, p6;
3023 if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
3024 {
3025 p1 = QPointF( 0, yBuffer );
3026 p2 = QPointF( width, yBuffer );
3027 p3 = QPointF( 0, yBuffer + innerHeight );
3028 p4 = QPointF( width, yBuffer + innerHeight );
3029 }
3030 else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
3031 {
3032 p1 = QPointF( xBuffer, height );
3033 p2 = QPointF( xBuffer, 0 );
3034 p3 = QPointF( xBuffer + innerWidth, height );
3035 p4 = QPointF( xBuffer + innerWidth, 0 );
3036 }
3037 else if ( lineAngle > 0 && lineAngle < 90 )
3038 {
3039 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3040 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3041 p1 = QPointF( 0, height );
3042 p2 = QPointF( width, 0 );
3043 p3 = QPointF( -dx, height - dy );
3044 p4 = QPointF( width - dx, -dy );
3045 p5 = QPointF( dx, height + dy );
3046 p6 = QPointF( width + dx, dy );
3047 }
3048 else if ( lineAngle > 180 && lineAngle < 270 )
3049 {
3050 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3051 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3052 p1 = QPointF( width, 0 );
3053 p2 = QPointF( 0, height );
3054 p3 = QPointF( width - dx, -dy );
3055 p4 = QPointF( -dx, height - dy );
3056 p5 = QPointF( width + dx, dy );
3057 p6 = QPointF( dx, height + dy );
3058 }
3059 else if ( lineAngle > 90 && lineAngle < 180 )
3060 {
3061 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3062 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3063 p1 = QPointF( 0, 0 );
3064 p2 = QPointF( width, height );
3065 p5 = QPointF( dx, -dy );
3066 p6 = QPointF( width + dx, height - dy );
3067 p3 = QPointF( -dx, dy );
3068 p4 = QPointF( width - dx, height + dy );
3069 }
3070 else if ( lineAngle > 270 && lineAngle < 360 )
3071 {
3072 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3073 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3074 p1 = QPointF( width, height );
3075 p2 = QPointF( 0, 0 );
3076 p5 = QPointF( width + dx, height - dy );
3077 p6 = QPointF( dx, -dy );
3078 p3 = QPointF( width - dx, height + dy );
3079 p4 = QPointF( -dx, dy );
3080 }
3081
3082 if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
3083 {
3084 QPointF tempPt;
3085 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
3086 p3 = QPointF( tempPt.x(), tempPt.y() );
3087 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
3088 p4 = QPointF( tempPt.x(), tempPt.y() );
3089 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
3090 p5 = QPointF( tempPt.x(), tempPt.y() );
3091 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
3092 p6 = QPointF( tempPt.x(), tempPt.y() );
3093
3094 //update p1, p2 last
3095 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
3096 p1 = QPointF( tempPt.x(), tempPt.y() );
3097 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
3098 p2 = QPointF( tempPt.x(), tempPt.y() );
3099 }
3100
3101 QPainter p( &patternImage );
3102
3103#if 0
3104 // DEBUG: Draw rectangle
3105 p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
3106 QPen pen( QColor( Qt::black ) );
3107 pen.setWidthF( 0.1 );
3108 pen.setCapStyle( Qt::FlatCap );
3109 p.setPen( pen );
3110
3111 // To see this rectangle, comment buffer cut below.
3112 // Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
3113 QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 );
3114 p.drawPolygon( polygon );
3115
3116 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 );
3117 p.drawPolygon( polygon );
3118#endif
3119
3120 // Use antialiasing because without antialiasing lines are rendered to the
3121 // right and below the mathematically defined points (not symmetrical)
3122 // and such tiles become useless for are filling
3123 p.setRenderHint( QPainter::Antialiasing, true );
3124
3125 // line rendering needs context for drawing on patternImage
3126 QgsRenderContext lineRenderContext;
3127 lineRenderContext.setPainter( &p );
3128 lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3130 lineRenderContext.setMapToPixel( mtp );
3131 lineRenderContext.setForceVectorOutput( false );
3132 lineRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3134 lineRenderContext.setDisabledSymbolLayersV2( context.renderContext().disabledSymbolLayersV2() );
3135
3136 fillLineSymbol->setRenderHints( fillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3137 fillLineSymbol->startRender( lineRenderContext, context.fields() );
3138
3139 QVector<QPolygonF> polygons;
3140 polygons.append( QPolygonF() << p1 << p2 );
3141 polygons.append( QPolygonF() << p3 << p4 );
3142 if ( !qgsDoubleNear( lineAngle, 0 ) && !qgsDoubleNear( lineAngle, 360 ) && !qgsDoubleNear( lineAngle, 90 ) && !qgsDoubleNear( lineAngle, 180 ) && !qgsDoubleNear( lineAngle, 270 ) )
3143 {
3144 polygons.append( QPolygonF() << p5 << p6 );
3145 }
3146
3147 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3148 for ( const QPolygonF &polygon : std::as_const( polygons ) )
3149 {
3150 fillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, useSelectedColor );
3151 }
3152
3153 fillLineSymbol->stopRender( lineRenderContext );
3154 p.end();
3155
3156 // Cut off the buffer
3157 patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
3158
3159 //set image to mBrush
3160 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3161 {
3162 QImage transparentImage = patternImage.copy();
3163 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3164 brush.setTextureImage( transparentImage );
3165 }
3166 else
3167 {
3168 brush.setTextureImage( patternImage );
3169 }
3170
3171 QTransform brushTransform;
3172 brush.setTransform( brushTransform );
3173
3174 return true;
3175}
3176
3178{
3179 // if we are using a vector based output, we need to render points as vectors
3180 // (OR if the line has data defined symbology, in which case we need to evaluate this line-by-line)
3181 mRenderUsingLines = context.forceVectorRendering()
3182 || ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
3185
3186 if ( !mRenderUsingLines )
3187 {
3188 // optimised render for screen only, use image based brush
3189 // (fallback to line rendering when pattern image will result in too large a memory footprint)
3190 mRenderUsingLines = !applyPattern( context, mBrush, mLineAngle, mDistance );
3191 }
3192
3193 if ( mRenderUsingLines && mFillLineSymbol )
3194 {
3195 mFillLineSymbol->setRenderHints( mFillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3196 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3197 mFillLineSymbolRenderStarted = true;
3198 }
3199}
3200
3202{
3203 if ( mFillLineSymbolRenderStarted )
3204 {
3205 mFillLineSymbol->stopRender( context.renderContext() );
3206 mFillLineSymbolRenderStarted = false;
3207 }
3208}
3209
3210void QgsLinePatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3211{
3212 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3213 if ( !useSelectedColor && !mRenderUsingLines )
3214 {
3215 // use image based brush for speed
3216 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3217 return;
3218 }
3219
3220 if ( !mFillLineSymbolRenderStarted && mFillLineSymbol )
3221 {
3222 mFillLineSymbol->setRenderHints( mFillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3223 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3224 mFillLineSymbolRenderStarted = true;
3225 }
3226
3227 // vector based output - so draw line by line!
3228 QPainter *p = context.renderContext().painter();
3229 if ( !p )
3230 {
3231 return;
3232 }
3233
3234 double lineAngle = mLineAngle;
3236 {
3237 context.setOriginalValueVariable( mLineAngle );
3239 }
3240
3241 double distance = mDistance;
3243 {
3244 context.setOriginalValueVariable( mDistance );
3246 }
3247 const double outputPixelDistance = context.renderContext().convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
3248
3249 double offset = mOffset;
3250 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDistance * offset / 100
3251 : context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3252
3253 // fix truncated pattern with larger offsets
3254 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDistance );
3255 if ( outputPixelOffset > outputPixelDistance / 2.0 )
3256 outputPixelOffset -= outputPixelDistance;
3257
3258 p->setPen( QPen( Qt::NoPen ) );
3259
3260 // if invalid parameters, skip out
3261 if ( qgsDoubleNear( distance, 0 ) )
3262 return;
3263
3264 p->save();
3265
3266 Qgis::LineClipMode clipMode = mClipMode;
3268 {
3270 bool ok = false;
3271 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::LineClipping, context.renderContext().expressionContext(), QString(), &ok );
3272 if ( ok )
3273 {
3274 Qgis::LineClipMode decodedMode = QgsSymbolLayerUtils::decodeLineClipMode( valueString, &ok );
3275 if ( ok )
3276 clipMode = decodedMode;
3277 }
3278 }
3279
3280 std::unique_ptr< QgsPolygon > shapePolygon;
3281 std::unique_ptr< QgsGeometryEngine > shapeEngine;
3282 switch ( clipMode )
3283 {
3285 break;
3286
3288 {
3289 shapePolygon = std::make_unique< QgsPolygon >();
3290 std::unique_ptr< QgsLineString > fromPolygon = QgsLineString::fromQPolygonF( points );
3291 shapePolygon->setExteriorRing( fromPolygon.release() );
3292 if ( rings )
3293 {
3294 for ( const QPolygonF &ring : *rings )
3295 {
3296 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
3297 shapePolygon->addInteriorRing( fromRing.release() );
3298 }
3299 }
3300 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3301 shapeEngine->prepareGeometry();
3302 break;
3303 }
3304
3306 {
3307 QPainterPath path;
3308 path.addPolygon( points );
3309 if ( rings )
3310 {
3311 for ( const QPolygonF &ring : *rings )
3312 {
3313 path.addPolygon( ring );
3314 }
3315 }
3316 p->setClipPath( path, Qt::IntersectClip );
3317 break;
3318 }
3319 }
3320
3321 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3322 const QRectF boundingRect = points.boundingRect();
3323
3324 QTransform invertedRotateTransform;
3325 double left;
3326 double top;
3327 double right;
3328 double bottom;
3329
3330 QTransform transform;
3331 if ( applyBrushTransform )
3332 {
3333 // rotation applies around center of feature
3334 transform.translate( -boundingRect.center().x(),
3335 -boundingRect.center().y() );
3336 transform.rotate( lineAngle );
3337 transform.translate( boundingRect.center().x(),
3338 boundingRect.center().y() );
3339 }
3340 else
3341 {
3342 // rotation applies around top of viewport
3343 transform.rotate( lineAngle );
3344 }
3345
3346 const QRectF transformedBounds = transform.map( points ).boundingRect();
3347
3348 // bounds are expanded out a bit to account for maximum line width
3349 const double buffer = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFillLineSymbol.get(), context.renderContext() );
3350 left = transformedBounds.left() - buffer * 2;
3351 top = transformedBounds.top() - buffer * 2;
3352 right = transformedBounds.right() + buffer * 2;
3353 bottom = transformedBounds.bottom() + buffer * 2;
3354 invertedRotateTransform = transform.inverted();
3355
3356 if ( !applyBrushTransform )
3357 {
3358 top -= transformedBounds.top() - ( outputPixelDistance * std::floor( transformedBounds.top() / outputPixelDistance ) );
3359 }
3360
3362 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
3363 const bool needsExpressionContext = mFillLineSymbol->hasDataDefinedProperties();
3364
3365 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3367
3368 int currentLine = 0;
3369 for ( double currentY = top; currentY <= bottom; currentY += outputPixelDistance )
3370 {
3371 if ( context.renderContext().renderingStopped() )
3372 break;
3373
3374 if ( needsExpressionContext )
3375 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_line_number" ), ++currentLine, true ) );
3376
3377 double x1 = left;
3378 double y1 = currentY;
3379 double x2 = left;
3380 double y2 = currentY;
3381 invertedRotateTransform.map( left, currentY - outputPixelOffset, &x1, &y1 );
3382 invertedRotateTransform.map( right, currentY - outputPixelOffset, &x2, &y2 );
3383
3384 if ( shapeEngine )
3385 {
3386 QgsLineString ls( QgsPoint( x1, y1 ), QgsPoint( x2, y2 ) );
3387 std::unique_ptr< QgsAbstractGeometry > intersection( shapeEngine->intersection( &ls ) );
3388 for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
3389 {
3390 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( *it ) )
3391 {
3392 mFillLineSymbol->renderPolyline( ls->asQPolygonF(), context.feature(), context.renderContext(), -1, useSelectedColor );
3393 }
3394 }
3395 }
3396 else
3397 {
3398 mFillLineSymbol->renderPolyline( QPolygonF() << QPointF( x1, y1 ) << QPointF( x2, y2 ), context.feature(), context.renderContext(), -1, useSelectedColor );
3399 }
3400 }
3401
3402 p->restore();
3403
3405}
3406
3408{
3409 QVariantMap map = QgsImageFillSymbolLayer::properties();
3410 map.insert( QStringLiteral( "angle" ), QString::number( mLineAngle ) );
3411 map.insert( QStringLiteral( "distance" ), QString::number( mDistance ) );
3412 map.insert( QStringLiteral( "line_width" ), QString::number( mLineWidth ) );
3413 map.insert( QStringLiteral( "color" ), QgsColorUtils::colorToString( mColor ) );
3414 map.insert( QStringLiteral( "offset" ), QString::number( mOffset ) );
3415 map.insert( QStringLiteral( "distance_unit" ), QgsUnitTypes::encodeUnit( mDistanceUnit ) );
3416 map.insert( QStringLiteral( "line_width_unit" ), QgsUnitTypes::encodeUnit( mLineWidthUnit ) );
3417 map.insert( QStringLiteral( "offset_unit" ), QgsUnitTypes::encodeUnit( mOffsetUnit ) );
3418 map.insert( QStringLiteral( "distance_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale ) );
3419 map.insert( QStringLiteral( "line_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineWidthMapUnitScale ) );
3420 map.insert( QStringLiteral( "offset_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ) );
3421 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3422 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3423 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeLineClipMode( mClipMode ) );
3424 return map;
3425}
3426
3428{
3430 if ( mFillLineSymbol )
3431 {
3432 clonedLayer->setSubSymbol( mFillLineSymbol->clone() );
3433 }
3434 copyPaintEffect( clonedLayer );
3435 copyDataDefinedProperties( clonedLayer );
3436 return clonedLayer;
3437}
3438
3439void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3440{
3441 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
3442 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
3443 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
3444 element.appendChild( symbolizerElem );
3445
3446 // <Geometry>
3447 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
3448
3449 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
3450 symbolizerElem.appendChild( fillElem );
3451
3452 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
3453 fillElem.appendChild( graphicFillElem );
3454
3455 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
3456 graphicFillElem.appendChild( graphicElem );
3457
3458 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
3459
3460 // Export to PNG (TODO: SVG)
3461 bool exportOk { false };
3462 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
3463 {
3464 const QImage image { toTiledPatternImage() };
3465 if ( ! image.isNull() )
3466 {
3467 const QFileInfo info { context.exportFilePath() };
3468 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
3469 pngPath = QgsFileUtils::uniquePath( pngPath );
3470 image.save( pngPath );
3471 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
3472 exportOk = true;
3473 }
3474 }
3475
3476 if ( ! exportOk )
3477 {
3478 //line properties must be inside the graphic definition
3479 QColor lineColor = mFillLineSymbol ? mFillLineSymbol->color() : QColor();
3480 double lineWidth = mFillLineSymbol ? mFillLineSymbol->width() : 0.0;
3481 lineWidth = QgsSymbolLayerUtils::rescaleUom( lineWidth, mLineWidthUnit, props );
3482 double distance = QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, props );
3483 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "horline" ), QColor(), lineColor, Qt::SolidLine, lineWidth, distance );
3484
3485 // <Rotation>
3486 QString angleFunc;
3487 bool ok;
3488 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
3489 if ( !ok )
3490 {
3491 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mLineAngle );
3492 }
3493 else if ( !qgsDoubleNear( angle + mLineAngle, 0.0 ) )
3494 {
3495 angleFunc = QString::number( angle + mLineAngle );
3496 }
3497 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
3498
3499 // <se:Displacement>
3500 QPointF lineOffset( std::sin( mLineAngle ) * mOffset, std::cos( mLineAngle ) * mOffset );
3501 lineOffset = QgsSymbolLayerUtils::rescaleUom( lineOffset, mOffsetUnit, props );
3502 QgsSymbolLayerUtils::createDisplacementElement( doc, graphicElem, lineOffset );
3503 }
3504}
3505
3506QString QgsLinePatternFillSymbolLayer::ogrFeatureStyleWidth( double widthScaleFactor ) const
3507{
3508 QString featureStyle;
3509 featureStyle.append( "Brush(" );
3510 featureStyle.append( QStringLiteral( "fc:%1" ).arg( mColor.name() ) );
3511 featureStyle.append( QStringLiteral( ",bc:%1" ).arg( QLatin1String( "#00000000" ) ) ); //transparent background
3512 featureStyle.append( ",id:\"ogr-brush-2\"" );
3513 featureStyle.append( QStringLiteral( ",a:%1" ).arg( mLineAngle ) );
3514 featureStyle.append( QStringLiteral( ",s:%1" ).arg( mLineWidth * widthScaleFactor ) );
3515 featureStyle.append( ",dx:0mm" );
3516 featureStyle.append( QStringLiteral( ",dy:%1mm" ).arg( mDistance * widthScaleFactor ) );
3517 featureStyle.append( ')' );
3518 return featureStyle;
3519}
3520
3522{
3524 && ( !mFillLineSymbol || !mFillLineSymbol->hasDataDefinedProperties() ) )
3525 {
3526 return; //no data defined settings
3527 }
3528
3529 double lineAngle = mLineAngle;
3531 {
3532 context.setOriginalValueVariable( mLineAngle );
3534 }
3535 double distance = mDistance;
3537 {
3538 context.setOriginalValueVariable( mDistance );
3540 }
3541 applyPattern( context, mBrush, lineAngle, distance );
3542}
3543
3545{
3546 QString name;
3547 QColor fillColor, lineColor;
3548 double size, lineWidth;
3549 Qt::PenStyle lineStyle;
3550
3551 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
3552 if ( fillElem.isNull() )
3553 return nullptr;
3554
3555 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
3556 if ( graphicFillElem.isNull() )
3557 return nullptr;
3558
3559 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
3560 if ( graphicElem.isNull() )
3561 return nullptr;
3562
3563 if ( !QgsSymbolLayerUtils::wellKnownMarkerFromSld( graphicElem, name, fillColor, lineColor, lineStyle, lineWidth, size ) )
3564 return nullptr;
3565
3566 if ( name != QLatin1String( "horline" ) )
3567 return nullptr;
3568
3569 double angle = 0.0;
3570 QString angleFunc;
3571 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3572 {
3573 bool ok;
3574 double d = angleFunc.toDouble( &ok );
3575 if ( ok )
3576 angle = d;
3577 }
3578
3579 double offset = 0.0;
3580 QPointF vectOffset;
3581 if ( QgsSymbolLayerUtils::displacementFromSldElement( graphicElem, vectOffset ) )
3582 {
3583 offset = std::sqrt( std::pow( vectOffset.x(), 2 ) + std::pow( vectOffset.y(), 2 ) );
3584 }
3585
3586 double scaleFactor = 1.0;
3587 const QString uom = element.attribute( QStringLiteral( "uom" ) );
3588 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
3589 size = size * scaleFactor;
3590 lineWidth = lineWidth * scaleFactor;
3591
3592 std::unique_ptr< QgsLinePatternFillSymbolLayer > sl = std::make_unique< QgsLinePatternFillSymbolLayer >();
3593 sl->setOutputUnit( sldUnitSize );
3594 sl->setColor( lineColor );
3595 sl->setLineWidth( lineWidth );
3596 sl->setLineAngle( angle );
3597 sl->setOffset( offset );
3598 sl->setDistance( size );
3599
3600 // try to get the stroke
3601 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
3602 if ( !strokeElem.isNull() )
3603 {
3605 if ( l )
3606 {
3607 QgsSymbolLayerList layers;
3608 layers.append( l );
3609 sl->setSubSymbol( new QgsLineSymbol( layers ) );
3610 }
3611 }
3612
3613 return sl.release();
3614}
3615
3616
3618
3621{
3622 mMarkerSymbol = std::make_unique<QgsMarkerSymbol>();
3623 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
3624}
3625
3627
3629{
3631 mDistanceXUnit = unit;
3632 mDistanceYUnit = unit;
3633 // don't change "percentage" units -- since they adapt directly to whatever other unit is set
3635 mDisplacementXUnit = unit;
3637 mDisplacementYUnit = unit;
3639 mOffsetXUnit = unit;
3641 mOffsetYUnit = unit;
3643 mRandomDeviationXUnit = unit;
3645 mRandomDeviationYUnit = unit;
3646
3647 if ( mMarkerSymbol )
3648 {
3649 mMarkerSymbol->setOutputUnit( unit );
3650 }
3651}
3652
3669
3681
3694
3710
3712{
3713 std::unique_ptr< QgsPointPatternFillSymbolLayer > layer = std::make_unique< QgsPointPatternFillSymbolLayer >();
3714 if ( properties.contains( QStringLiteral( "distance_x" ) ) )
3715 {
3716 layer->setDistanceX( properties[QStringLiteral( "distance_x" )].toDouble() );
3717 }
3718 if ( properties.contains( QStringLiteral( "distance_y" ) ) )
3719 {
3720 layer->setDistanceY( properties[QStringLiteral( "distance_y" )].toDouble() );
3721 }
3722 if ( properties.contains( QStringLiteral( "displacement_x" ) ) )
3723 {
3724 layer->setDisplacementX( properties[QStringLiteral( "displacement_x" )].toDouble() );
3725 }
3726 if ( properties.contains( QStringLiteral( "displacement_y" ) ) )
3727 {
3728 layer->setDisplacementY( properties[QStringLiteral( "displacement_y" )].toDouble() );
3729 }
3730 if ( properties.contains( QStringLiteral( "offset_x" ) ) )
3731 {
3732 layer->setOffsetX( properties[QStringLiteral( "offset_x" )].toDouble() );
3733 }
3734 if ( properties.contains( QStringLiteral( "offset_y" ) ) )
3735 {
3736 layer->setOffsetY( properties[QStringLiteral( "offset_y" )].toDouble() );
3737 }
3738
3739 if ( properties.contains( QStringLiteral( "distance_x_unit" ) ) )
3740 {
3741 layer->setDistanceXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_x_unit" )].toString() ) );
3742 }
3743 if ( properties.contains( QStringLiteral( "distance_x_map_unit_scale" ) ) )
3744 {
3745 layer->setDistanceXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_x_map_unit_scale" )].toString() ) );
3746 }
3747 if ( properties.contains( QStringLiteral( "distance_y_unit" ) ) )
3748 {
3749 layer->setDistanceYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_y_unit" )].toString() ) );
3750 }
3751 if ( properties.contains( QStringLiteral( "distance_y_map_unit_scale" ) ) )
3752 {
3753 layer->setDistanceYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_y_map_unit_scale" )].toString() ) );
3754 }
3755 if ( properties.contains( QStringLiteral( "displacement_x_unit" ) ) )
3756 {
3757 layer->setDisplacementXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_x_unit" )].toString() ) );
3758 }
3759 if ( properties.contains( QStringLiteral( "displacement_x_map_unit_scale" ) ) )
3760 {
3761 layer->setDisplacementXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_x_map_unit_scale" )].toString() ) );
3762 }
3763 if ( properties.contains( QStringLiteral( "displacement_y_unit" ) ) )
3764 {
3765 layer->setDisplacementYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_y_unit" )].toString() ) );
3766 }
3767 if ( properties.contains( QStringLiteral( "displacement_y_map_unit_scale" ) ) )
3768 {
3769 layer->setDisplacementYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_y_map_unit_scale" )].toString() ) );
3770 }
3771 if ( properties.contains( QStringLiteral( "offset_x_unit" ) ) )
3772 {
3773 layer->setOffsetXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_x_unit" )].toString() ) );
3774 }
3775 if ( properties.contains( QStringLiteral( "offset_x_map_unit_scale" ) ) )
3776 {
3777 layer->setOffsetXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_x_map_unit_scale" )].toString() ) );
3778 }
3779 if ( properties.contains( QStringLiteral( "offset_y_unit" ) ) )
3780 {
3781 layer->setOffsetYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_y_unit" )].toString() ) );
3782 }
3783 if ( properties.contains( QStringLiteral( "offset_y_map_unit_scale" ) ) )
3784 {
3785 layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_y_map_unit_scale" )].toString() ) );
3786 }
3787
3788 if ( properties.contains( QStringLiteral( "random_deviation_x" ) ) )
3789 {
3790 layer->setMaximumRandomDeviationX( properties[QStringLiteral( "random_deviation_x" )].toDouble() );
3791 }
3792 if ( properties.contains( QStringLiteral( "random_deviation_y" ) ) )
3793 {
3794 layer->setMaximumRandomDeviationY( properties[QStringLiteral( "random_deviation_y" )].toDouble() );
3795 }
3796 if ( properties.contains( QStringLiteral( "random_deviation_x_unit" ) ) )
3797 {
3798 layer->setRandomDeviationXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_x_unit" )].toString() ) );
3799 }
3800 if ( properties.contains( QStringLiteral( "random_deviation_x_map_unit_scale" ) ) )
3801 {
3802 layer->setRandomDeviationXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_x_map_unit_scale" )].toString() ) );
3803 }
3804 if ( properties.contains( QStringLiteral( "random_deviation_y_unit" ) ) )
3805 {
3806 layer->setRandomDeviationYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_y_unit" )].toString() ) );
3807 }
3808 if ( properties.contains( QStringLiteral( "random_deviation_y_map_unit_scale" ) ) )
3809 {
3810 layer->setRandomDeviationYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_y_map_unit_scale" )].toString() ) );
3811 }
3812 unsigned long seed = 0;
3813 if ( properties.contains( QStringLiteral( "seed" ) ) )
3814 seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
3815 else
3816 {
3817 // if we a creating a new point pattern fill from scratch, we default to a random seed
3818 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
3819 std::random_device rd;
3820 std::mt19937 mt( seed == 0 ? rd() : seed );
3821 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
3822 seed = uniformDist( mt );
3823 }
3824 layer->setSeed( seed );
3825
3826 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
3827 {
3828 layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
3829 }
3830 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
3831 {
3832 layer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
3833 }
3834 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
3835 {
3836 layer->setClipMode( QgsSymbolLayerUtils::decodeMarkerClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
3837 }
3838 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
3839 {
3840 layer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
3841 }
3842
3843 if ( properties.contains( QStringLiteral( "angle" ) ) )
3844 {
3845 layer->setAngle( properties[QStringLiteral( "angle" )].toDouble() );
3846 }
3847
3848 layer->restoreOldDataDefinedProperties( properties );
3849
3850 return layer.release();
3851}
3852
3854{
3855 return QStringLiteral( "PointPatternFill" );
3856}
3857
3858bool QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double distanceX, double distanceY,
3859 double displacementX, double displacementY, double offsetX, double offsetY )
3860{
3861 //render 3 rows and columns in one go to easily incorporate displacement
3862 const QgsRenderContext &ctx = context.renderContext();
3865
3866 double widthOffset = std::fmod(
3867 mOffsetXUnit == Qgis::RenderUnit::Percentage ? ( width * offsetX / 200 ) : ctx.convertToPainterUnits( offsetX, mOffsetXUnit, mOffsetXMapUnitScale ),
3868 width );
3869 double heightOffset = std::fmod(
3870 mOffsetYUnit == Qgis::RenderUnit::Percentage ? ( height * offsetY / 200 ) : ctx.convertToPainterUnits( offsetY, mOffsetYUnit, mOffsetYMapUnitScale ),
3871 height );
3872
3873 if ( width > 2000 || height > 2000 ) //protect symbol layer from eating too much memory
3874 {
3875 brush.setTextureImage( QImage() );
3876 return false;
3877 }
3878
3879 QImage patternImage( width, height, QImage::Format_ARGB32 );
3880 patternImage.fill( 0 );
3881 if ( patternImage.isNull() )
3882 {
3883 brush.setTextureImage( QImage() );
3884 return false;
3885 }
3886 if ( mMarkerSymbol )
3887 {
3888 QPainter p( &patternImage );
3889
3890 //marker rendering needs context for drawing on patternImage
3891 QgsRenderContext pointRenderContext;
3892 pointRenderContext.setRendererScale( context.renderContext().rendererScale() );
3893 pointRenderContext.setPainter( &p );
3894 pointRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3895
3898 pointRenderContext.setMapToPixel( mtp );
3899 pointRenderContext.setForceVectorOutput( false );
3900 pointRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3902
3904 mMarkerSymbol->startRender( pointRenderContext, context.fields() );
3905
3906 //render points on distance grid
3907 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3908 {
3909 for ( double currentY = -height; currentY <= height * 2.0; currentY += height )
3910 {
3911 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset, currentY + heightOffset ), context.feature(), pointRenderContext );
3912 }
3913 }
3914
3915 //render displaced points
3916 double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
3917 ? ( width * displacementX / 200 )
3918 : ctx.convertToPainterUnits( displacementX, mDisplacementXUnit, mDisplacementXMapUnitScale );
3919 double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
3920 ? ( height * displacementY / 200 )
3921 : ctx.convertToPainterUnits( displacementY, mDisplacementYUnit, mDisplacementYMapUnitScale );
3922 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3923 {
3924 for ( double currentY = -height / 2.0; currentY <= height * 2.0; currentY += height )
3925 {
3926 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + displacementPixelX, currentY + heightOffset ), context.feature(), pointRenderContext );
3927 }
3928 }
3929
3930 for ( double currentX = -width / 2.0; currentX <= width * 2.0; currentX += width )
3931 {
3932 for ( double currentY = -height; currentY <= height * 2.0; currentY += height / 2.0 )
3933 {
3934 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + ( std::fmod( currentY, height ) != 0 ? displacementPixelX : 0 ), currentY + heightOffset - displacementPixelY ), context.feature(), pointRenderContext );
3935 }
3936 }
3937
3938 mMarkerSymbol->stopRender( pointRenderContext );
3939 }
3940
3941 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3942 {
3943 QImage transparentImage = patternImage.copy();
3944 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3945 brush.setTextureImage( transparentImage );
3946 }
3947 else
3948 {
3949 brush.setTextureImage( patternImage );
3950 }
3951 QTransform brushTransform;
3952 brush.setTransform( brushTransform );
3953
3954 return true;
3955}
3956
3958{
3959 // if we are using a vector based output, we need to render points as vectors
3960 // (OR if the marker has data defined symbology, in which case we need to evaluate this point-by-point)
3961 mRenderUsingMarkers = context.forceVectorRendering()
3962 || ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
3966 || mClipMode != Qgis::MarkerClipMode::Shape
3969 || !qgsDoubleNear( mAngle, 0 )
3971
3972 if ( !mRenderUsingMarkers )
3973 {
3974 // optimised render for screen only, use image based brush
3975 // (fallback to line rendering when pattern image will result in too large a memory footprint)
3976 mRenderUsingMarkers = !applyPattern( context, mBrush, mDistanceX, mDistanceY, mDisplacementX, mDisplacementY, mOffsetX, mOffsetY );
3977 }
3978
3979 if ( mRenderUsingMarkers && mMarkerSymbol )
3980 {
3982 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
3984 }
3985}
3986
3988{
3990 {
3991 mMarkerSymbol->stopRender( context.renderContext() );
3993 }
3994}
3995
3997{
3998 installMasks( context, true );
3999
4000 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4001 // Otherwise generators used in the subsymbol will only render a single point per feature (they
4002 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
4003}
4004
4006{
4007 removeMasks( context, true );
4008
4009 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4010 // Otherwise generators used in the subsymbol will only render a single point per feature (they
4011 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
4012}
4013
4014void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4015{
4016 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4017 if ( !useSelectedColor && !mRenderUsingMarkers )
4018 {
4019 // use image based brush for speed
4020 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
4021 return;
4022 }
4023
4025 {
4027 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
4029 }
4030
4031 // vector based output - so draw dot by dot!
4032 QPainter *p = context.renderContext().painter();
4033 if ( !p )
4034 {
4035 return;
4036 }
4037
4038 double angle = mAngle;
4040 {
4043 }
4044
4045 double distanceX = mDistanceX;
4047 {
4050 }
4052
4053 double distanceY = mDistanceY;
4055 {
4058 }
4060
4061 double offsetX = mOffsetX;
4063 {
4066 }
4067 const double widthOffset = std::fmod(
4069 ? ( offsetX * width / 100 )
4071 width );
4072
4073 double offsetY = mOffsetY;
4075 {
4078 }
4079 const double heightOffset = std::fmod(
4081 ? ( offsetY * height / 100 )
4083 height );
4084
4087 {
4090 }
4091 const double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
4092 ? ( displacementX * width / 100 )
4094
4097 {
4100 }
4101 const double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
4102 ? ( displacementY * height / 100 )
4104
4105 p->setPen( QPen( Qt::NoPen ) );
4106
4107 // if invalid parameters, skip out
4108 if ( qgsDoubleNear( width, 0 ) || qgsDoubleNear( height, 0 ) || width < 0 || height < 0 )
4109 return;
4110
4111 p->save();
4112
4113 Qgis::MarkerClipMode clipMode = mClipMode;
4115 {
4117 bool ok = false;
4118 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::MarkerClipping, context.renderContext().expressionContext(), QString(), &ok );
4119 if ( ok )
4120 {
4121 Qgis::MarkerClipMode decodedMode = QgsSymbolLayerUtils::decodeMarkerClipMode( valueString, &ok );
4122 if ( ok )
4123 clipMode = decodedMode;
4124 }
4125 }
4126
4127 std::unique_ptr< QgsPolygon > shapePolygon;
4128 std::unique_ptr< QgsGeometryEngine > shapeEngine;
4129 switch ( clipMode )
4130 {
4134 {
4135 shapePolygon = std::make_unique< QgsPolygon >();
4136 std::unique_ptr< QgsLineString > fromPolygon = QgsLineString::fromQPolygonF( points );
4137 shapePolygon->setExteriorRing( fromPolygon.release() );
4138 if ( rings )
4139 {
4140 for ( const QPolygonF &ring : *rings )
4141 {
4142 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
4143 shapePolygon->addInteriorRing( fromRing.release() );
4144 }
4145 }
4146 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
4147 shapeEngine->prepareGeometry();
4148 break;
4149 }
4150
4152 {
4153 QPainterPath path;
4154 path.addPolygon( points );
4155 if ( rings )
4156 {
4157 for ( const QPolygonF &ring : *rings )
4158 {
4159 path.addPolygon( ring );
4160 }
4161 }
4162 p->setClipPath( path, Qt::IntersectClip );
4163 break;
4164 }
4165 }
4166
4167 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
4168 const QRectF boundingRect = points.boundingRect();
4169
4170 QTransform invertedRotateTransform;
4171 double left;
4172 double top;
4173 double right;
4174 double bottom;
4175
4176 if ( !qgsDoubleNear( angle, 0 ) )
4177 {
4178 QTransform transform;
4179 if ( applyBrushTransform )
4180 {
4181 // rotation applies around center of feature
4182 transform.translate( -boundingRect.center().x(),
4183 -boundingRect.center().y() );
4184 transform.rotate( -angle );
4185 transform.translate( boundingRect.center().x(),
4186 boundingRect.center().y() );
4187 }
4188 else
4189 {
4190 // rotation applies around top of viewport
4191 transform.rotate( -angle );
4192 }
4193
4194 const QRectF transformedBounds = transform.map( points ).boundingRect();
4195 left = transformedBounds.left() - 2 * width;
4196 top = transformedBounds.top() - 2 * height;
4197 right = transformedBounds.right() + 2 * width;
4198 bottom = transformedBounds.bottom() + 2 * height;
4199 invertedRotateTransform = transform.inverted();
4200
4201 if ( !applyBrushTransform )
4202 {
4203 left -= transformedBounds.left() - ( width * std::floor( transformedBounds.left() / width ) );
4204 top -= transformedBounds.top() - ( height * std::floor( transformedBounds.top() / height ) );
4205 }
4206 }
4207 else
4208 {
4209 left = boundingRect.left() - 2 * width;
4210 top = boundingRect.top() - 2 * height;
4211 right = boundingRect.right() + 2 * width;
4212 bottom = boundingRect.bottom() + 2 * height;
4213
4214 if ( !applyBrushTransform )
4215 {
4216 left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
4217 top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
4218 }
4219 }
4220
4221 unsigned long seed = mSeed;
4223 {
4224 context.renderContext().expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
4226 }
4227
4228 double maxRandomDeviationX = mRandomDeviationX;
4230 {
4231 context.setOriginalValueVariable( maxRandomDeviationX );
4232 maxRandomDeviationX = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::RandomOffsetX, context.renderContext().expressionContext(), maxRandomDeviationX );
4233 }
4234 const double maxRandomDeviationPixelX = mRandomDeviationXUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationX * width / 100 )
4236
4237 double maxRandomDeviationY = mRandomDeviationY;
4239 {
4240 context.setOriginalValueVariable( maxRandomDeviationY );
4241 maxRandomDeviationY = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::RandomOffsetY, context.renderContext().expressionContext(), maxRandomDeviationY );
4242 }
4243 const double maxRandomDeviationPixelY = mRandomDeviationYUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationY * height / 100 )
4245
4246 std::random_device rd;
4247 std::mt19937 mt( seed == 0 ? rd() : seed );
4248 std::uniform_real_distribution<> uniformDist( 0, 1 );
4249 const bool useRandomShift = !qgsDoubleNear( maxRandomDeviationPixelX, 0 ) || !qgsDoubleNear( maxRandomDeviationPixelY, 0 );
4250
4252 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
4253 int pointNum = 0;
4254 const bool needsExpressionContext = mMarkerSymbol->hasDataDefinedProperties();
4255
4256 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4258
4259 const double prevOpacity = mMarkerSymbol->opacity();
4260 mMarkerSymbol->setOpacity( mMarkerSymbol->opacity() * context.opacity() );
4261
4262 bool alternateColumn = false;
4263 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
4264 for ( double currentX = left; currentX <= right; currentX += width, alternateColumn = !alternateColumn )
4265 {
4266 if ( context.renderContext().renderingStopped() )
4267 break;
4268
4269 if ( needsExpressionContext )
4270 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_column" ), ++currentCol, true ) );
4271
4272 bool alternateRow = false;
4273 const double columnX = currentX + widthOffset;
4274 int currentRow = -3;
4275 for ( double currentY = top; currentY <= bottom; currentY += height, alternateRow = !alternateRow )
4276 {
4277 if ( context.renderContext().renderingStopped() )
4278 break;
4279
4280 double y = currentY + heightOffset;
4281 double x = columnX;
4282 if ( alternateRow )
4283 x += displacementPixelX;
4284
4285 if ( !alternateColumn )
4286 y -= displacementPixelY;
4287
4288 if ( !qgsDoubleNear( angle, 0 ) )
4289 {
4290 double xx = x;
4291 double yy = y;
4292 invertedRotateTransform.map( xx, yy, &x, &y );
4293 }
4294
4295 if ( useRandomShift )
4296 {
4297 x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
4298 y += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelY;
4299 }
4300
4301 if ( needsExpressionContext )
4302 {
4304 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_row" ), ++currentRow, true ) );
4305 }
4306
4307 if ( shapeEngine )
4308 {
4309 bool renderPoint = true;
4310 switch ( clipMode )
4311 {
4313 {
4314 // 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
4315 const QgsRectangle markerRect = QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) );
4316 QgsPoint p( markerRect.center() );
4317 renderPoint = shapeEngine->intersects( &p );
4318 break;
4319 }
4320
4323 {
4324 const QgsGeometry markerBounds = QgsGeometry::fromRect( QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) ) );
4325
4327 renderPoint = shapeEngine->contains( markerBounds.constGet() );
4328 else
4329 renderPoint = shapeEngine->intersects( markerBounds.constGet() );
4330 break;
4331 }
4332
4334 break;
4335 }
4336
4337 if ( !renderPoint )
4338 continue;
4339 }
4340
4341 mMarkerSymbol->renderPoint( QPointF( x, y ), context.feature(), context.renderContext(), -1, useSelectedColor );
4342 }
4343 }
4344
4345 mMarkerSymbol->setOpacity( prevOpacity );
4346
4347 p->restore();
4348
4350}
4351
4353{
4354 QVariantMap map = QgsImageFillSymbolLayer::properties();
4355 map.insert( QStringLiteral( "distance_x" ), QString::number( mDistanceX ) );
4356 map.insert( QStringLiteral( "distance_y" ), QString::number( mDistanceY ) );
4357 map.insert( QStringLiteral( "displacement_x" ), QString::number( mDisplacementX ) );
4358 map.insert( QStringLiteral( "displacement_y" ), QString::number( mDisplacementY ) );
4359 map.insert( QStringLiteral( "offset_x" ), QString::number( mOffsetX ) );
4360 map.insert( QStringLiteral( "offset_y" ), QString::number( mOffsetY ) );
4361 map.insert( QStringLiteral( "distance_x_unit" ), QgsUnitTypes::encodeUnit( mDistanceXUnit ) );
4362 map.insert( QStringLiteral( "distance_y_unit" ), QgsUnitTypes::encodeUnit( mDistanceYUnit ) );
4363 map.insert( QStringLiteral( "displacement_x_unit" ), QgsUnitTypes::encodeUnit( mDisplacementXUnit ) );
4364 map.insert( QStringLiteral( "displacement_y_unit" ), QgsUnitTypes::encodeUnit( mDisplacementYUnit ) );
4365 map.insert( QStringLiteral( "offset_x_unit" ), QgsUnitTypes::encodeUnit( mOffsetXUnit ) );
4366 map.insert( QStringLiteral( "offset_y_unit" ), QgsUnitTypes::encodeUnit( mOffsetYUnit ) );
4367 map.insert( QStringLiteral( "distance_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceXMapUnitScale ) );
4368 map.insert( QStringLiteral( "distance_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceYMapUnitScale ) );
4369 map.insert( QStringLiteral( "displacement_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementXMapUnitScale ) );
4370 map.insert( QStringLiteral( "displacement_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementYMapUnitScale ) );
4371 map.insert( QStringLiteral( "offset_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetXMapUnitScale ) );
4372 map.insert( QStringLiteral( "offset_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetYMapUnitScale ) );
4373 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
4374 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
4375 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeMarkerClipMode( mClipMode ) );
4376 map.insert( QStringLiteral( "random_deviation_x" ), QString::number( mRandomDeviationX ) );
4377 map.insert( QStringLiteral( "random_deviation_y" ), QString::number( mRandomDeviationY ) );
4378 map.insert( QStringLiteral( "random_deviation_x_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationXUnit ) );
4379 map.insert( QStringLiteral( "random_deviation_y_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationYUnit ) );
4380 map.insert( QStringLiteral( "random_deviation_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationXMapUnitScale ) );
4381 map.insert( QStringLiteral( "random_deviation_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationYMapUnitScale ) );
4382 map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
4383 map.insert( QStringLiteral( "angle" ), mAngle );
4384 return map;
4385}
4386
4388{
4390 if ( mMarkerSymbol )
4391 {
4392 clonedLayer->setSubSymbol( mMarkerSymbol->clone() );
4393 }
4394 clonedLayer->setClipMode( mClipMode );
4395 copyDataDefinedProperties( clonedLayer );
4396 copyPaintEffect( clonedLayer );
4397 return clonedLayer;
4398}
4399
4400void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4401{
4402 for ( int symbolLayerIdx = 0; symbolLayerIdx < mMarkerSymbol->symbolLayerCount(); symbolLayerIdx++ )
4403 {
4404 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
4405 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
4406 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
4407 element.appendChild( symbolizerElem );
4408
4409 // <Geometry>
4410 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
4411
4412 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
4413 symbolizerElem.appendChild( fillElem );
4414
4415 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
4416 fillElem.appendChild( graphicFillElem );
4417
4418 QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( symbolLayerIdx );
4419
4420 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
4421
4422 // Export to PNG (TODO: SVG)
4423 bool exportOk { false };
4424 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
4425 {
4426 const QImage image { toTiledPatternImage( ) };
4427 if ( ! image.isNull() )
4428 {
4429 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
4430 graphicFillElem.appendChild( graphicElem );
4431 const QFileInfo info { context.exportFilePath() };
4432 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
4433 pngPath = QgsFileUtils::uniquePath( pngPath );
4434 image.save( pngPath );
4435 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
4436 exportOk = true;
4437 }
4438 }
4439
4440 if ( ! exportOk )
4441 {
4442 // Converts to GeoServer "graphic-margin": symbol size must be subtracted from distance and then divided by 2
4443 const double markerSize { mMarkerSymbol->size() };
4444
4445 // store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
4448 // From: https://docs.geoserver.org/stable/en/user/styling/sld/extensions/margins.html
4449 // top-bottom,right-left (two values, top and bottom sharing the same value)
4450 const QString marginSpec = QString( "%1 %2" ).arg( qgsDoubleToString( ( dy - markerSize ) / 2, 2 ), qgsDoubleToString( ( dx - markerSize ) / 2, 2 ) );
4451
4452 QDomElement graphicMarginElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), marginSpec );
4453 symbolizerElem.appendChild( graphicMarginElem );
4454
4455 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
4456 {
4457 markerLayer->writeSldMarker( doc, graphicFillElem, props );
4458 }
4459 else if ( layer )
4460 {
4461 QString errorMsg = QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() );
4462 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4463 }
4464 else
4465 {
4466 QString errorMsg = QStringLiteral( "Missing point pattern symbol layer. Skip it." );
4467 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4468 }
4469 }
4470 }
4471}
4472
4474{
4475
4476 double angleRads { qDegreesToRadians( mAngle ) };
4477
4478 int distanceXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, {} ) ) };
4479 int distanceYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, {} ) ) };
4480
4481 const int displacementXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementX, mDisplacementXUnit, {} ) ) };
4482 const int displacementYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementY, mDisplacementYUnit, {} ) ) };
4483
4484 // Consider displacement, double the distance.
4485 if ( displacementXPx != 0 )
4486 {
4487 distanceXPx *= 2;
4488 }
4489
4490 if ( displacementYPx != 0 )
4491 {
4492 distanceYPx *= 2;
4493 }
4494
4495 const QSize size { QgsSymbolLayerUtils::tileSize( distanceXPx, distanceYPx, angleRads ) };
4496
4497 QPixmap pixmap( size );
4498 pixmap.fill( Qt::transparent );
4499 QPainter painter;
4500 painter.begin( &pixmap );
4501 painter.setRenderHint( QPainter::Antialiasing );
4502 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
4506 renderContext.setForceVectorOutput( true );
4507 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
4508
4509 std::unique_ptr< QgsPointPatternFillSymbolLayer > layerClone( clone() );
4510
4511 layerClone->setAngle( qRadiansToDegrees( angleRads ) );
4512
4513 // No way we can export a random pattern, disable it.
4514 layerClone->setMaximumRandomDeviationX( 0 );
4515 layerClone->setMaximumRandomDeviationY( 0 );
4516
4517 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
4518 painter.end();
4519 return pixmap.toImage();
4520}
4521
4523{
4524
4525 // input element is PolygonSymbolizer
4526
4527 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
4528 if ( fillElem.isNull() )
4529 return nullptr;
4530
4531 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
4532 if ( graphicFillElem.isNull() )
4533 return nullptr;
4534
4535 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
4536 if ( graphicElem.isNull() )
4537 return nullptr;
4538
4539 QgsSymbolLayer *simpleMarkerSl = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicFillElem );
4540 if ( !simpleMarkerSl )
4541 return nullptr;
4542
4543
4544 QgsSymbolLayerList layers;
4545 layers.append( simpleMarkerSl );
4546
4547 std::unique_ptr< QgsMarkerSymbol > marker = std::make_unique< QgsMarkerSymbol >( layers );
4548
4549 // Converts from GeoServer "graphic-margin": symbol size must be added and margin doubled
4550 const double markerSize { marker->size() };
4551
4552 std::unique_ptr< QgsPointPatternFillSymbolLayer > pointPatternFillSl = std::make_unique< QgsPointPatternFillSymbolLayer >();
4553 pointPatternFillSl->setSubSymbol( marker.release() );
4554 // This may not be correct in all cases, TODO: check "uom"
4555 pointPatternFillSl->setDistanceXUnit( Qgis::RenderUnit::Pixels );
4556 pointPatternFillSl->setDistanceYUnit( Qgis::RenderUnit::Pixels );
4557
4558 auto distanceParser = [ & ]( const QStringList & values )
4559 {
4560 switch ( values.count( ) )
4561 {
4562 case 1: // top-right-bottom-left (single value for all four margins)
4563 {
4564 bool ok;
4565 const double v { values.at( 0 ).toDouble( &ok ) };
4566 if ( ok )
4567 {
4568 pointPatternFillSl->setDistanceX( v * 2 + markerSize );
4569 pointPatternFillSl->setDistanceY( v * 2 + markerSize );
4570 }
4571 break;
4572 }
4573 case 2: // top-bottom,right-left (two values, top and bottom sharing the same value)
4574 {
4575 bool ok;
4576 const double vX { values.at( 1 ).toDouble( &ok ) };
4577 if ( ok )
4578 {
4579 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4580 }
4581 const double vY { values.at( 0 ).toDouble( &ok ) };
4582 if ( ok )
4583 {
4584 pointPatternFillSl->setDistanceY( vY * 2 + markerSize );
4585 }
4586 break;
4587 }
4588 case 3: // top,right-left,bottom (three values, with right and left sharing the same value)
4589 {
4590 bool ok;
4591 const double vX { values.at( 1 ).toDouble( &ok ) };
4592 if ( ok )
4593 {
4594 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4595 }
4596 const double vYt { values.at( 0 ).toDouble( &ok ) };
4597 if ( ok )
4598 {
4599 const double vYb { values.at( 2 ).toDouble( &ok ) };
4600 if ( ok )
4601 {
4602 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4603 }
4604 }
4605 break;
4606 }
4607 case 4: // top,right,bottom,left (one explicit value per margin)
4608 {
4609 bool ok;
4610 const double vYt { values.at( 0 ).toDouble( &ok ) };
4611 if ( ok )
4612 {
4613 const double vYb { values.at( 2 ).toDouble( &ok ) };
4614 if ( ok )
4615 {
4616 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4617 }
4618 }
4619 const double vXr { values.at( 1 ).toDouble( &ok ) };
4620 if ( ok )
4621 {
4622 const double vXl { values.at( 3 ).toDouble( &ok ) };
4623 if ( ok )
4624 {
4625 pointPatternFillSl->setDistanceX( ( vXr + vXl ) + markerSize );
4626 }
4627 }
4628 break;
4629 }
4630 default:
4631 break;
4632 }
4633 };
4634
4635 // Set distance X and Y from vendor options, or from Size if no vendor options are set
4636 bool distanceFromVendorOption { false };
4637 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
4638 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
4639 {
4640 // Legacy
4641 if ( it.key() == QLatin1String( "distance" ) )
4642 {
4643 distanceParser( it.value().split( ',' ) );
4644 distanceFromVendorOption = true;
4645 }
4646 // GeoServer
4647 else if ( it.key() == QLatin1String( "graphic-margin" ) )
4648 {
4649 distanceParser( it.value().split( ' ' ) );
4650 distanceFromVendorOption = true;
4651 }
4652 }
4653
4654 // Get distances from size
4655 if ( ! distanceFromVendorOption && ! graphicFillElem.elementsByTagName( QStringLiteral( "Size" ) ).isEmpty() )
4656 {
4657 const QDomElement sizeElement { graphicFillElem.elementsByTagName( QStringLiteral( "Size" ) ).at( 0 ).toElement() };
4658 bool ok;
4659 const double size { sizeElement.text().toDouble( &ok ) };
4660 if ( ok )
4661 {
4662 pointPatternFillSl->setDistanceX( size );
4663 pointPatternFillSl->setDistanceY( size );
4664 }
4665 }
4666
4667 return pointPatternFillSl.release();
4668}
4669
4671{
4672 if ( !symbol )
4673 {
4674 return false;
4675 }
4676
4677 if ( symbol->type() == Qgis::SymbolType::Marker )
4678 {
4679 QgsMarkerSymbol *markerSymbol = static_cast<QgsMarkerSymbol *>( symbol );
4680 mMarkerSymbol.reset( markerSymbol );
4681 }
4682 return true;
4683}
4684
4689
4691{
4695 && ( !mMarkerSymbol || !mMarkerSymbol->hasDataDefinedProperties() ) )
4696 {
4697 return;
4698 }
4699
4700 double distanceX = mDistanceX;
4702 {
4705 }
4706 double distanceY = mDistanceY;
4708 {
4711 }
4714 {
4717 }
4720 {
4723 }
4724 double offsetX = mOffsetX;
4726 {
4729 }
4730 double offsetY = mOffsetY;
4732 {
4735 }
4736 applyPattern( context, mBrush, distanceX, distanceY, displacementX, displacementY, offsetX, offsetY );
4737}
4738
4740{
4741 return 0;
4742}
4743
4745{
4746 QSet<QString> attributes = QgsImageFillSymbolLayer::usedAttributes( context );
4747
4748 if ( mMarkerSymbol )
4749 attributes.unite( mMarkerSymbol->usedAttributes( context ) );
4750
4751 return attributes;
4752}
4753
4755{
4757 return true;
4758 if ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
4759 return true;
4760 return false;
4761}
4762
4764{
4765 mColor = c;
4766 if ( mMarkerSymbol )
4767 mMarkerSymbol->setColor( c );
4768}
4769
4771{
4772 return mMarkerSymbol ? mMarkerSymbol->color() : mColor;
4773}
4774
4776
4777
4782
4784
4786{
4787 std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4788
4789 if ( properties.contains( QStringLiteral( "point_on_surface" ) ) )
4790 sl->setPointOnSurface( properties[QStringLiteral( "point_on_surface" )].toInt() != 0 );
4791 if ( properties.contains( QStringLiteral( "point_on_all_parts" ) ) )
4792 sl->setPointOnAllParts( properties[QStringLiteral( "point_on_all_parts" )].toInt() != 0 );
4793 if ( properties.contains( QStringLiteral( "clip_points" ) ) )
4794 sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() != 0 );
4795 if ( properties.contains( QStringLiteral( "clip_on_current_part_only" ) ) )
4796 sl->setClipOnCurrentPartOnly( properties[QStringLiteral( "clip_on_current_part_only" )].toInt() != 0 );
4797
4798 sl->restoreOldDataDefinedProperties( properties );
4799
4800 return sl.release();
4801}
4802
4804{
4805 return QStringLiteral( "CentroidFill" );
4806}
4807
4808void QgsCentroidFillSymbolLayer::setColor( const QColor &color )
4809{
4810 mMarker->setColor( color );
4811 mColor = color;
4812}
4813
4815{
4816 return mMarker ? mMarker->color() : mColor;
4817}
4818
4820{
4821 mMarker->setRenderHints( mMarker->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
4822 mMarker->startRender( context.renderContext(), context.fields() );
4823}
4824
4826{
4827 mMarker->stopRender( context.renderContext() );
4828}
4829
4830void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4831{
4832 Part part;
4833 part.exterior = points;
4834 if ( rings )
4835 part.rings = *rings;
4836
4837 if ( mRenderingFeature )
4838 {
4839 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
4840 // until after we've received the final part
4841 mFeatureSymbolOpacity = context.opacity();
4843 mCurrentParts << part;
4844 }
4845 else
4846 {
4847 // not rendering a feature, so we can just render the polygon immediately
4848 const double prevOpacity = mMarker->opacity();
4849 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
4850 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4851 render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
4852 mMarker->setOpacity( prevOpacity );
4853 }
4854}
4855
4857{
4858 installMasks( context, true );
4859
4860 mRenderingFeature = true;
4861 mCurrentParts.clear();
4862}
4863
4865{
4866 mRenderingFeature = false;
4867
4868 const double prevOpacity = mMarker->opacity();
4869 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
4870
4871 render( context, mCurrentParts, feature, mUseSelectedColor );
4873 mUseSelectedColor = false;
4874 mMarker->setOpacity( prevOpacity );
4875
4876 removeMasks( context, true );
4877}
4878
4879void QgsCentroidFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsCentroidFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
4880{
4883 bool clipPoints = mClipPoints;
4885
4886 // TODO add expressions support
4887
4888 QVector< QgsGeometry > geometryParts;
4889 geometryParts.reserve( parts.size() );
4890 QPainterPath globalPath;
4891
4892 int maxArea = 0;
4893 int maxAreaPartIdx = 0;
4894
4895 for ( int i = 0; i < parts.size(); i++ )
4896 {
4897 const Part part = parts[i];
4898 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
4899
4900 if ( !geom.isNull() && !part.rings.empty() )
4901 {
4902 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
4903
4904 if ( !pointOnAllParts )
4905 {
4906 int area = poly->area();
4907
4908 if ( area > maxArea )
4909 {
4910 maxArea = area;
4911 maxAreaPartIdx = i;
4912 }
4913 }
4914 }
4915
4917 {
4918 globalPath.addPolygon( part.exterior );
4919 for ( const QPolygonF &ring : part.rings )
4920 {
4921 globalPath.addPolygon( ring );
4922 }
4923 }
4924 }
4925
4926 for ( int i = 0; i < parts.size(); i++ )
4927 {
4928 if ( !pointOnAllParts && i != maxAreaPartIdx )
4929 continue;
4930
4931 const Part part = parts[i];
4932
4933 if ( clipPoints )
4934 {
4935 QPainterPath path;
4936
4938 {
4939 path.addPolygon( part.exterior );
4940 for ( const QPolygonF &ring : part.rings )
4941 {
4942 path.addPolygon( ring );
4943 }
4944 }
4945 else
4946 {
4947 path = globalPath;
4948 }
4949
4950 context.painter()->save();
4951 context.painter()->setClipPath( path );
4952 }
4953
4954 QPointF centroid = pointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( part.exterior, &part.rings ) : QgsSymbolLayerUtils::polygonCentroid( part.exterior );
4955
4956 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4958 mMarker->renderPoint( centroid, feature.isValid() ? &feature : nullptr, context, -1, selected );
4959 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
4960
4961 if ( clipPoints )
4962 {
4963 context.painter()->restore();
4964 }
4965 }
4966}
4967
4969{
4970 QVariantMap map;
4971 map[QStringLiteral( "point_on_surface" )] = QString::number( mPointOnSurface );
4972 map[QStringLiteral( "point_on_all_parts" )] = QString::number( mPointOnAllParts );
4973 map[QStringLiteral( "clip_points" )] = QString::number( mClipPoints );
4974 map[QStringLiteral( "clip_on_current_part_only" )] = QString::number( mClipOnCurrentPartOnly );
4975 return map;
4976}
4977
4979{
4980 std::unique_ptr< QgsCentroidFillSymbolLayer > x = std::make_unique< QgsCentroidFillSymbolLayer >();
4981 x->mAngle = mAngle;
4982 x->mColor = mColor;
4983 x->setSubSymbol( mMarker->clone() );
4984 x->setPointOnSurface( mPointOnSurface );
4985 x->setPointOnAllParts( mPointOnAllParts );
4986 x->setClipPoints( mClipPoints );
4987 x->setClipOnCurrentPartOnly( mClipOnCurrentPartOnly );
4988 copyDataDefinedProperties( x.get() );
4989 copyPaintEffect( x.get() );
4990 return x.release();
4991}
4992
4993void QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4994{
4995 // SLD 1.0 specs says: "if a line, polygon, or raster geometry is
4996 // used with PointSymbolizer, then the semantic is to use the centroid
4997 // of the geometry, or any similar representative point.
4998 mMarker->toSld( doc, element, props );
4999}
5000
5002{
5004 if ( !l )
5005 return nullptr;
5006
5007 QgsSymbolLayerList layers;
5008 layers.append( l );
5009 std::unique_ptr< QgsMarkerSymbol > marker( new QgsMarkerSymbol( layers ) );
5010
5011 std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
5012 sl->setSubSymbol( marker.release() );
5013 sl->setPointOnAllParts( false );
5014 return sl.release();
5015}
5016
5017
5022
5024{
5025 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5026 {
5027 delete symbol;
5028 return false;
5029 }
5030
5031 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5032 mColor = mMarker->color();
5033 return true;
5034}
5035
5037{
5038 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5039
5040 if ( mMarker )
5041 attributes.unite( mMarker->usedAttributes( context ) );
5042
5043 return attributes;
5044}
5045
5047{
5049 return true;
5050 if ( mMarker && mMarker->hasDataDefinedProperties() )
5051 return true;
5052 return false;
5053}
5054
5059
5061{
5062 if ( mMarker )
5063 {
5064 mMarker->setOutputUnit( unit );
5065 }
5066}
5067
5069{
5070 if ( mMarker )
5071 {
5072 return mMarker->outputUnit();
5073 }
5074 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5075}
5076
5078{
5079 if ( mMarker )
5080 {
5081 return mMarker->usesMapUnits();
5082 }
5083 return false;
5084}
5085
5087{
5088 if ( mMarker )
5089 {
5090 mMarker->setMapUnitScale( scale );
5091 }
5092}
5093
5095{
5096 if ( mMarker )
5097 {
5098 return mMarker->mapUnitScale();
5099 }
5100 return QgsMapUnitScale();
5101}
5102
5103
5104
5105
5108 , mImageFilePath( imageFilePath )
5109{
5110 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //disable sub symbol
5112}
5113
5115
5116QgsSymbolLayer *QgsRasterFillSymbolLayer::create( const QVariantMap &properties )
5117{
5119 double alpha = 1.0;
5120 QPointF offset;
5121 double angle = 0.0;
5122 double width = 0.0;
5123
5124 QString imagePath;
5125 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
5126 {
5127 imagePath = properties[QStringLiteral( "imageFile" )].toString();
5128 }
5129 if ( properties.contains( QStringLiteral( "coordinate_mode" ) ) )
5130 {
5131 mode = static_cast< Qgis::SymbolCoordinateReference >( properties[QStringLiteral( "coordinate_mode" )].toInt() );
5132 }
5133 if ( properties.contains( QStringLiteral( "alpha" ) ) )
5134 {
5135 alpha = properties[QStringLiteral( "alpha" )].toDouble();
5136 }
5137 if ( properties.contains( QStringLiteral( "offset" ) ) )
5138 {
5139 offset = QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "offset" )].toString() );
5140 }
5141 if ( properties.contains( QStringLiteral( "angle" ) ) )
5142 {
5143 angle = properties[QStringLiteral( "angle" )].toDouble();
5144 }
5145 if ( properties.contains( QStringLiteral( "width" ) ) )
5146 {
5147 width = properties[QStringLiteral( "width" )].toDouble();
5148 }
5149 std::unique_ptr< QgsRasterFillSymbolLayer > symbolLayer = std::make_unique< QgsRasterFillSymbolLayer >( imagePath );
5150 symbolLayer->setCoordinateMode( mode );
5151 symbolLayer->setOpacity( alpha );
5152 symbolLayer->setOffset( offset );
5153 symbolLayer->setAngle( angle );
5154 symbolLayer->setWidth( width );
5155 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
5156 {
5157 symbolLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
5158 }
5159 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
5160 {
5161 symbolLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
5162 }
5163 if ( properties.contains( QStringLiteral( "width_unit" ) ) )
5164 {
5165 symbolLayer->setSizeUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "width_unit" )].toString() ) );
5166 }
5167 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
5168 {
5169 symbolLayer->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
5170 }
5171
5172 if ( properties.contains( QStringLiteral( "height" ) ) )
5173 {
5174 symbolLayer->setHeight( properties[QStringLiteral( "height" )].toDouble() );
5175 }
5176
5177 symbolLayer->restoreOldDataDefinedProperties( properties );
5178
5179 return symbolLayer.release();
5180}
5181
5183{
5184 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
5185 if ( fillElem.isNull() )
5186 return nullptr;
5187
5188 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
5189 if ( graphicFillElem.isNull() )
5190 return nullptr;
5191
5192 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
5193 if ( graphicElem.isNull() )
5194 return nullptr;
5195
5196 QString path, mimeType;
5197 double size;
5198 QColor fillColor;
5199
5200 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
5201 return nullptr;
5202
5203 // Try to correct the path, this is a wild guess but we have not access to the SLD path here.
5204 if ( ! QFile::exists( path ) )
5205 {
5206 path = QgsProject::instance()->pathResolver().readPath( path ); // skip-keyword-check
5207 }
5208
5209 std::unique_ptr< QgsRasterFillSymbolLayer> sl = std::make_unique< QgsRasterFillSymbolLayer>( path );
5210
5211 return sl.release();
5212}
5213
5214void QgsRasterFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
5215{
5216 QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
5217 if ( it != properties.end() )
5218 {
5219 if ( saving )
5220 it.value() = pathResolver.writePath( it.value().toString() );
5221 else
5222 it.value() = pathResolver.readPath( it.value().toString() );
5223 }
5224}
5225
5227{
5228 Q_UNUSED( symbol )
5229 return true;
5230}
5231
5233{
5234 return QStringLiteral( "RasterFill" );
5235}
5236
5241
5242void QgsRasterFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5243{
5244 QPainter *p = context.renderContext().painter();
5245 if ( !p )
5246 {
5247 return;
5248 }
5249
5250 QPointF offset = mOffset;
5252 {
5255 bool ok = false;
5256 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
5257 if ( ok )
5258 offset = res;
5259 }
5260 if ( !offset.isNull() )
5261 {
5262 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
5263 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
5264 p->translate( offset );
5265 }
5266 if ( mCoordinateMode == Qgis::SymbolCoordinateReference::Feature )
5267 {
5268 QRectF boundingRect = points.boundingRect();
5269 mBrush.setTransform( mBrush.transform().translate( boundingRect.left() - mBrush.transform().dx(),
5270 boundingRect.top() - mBrush.transform().dy() ) );
5271 }
5272
5273 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
5274 if ( !offset.isNull() )
5275 {
5276 p->translate( -offset );
5277 }
5278}
5279
5281{
5282 applyPattern( mBrush, mImageFilePath, mWidth, mHeight, mOpacity * context.opacity(), context );
5283}
5284
5286{
5287 Q_UNUSED( context )
5288}
5289
5291{
5292 QVariantMap map;
5293 map[QStringLiteral( "imageFile" )] = mImageFilePath;
5294 map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
5295 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
5296 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
5297 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
5298 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
5299 map[QStringLiteral( "angle" )] = QString::number( mAngle );
5300
5301 map[QStringLiteral( "width" )] = QString::number( mWidth );
5302 map[QStringLiteral( "height" )] = QString::number( mHeight );
5303 map[QStringLiteral( "width_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
5304 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
5305
5306 return map;
5307}
5308
5310{
5311 std::unique_ptr< QgsRasterFillSymbolLayer > sl = std::make_unique< QgsRasterFillSymbolLayer >( mImageFilePath );
5312 sl->setCoordinateMode( mCoordinateMode );
5313 sl->setOpacity( mOpacity );
5314 sl->setOffset( mOffset );
5315 sl->setOffsetUnit( mOffsetUnit );
5316 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
5317 sl->setAngle( mAngle );
5318 sl->setWidth( mWidth );
5319 sl->setHeight( mHeight );
5320 sl->setSizeUnit( mSizeUnit );
5321 sl->setSizeMapUnitScale( mSizeMapUnitScale );
5322
5323 copyDataDefinedProperties( sl.get() );
5324 copyPaintEffect( sl.get() );
5325 return sl.release();
5326}
5327
5329{
5330 return context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
5331}
5332
5334{
5335 return mSizeUnit == Qgis::RenderUnit::MapUnits || mSizeUnit == Qgis::RenderUnit::MetersInMapUnits
5336 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
5337}
5338
5340{
5341 return QColor();
5342}
5343
5345{
5347 mOffsetUnit = unit;
5348 mSizeUnit = unit;
5349}
5350
5351void QgsRasterFillSymbolLayer::setImageFilePath( const QString &imagePath )
5352{
5353 mImageFilePath = imagePath;
5354}
5355
5357{
5358 mCoordinateMode = mode;
5359}
5360
5361void QgsRasterFillSymbolLayer::setOpacity( const double opacity )
5362{
5363 mOpacity = opacity;
5364}
5365
5367{
5368 if ( !dataDefinedProperties().hasActiveProperties() )
5369 return; // shortcut
5370
5371 const bool hasWidthExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Width );
5372 const bool hasHeightExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Height );
5373 const bool hasFileExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::File );
5374 const bool hasOpacityExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Opacity );
5375 const bool hasAngleExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Angle );
5376
5377 if ( !hasWidthExpression && !hasHeightExpression && !hasAngleExpression && !hasOpacityExpression && !hasFileExpression )
5378 {
5379 return; //no data defined settings
5380 }
5381
5382 bool ok;
5383 if ( hasAngleExpression )
5384 {
5387 if ( ok )
5388 mNextAngle = nextAngle;
5389 }
5390
5391 if ( !hasWidthExpression && !hasHeightExpression && !hasOpacityExpression && !hasFileExpression )
5392 {
5393 return; //nothing further to do
5394 }
5395
5396 double width = mWidth;
5397 if ( hasWidthExpression )
5398 {
5399 context.setOriginalValueVariable( mWidth );
5401 }
5402 double height = mHeight;
5403 if ( hasHeightExpression )
5404 {
5405 context.setOriginalValueVariable( mHeight );
5407 }
5408 double opacity = mOpacity;
5409 if ( hasOpacityExpression )
5410 {
5411 context.setOriginalValueVariable( mOpacity );
5413 }
5414 QString file = mImageFilePath;
5415 if ( hasFileExpression )
5416 {
5417 context.setOriginalValueVariable( mImageFilePath );
5419 }
5420 applyPattern( mBrush, file, width, height, opacity, context );
5421}
5422
5427
5428void QgsRasterFillSymbolLayer::applyPattern( QBrush &brush, const QString &imageFilePath, const double width, const double height, const double alpha, const QgsSymbolRenderContext &context )
5429{
5430 double imageWidth = 0;
5431 double imageHeight = 0;
5432
5433 // defer retrieval of original size till we actually NEED it
5434 QSize originalSize;
5435
5436 if ( width > 0 )
5437 {
5438 if ( mSizeUnit != Qgis::RenderUnit::Percentage )
5439 {
5440 imageWidth = context.renderContext().convertToPainterUnits( width, mSizeUnit, mSizeMapUnitScale );
5441 }
5442 else
5443 {
5444 // RenderPercentage Unit Type takes original image size
5446 if ( originalSize.isEmpty() )
5447 return;
5448
5449 imageWidth = ( width * originalSize.width() ) / 100.0;
5450
5451 // don't render symbols with size below one or above 10,000 pixels
5452 if ( static_cast< int >( imageWidth ) < 1 || 10000.0 < imageWidth )
5453 return;
5454 }
5455 }
5456 if ( height > 0 )
5457 {
5458 if ( mSizeUnit != Qgis::RenderUnit::Percentage )
5459 {
5460 imageHeight = context.renderContext().convertToPainterUnits( height, mSizeUnit, mSizeMapUnitScale );
5461 }
5462 else
5463 {
5464 // RenderPercentage Unit Type takes original image size
5465 if ( !originalSize.isValid() )
5467
5468 if ( originalSize.isEmpty() )
5469 return;
5470
5471 imageHeight = ( height * originalSize.height() ) / 100.0;
5472
5473 // don't render symbols with size below one or above 10,000 pixels
5474 if ( static_cast< int >( imageHeight ) < 1 || 10000.0 < imageHeight )
5475 return;
5476 }
5477 }
5478
5479 if ( width == 0 && imageHeight > 0 )
5480 {
5481 if ( !originalSize.isValid() )
5483
5484 imageWidth = imageHeight * originalSize.width() / originalSize.height();
5485 }
5486 else if ( height == 0 && imageWidth > 0 )
5487 {
5488 if ( !originalSize.isValid() )
5490
5491 imageHeight = imageWidth * originalSize.height() / originalSize.width();
5492 }
5493 if ( imageWidth == 0 || imageHeight == 0 )
5494 {
5495 if ( !originalSize.isValid() )
5497
5498 imageWidth = originalSize.width();
5499 imageHeight = originalSize.height();
5500 }
5501
5502 bool cached;
5503 QImage img = QgsApplication::imageCache()->pathAsImage( imageFilePath, QSize( std::round< int >( imageWidth ), std::round< int >( imageHeight ) ), false, alpha, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
5504 if ( img.isNull() )
5505 return;
5506
5507 brush.setTextureImage( img );
5508}
5509
5510
5511//
5512// QgsRandomMarkerFillSymbolLayer
5513//
5514
5515QgsRandomMarkerFillSymbolLayer::QgsRandomMarkerFillSymbolLayer( int pointCount, Qgis::PointCountMethod method, double densityArea, unsigned long seed )
5516 : mCountMethod( method )
5517 , mPointCount( pointCount )
5518 , mDensityArea( densityArea )
5519 , mSeed( seed )
5520{
5522}
5523
5525
5527{
5528 const Qgis::PointCountMethod countMethod = static_cast< Qgis::PointCountMethod >( properties.value( QStringLiteral( "count_method" ), QStringLiteral( "0" ) ).toInt() );
5529 const int pointCount = properties.value( QStringLiteral( "point_count" ), QStringLiteral( "10" ) ).toInt();
5530 const double densityArea = properties.value( QStringLiteral( "density_area" ), QStringLiteral( "250.0" ) ).toDouble();
5531
5532 unsigned long seed = 0;
5533 if ( properties.contains( QStringLiteral( "seed" ) ) )
5534 seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
5535 else
5536 {
5537 // if we a creating a new random marker fill from scratch, we default to a random seed
5538 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
5539 std::random_device rd;
5540 std::mt19937 mt( seed == 0 ? rd() : seed );
5541 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
5542 seed = uniformDist( mt );
5543 }
5544
5545 std::unique_ptr< QgsRandomMarkerFillSymbolLayer > sl = std::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, countMethod, densityArea, seed );
5546
5547 if ( properties.contains( QStringLiteral( "density_area_unit" ) ) )
5548 sl->setDensityAreaUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "density_area_unit" )].toString() ) );
5549 if ( properties.contains( QStringLiteral( "density_area_unit_scale" ) ) )
5550 sl->setDensityAreaUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "density_area_unit_scale" )].toString() ) );
5551
5552 if ( properties.contains( QStringLiteral( "clip_points" ) ) )
5553 {
5554 sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() );
5555 }
5556
5557 return sl.release();
5558}
5559
5561{
5562 return QStringLiteral( "RandomMarkerFill" );
5563}
5564
5566{
5567 mMarker->setColor( color );
5568 mColor = color;
5569}
5570
5572{
5573 return mMarker ? mMarker->color() : mColor;
5574}
5575
5577{
5578 mMarker->setRenderHints( mMarker->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
5579 mMarker->startRender( context.renderContext(), context.fields() );
5580}
5581
5583{
5584 mMarker->stopRender( context.renderContext() );
5585}
5586
5587void QgsRandomMarkerFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5588{
5589 Part part;
5590 part.exterior = points;
5591 if ( rings )
5592 part.rings = *rings;
5593
5594 if ( mRenderingFeature )
5595 {
5596 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
5597 // until after we've received the final part
5598 mFeatureSymbolOpacity = context.opacity();
5599 mCurrentParts << part;
5600 }
5601 else
5602 {
5603 // not rendering a feature, so we can just render the polygon immediately
5604 const double prevOpacity = mMarker->opacity();
5605 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
5606 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
5607 render( context.renderContext(), QVector< Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
5608 mMarker->setOpacity( prevOpacity );
5609 }
5610}
5611
5612void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsRandomMarkerFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
5613{
5614 bool clipPoints = mClipPoints;
5616 {
5619 }
5620
5621 QVector< QgsGeometry > geometryParts;
5622 geometryParts.reserve( parts.size() );
5623 QPainterPath path;
5624
5625 for ( const Part &part : parts )
5626 {
5627 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
5628 if ( !geom.isNull() && !part.rings.empty() )
5629 {
5630 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
5631 for ( const QPolygonF &ring : part.rings )
5632 {
5633 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
5634 poly->addInteriorRing( fromRing.release() );
5635 }
5636 }
5637 if ( !geom.isGeosValid() )
5638 {
5639 geom = geom.buffer( 0, 0 );
5640 }
5641 geometryParts << geom;
5642
5643 if ( clipPoints )
5644 {
5645 path.addPolygon( part.exterior );
5646 for ( const QPolygonF &ring : part.rings )
5647 {
5648 path.addPolygon( ring );
5649 }
5650 }
5651 }
5652
5653 const QgsGeometry geom = geometryParts.count() != 1 ? QgsGeometry::unaryUnion( geometryParts ) : geometryParts.at( 0 );
5654
5655 if ( clipPoints )
5656 {
5657 context.painter()->save();
5658 context.painter()->setClipPath( path );
5659 }
5660
5661
5662 int count = mPointCount;
5664 {
5667 }
5668
5669 switch ( mCountMethod )
5670 {
5672 {
5673 double densityArea = mDensityArea;
5675 {
5678 }
5679 densityArea = context.convertToPainterUnits( std::sqrt( densityArea ), mDensityAreaUnit, mDensityAreaUnitScale );
5680 densityArea = std::pow( densityArea, 2 );
5681 count = std::max( 0.0, std::ceil( count * ( geom.area() / densityArea ) ) );
5682 break;
5683 }
5685 break;
5686 }
5687
5688 unsigned long seed = mSeed;
5690 {
5691 context.expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
5693 }
5694
5695 QVector< QgsPointXY > randomPoints = geom.randomPointsInPolygon( count, seed );
5696#if 0
5697 // in some cases rendering from top to bottom is nice (e.g. randomised tree markers), but in other cases it's not wanted..
5698 // TODO consider exposing this as an option
5699 std::sort( randomPoints.begin(), randomPoints.end(), []( const QgsPointXY & a, const QgsPointXY & b )->bool
5700 {
5701 return a.y() < b.y();
5702 } );
5703#endif
5705 QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scope );
5706 int pointNum = 0;
5707 const bool needsExpressionContext = mMarker->hasDataDefinedProperties();
5708
5709 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
5711
5712 for ( const QgsPointXY &p : std::as_const( randomPoints ) )
5713 {
5714 if ( needsExpressionContext )
5716 mMarker->renderPoint( QPointF( p.x(), p.y() ), feature.isValid() ? &feature : nullptr, context, -1, selected );
5717 }
5718
5719 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
5720
5721 if ( clipPoints )
5722 {
5723 context.painter()->restore();
5724 }
5725}
5726
5728{
5729 QVariantMap map;
5730 map.insert( QStringLiteral( "count_method" ), QString::number( static_cast< int >( mCountMethod ) ) );
5731 map.insert( QStringLiteral( "point_count" ), QString::number( mPointCount ) );
5732 map.insert( QStringLiteral( "density_area" ), QString::number( mDensityArea ) );
5733 map.insert( QStringLiteral( "density_area_unit" ), QgsUnitTypes::encodeUnit( mDensityAreaUnit ) );
5734 map.insert( QStringLiteral( "density_area_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDensityAreaUnitScale ) );
5735 map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
5736 map.insert( QStringLiteral( "clip_points" ), QString::number( mClipPoints ) );
5737 return map;
5738}
5739
5741{
5742 std::unique_ptr< QgsRandomMarkerFillSymbolLayer > res = std::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mCountMethod, mDensityArea, mSeed );
5743 res->mAngle = mAngle;
5744 res->mColor = mColor;
5745 res->setDensityAreaUnit( mDensityAreaUnit );
5746 res->setDensityAreaUnitScale( mDensityAreaUnitScale );
5747 res->mClipPoints = mClipPoints;
5748 res->setSubSymbol( mMarker->clone() );
5749 copyDataDefinedProperties( res.get() );
5750 copyPaintEffect( res.get() );
5751 return res.release();
5752}
5753
5758
5760{
5761 return mMarker.get();
5762}
5763
5765{
5766 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5767 {
5768 delete symbol;
5769 return false;
5770 }
5771
5772 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5773 mColor = mMarker->color();
5774 return true;
5775}
5776
5778{
5779 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5780
5781 if ( mMarker )
5782 attributes.unite( mMarker->usedAttributes( context ) );
5783
5784 return attributes;
5785}
5786
5788{
5790 return true;
5791 if ( mMarker && mMarker->hasDataDefinedProperties() )
5792 return true;
5793 return false;
5794}
5795
5797{
5798 return mPointCount;
5799}
5800
5802{
5803 mPointCount = pointCount;
5804}
5805
5807{
5808 return mSeed;
5809}
5810
5812{
5813 mSeed = seed;
5814}
5815
5817{
5818 return mClipPoints;
5819}
5820
5822{
5823 mClipPoints = clipPoints;
5824}
5825
5827{
5828 return mCountMethod;
5829}
5830
5832{
5833 mCountMethod = method;
5834}
5835
5837{
5838 return mDensityArea;
5839}
5840
5842{
5843 mDensityArea = area;
5844}
5845
5847{
5848 installMasks( context, true );
5849
5850 mRenderingFeature = true;
5851 mCurrentParts.clear();
5852}
5853
5855{
5856 mRenderingFeature = false;
5857
5858 const double prevOpacity = mMarker->opacity();
5859 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
5860
5861 render( context, mCurrentParts, feature, false );
5862
5863 mFeatureSymbolOpacity = 1;
5864 mMarker->setOpacity( prevOpacity );
5865
5866 removeMasks( context, true );
5867}
5868
5869
5871{
5872 mDensityAreaUnit = unit;
5873 if ( mMarker )
5874 {
5875 mMarker->setOutputUnit( unit );
5876 }
5877}
5878
5880{
5881 if ( mMarker )
5882 {
5883 return mMarker->outputUnit();
5884 }
5885 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5886}
5887
5889{
5890 if ( mMarker )
5891 {
5892 return mMarker->usesMapUnits();
5893 }
5894 return false;
5895}
5896
5898{
5899 if ( mMarker )
5900 {
5901 mMarker->setMapUnitScale( scale );
5902 }
5903}
5904
5906{
5907 if ( mMarker )
5908 {
5909 return mMarker->mapUnitScale();
5910 }
5911 return QgsMapUnitScale();
5912}
MarkerClipMode
Marker clipping modes.
Definition qgis.h:3071
@ 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.
@ IsSymbolLayerSubSymbol
Symbol is being rendered as a sub-symbol of a QgsSymbolLayer.
LineClipMode
Line clipping modes.
Definition qgis.h:3085
@ 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:3000
@ ColorRamp
Gradient color ramp.
@ SimpleTwoColor
Simple two color gradient.
@ CanCalculateMaskGeometryPerFeature
If present, indicates that mask geometry can safely be calculated per feature for the symbol layer....
GradientSpread
Gradient spread options, which control how gradients are rendered outside of their start and end poin...
Definition qgis.h:3044
@ Repeat
Repeat gradient.
@ Reflect
Reflect gradient.
@ Pad
Pad out gradient using colors at endpoint of gradient.
@ Png
Export complex styles to separate PNG files for better compatibility with OGC servers.
PointCountMethod
Methods which define the number of points randomly filling a polygon.
Definition qgis.h:3059
@ Absolute
The point count is used as an absolute count of markers.
@ DensityBased
The point count is part of a marker density count.
QFlags< SymbolLayerFlag > SymbolLayerFlags
Symbol layer flags.
Definition qgis.h:821
RenderUnit
Rendering size units.
Definition qgis.h:4892
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size)
@ Unknown
Mixed or unknown units.
@ MapUnits
Map units.
@ MetersInMapUnits
Meters value as Map units.
@ RenderingSubSymbol
Set whenever a sub-symbol of a parent symbol is currently being rendered. Can be used during symbol a...
@ RenderSymbolPreview
The render is for a symbol preview only and map based properties may not be available,...
@ RenderMapTile
Draw map such that there are no problems between adjacent tiles.
@ HighQualityImageTransforms
Enable high quality image transformations, which results in better appearance of scaled or rotated ra...
@ RenderBlocking
Render and load remote sources in the same thread to ensure rendering remote sources (svg and images)...
GradientType
Gradient types.
Definition qgis.h:3014
@ Linear
Linear gradient.
@ Conical
Conical (polar) gradient.
@ Radial
Radial (circular) gradient.
QFlags< SymbolRenderHint > SymbolRenderHints
Symbol render hints.
Definition qgis.h:741
@ Marker
Marker symbol.
@ Line
Line symbol.
SymbolCoordinateReference
Symbol coordinate reference modes.
Definition qgis.h:3029
@ 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...
A fill symbol layer which renders a marker symbol at the centroid of a polygon geometry.
static QgsSymbolLayer * createFromSld(QDomElement &element)
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.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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.
bool setSubSymbol(QgsSymbol *symbol) FINAL
Sets layer's subsymbol. takes ownership of the passed symbol.
void stopFeatureRender(const QgsFeature &feature, QgsRenderContext &context) override
Called after the layer has been rendered for a particular feature.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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...
~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.
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 QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
static QgsColorRamp * create(const QVariantMap &properties=QVariantMap())
Creates the symbol layer.
static QString typeString()
Returns the string identifier for QgsCptCityColorRamp.
double area() const override
Returns the planar, 2-dimensional area of the geometry.
Exports QGIS layers to the DXF format.
static double mapUnitScaleFactor(double scale, Qgis::RenderUnit symbolUnits, Qgis::DistanceUnit mapUnits, double mapUnitsPerPixel=1.0)
Returns scale factor for conversion to map units.
Qgis::DistanceUnit mapUnits() const
Retrieve map units.
double symbologyScale() const
Returns the reference scale for output.
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:58
bool isValid() const
Returns the validity of this feature.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
static QString uniquePath(const QString &path)
Creates a unique file path name from a desired path by appending _<n> (where <n> is an integer number...
void _renderPolygon(QPainter *p, const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context)
Default method to render polygon.
double angle() const
Returns the rotation angle of the fill symbol, in degrees clockwise.
A geometry is the spatial representation of a feature.
QVector< QgsPointXY > randomPointsInPolygon(int count, const std::function< bool(const QgsPointXY &) > &acceptPoint, unsigned long seed=0, QgsFeedback *feedback=nullptr, int maxTriesPerPoint=0) const
Returns a list of count random points generated inside a (multi)polygon geometry (if acceptPoint is s...
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
bool isGeosValid(Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Checks validity of the geometry using GEOS.
double area() const
Returns the planar, 2-dimensional area of the geometry.
QgsGeometry buffer(double distance, int segments) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry, double precision=0.0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlag::SkipEmptyInteriorRings)
Creates and returns a new geometry engine representing the specified geometry using precision on a gr...
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.
A fill symbol layer which draws a smooth color gradient over a polygon.
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used for the gradient fill.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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...
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.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
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
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.
QgsMapUnitScale mapUnitScale() const override
Qt::PenStyle dxfPenStyle() const override
Gets pen style.
Qgis::RenderUnit mStrokeWidthUnit
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
virtual void applyDataDefinedSettings(QgsSymbolRenderContext &context)
Applies data defined settings prior to generating the fill symbol brush.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void renderPolygon(const QPolygonF &points, const QVector< QPolygonF > *rings, QgsSymbolRenderContext &context) override
Renders the fill symbol layer for the polygon whose outer ring is defined by points,...
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.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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.
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 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.
QImage toTiledPatternImage() const override
Renders the symbol layer as an image that can be used as a seamless pattern fill for polygons,...
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.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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.
static std::unique_ptr< QgsLineString > fromQPolygonF(const QPolygonF &polygon)
Returns a new linestring from a QPolygonF polygon input.
QPolygonF asQPolygonF() const override
Returns a QPolygonF representing the points.
A line symbol type, for rendering LineString and MultiLineString geometries.
Perform transforms between map coordinates and device coordinates.
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
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,...
double distanceX() const
Returns the horizontal distance between rendered markers in the fill.
QImage toTiledPatternImage() const override
Renders the symbol layer as an image that can be used as a seamless pattern fill for polygons,...
double displacementY() const
Returns the vertical displacement for odd numbered columns in the pattern.
void setColor(const QColor &c) override
Sets the "representative" color for the symbol layer.
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.
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.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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.
double displacementX() const
Returns the horizontal displacement for odd numbered rows in the pattern.
void setOutputUnit(Qgis::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.
~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.
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:60
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double x
Definition qgspoint.h:52
double y
Definition qgspoint.h:53
Polygon geometry type.
Definition qgspolygon.h:33
void addInteriorRing(QgsCurve *ring) override
Adds an interior ring to the geometry (takes ownership)
static QgsProject * instance()
Returns the QgsProject singleton instance.
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const final
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const final
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,...
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.
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
void setMapUnitScale(const QgsMapUnitScale &scale) override
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.
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.
bool setSubSymbol(QgsSymbol *symbol) FINAL
Sets layer's subsymbol. takes ownership of the passed symbol.
void setDensityArea(double area)
Sets the density area used to count the number of points to randomly fill the polygon.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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 ...
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
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 * createFromSld(QDomElement &element)
Creates a new QgsRasterFillSymbolLayer from a SLD element.
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.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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 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.
double height() const
Returns the height used for scaling the image used in 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.
QgsPointXY center
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.
QSet< QString > disabledSymbolLayersV2() const
When rendering a map layer in a second pass (for selective masking), some symbol layers may be disabl...
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
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 ...
QgsExpressionContext & expressionContext()
Gets the expression context.
void setDisabledSymbolLayersV2(const QSet< QString > &symbolLayers)
When rendering a map layer in a second pass (for selective masking), some symbol layers may be disabl...
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...
static QgsRenderContext fromQPainter(QPainter *painter)
Creates a default render context given a pixel based QPainter destination.
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.
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.
Qgis::RenderUnit patternWidthUnit() const
Returns the units for the width of the SVG images in the pattern.
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.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
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.
Qgis::RenderUnit svgStrokeWidthUnit() const
Returns the units for the stroke 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
void setMapUnitScale(const QgsMapUnitScale &scale) override
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
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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 setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
void stopRender(QgsSymbolRenderContext &context) override
Called after a set of rendering operations has finished on the supplied render context.
QColor 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...
Qgis::RenderUnit outputUnit() const override
Returns the units to use for sizes and widths within the symbol layer.
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.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
QString layerType() const override
Returns a string that represents this layer type.
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 ...
void setColorRamp(QgsColorRamp *ramp)
Sets the color ramp used to draw the shapeburst fill.
Renders polygons using a single fill and stroke color.
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.
Qgis::SymbolLayerFlags flags() const override
Returns flags which control the symbol layer's behavior.
~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 ...
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.
void setOutputUnit(Qgis::RenderUnit unit) override
Sets the units to use for sizes and widths within the symbol layer.
QgsMapUnitScale mOffsetMapUnitScale
Qgis::RenderUnit mStrokeWidthUnit
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
QImage toTiledPatternImage() const override
Renders the symbol layer as an image that can be used as a seamless pattern fill for polygons,...
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
Qgis::RenderUnit outputUnit() const override
Returns 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)
The QgsSldExportContext class holds SLD export options and other information related to SLD export of...
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.
Contains utility functions for working with symbols and symbol layers.
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 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 void externalGraphicToSld(QDomDocument &doc, QDomElement &element, const QString &path, const QString &mime, const QColor &color, double size=-1)
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 void multiplyImageOpacity(QImage *image, qreal opacity)
Multiplies opacity of image pixel values with a (global) transparency value.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static double rescaleUom(double size, Qgis::RenderUnit unit, const QVariantMap &props)
Rescales the given size based on the uomScale found in the props, if any is found,...
static QString ogrFeatureStylePen(double width, double mmScaleFactor, double mapUnitsScaleFactor, const QColor &c, Qt::PenJoinStyle joinStyle=Qt::MiterJoin, Qt::PenCapStyle capStyle=Qt::FlatCap, double offset=0.0, const QVector< qreal > *dashPattern=nullptr)
Create ogr feature style string for pen.
static bool externalGraphicFromSld(QDomElement &element, QString &path, QString &mime, QColor &color, double &size)
static void parametricSvgToSld(QDomDocument &doc, QDomElement &graphicElem, const QString &path, const QColor &fillColor, double size, const QColor &strokeColor, double strokeWidth)
Encodes a reference to a parametric SVG into SLD, as a succession of parametric SVG using URL paramet...
static bool lineFromSld(QDomElement &element, Qt::PenStyle &penStyle, QColor &color, double &width, Qt::PenJoinStyle *penJoinStyle=nullptr, Qt::PenCapStyle *penCapStyle=nullptr, QVector< qreal > *customDashPattern=nullptr, double *dashOffset=nullptr)
static QString ogrFeatureStyleBrush(const QColor &fillColr)
Create ogr feature style string for brush.
static 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 QSize tileSize(int width, int height, double &angleRad)
Calculate the minimum size in pixels of a symbol tile given the symbol width and height and the symbo...
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 Qgis::RenderUnit decodeSldUom(const QString &str, double *scaleFactor=nullptr)
Decodes a SLD unit of measure string to a render unit.
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 QgsStringMap getVendorOptionList(QDomElement &element)
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.
virtual bool setSubSymbol(QgsSymbol *symbol)
Sets layer's subsymbol. takes ownership of the passed symbol.
bool shouldRenderUsingSelectionColor(const QgsSymbolRenderContext &context) const
Returns true if the symbol layer should be rendered using the selection color from the render context...
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.
bool installMasks(QgsRenderContext &context, bool recursive, const QRectF &rect=QRectF())
When rendering, install masks on context painter.
void removeMasks(QgsRenderContext &context, bool recursive)
When rendering, remove previously installed masks from context painter if recursive is true masks are...
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.
@ GradientType
Gradient fill type.
@ SecondaryColor
Secondary color (eg for gradient fills)
@ File
Filename, eg for svg files.
@ GradientReference2Y
Gradient reference point 2 y.
@ GradientReference1X
Gradient reference point 1 x.
@ OffsetY
Vertical offset.
@ OffsetX
Horizontal offset.
@ GradientReference1Y
Gradient reference point 1 y.
@ PointCount
Point count.
@ GradientSpread
Gradient spread mode.
@ ShapeburstMaxDistance
Shapeburst fill from edge distance.
@ StrokeStyle
Stroke style (eg solid, dashed)
@ DistanceY
Vertical distance between points.
@ DensityArea
Density area.
@ ClipPoints
Whether markers should be clipped to polygon boundaries.
@ LineClipping
Line clipping mode.
@ ShapeburstIgnoreRings
Shapeburst ignore rings.
@ ShapeburstUseWholeShape
Shapeburst use whole shape.
@ DisplacementX
Horizontal displacement.
@ CoordinateMode
Gradient coordinate mode.
@ FillStyle
Fill style (eg solid, dots)
@ GradientReference2X
Gradient reference point 2 x.
@ StrokeColor
Stroke color.
@ BlurRadius
Shapeburst blur radius.
@ MarkerClipping
Marker clipping mode.
@ RandomSeed
Random number seed.
@ LineAngle
Line angle, or angle of hash lines for hash line symbols.
@ JoinStyle
Line join style.
@ RandomOffsetY
Random offset Y.
@ DisplacementY
Vertical displacement.
@ DistanceX
Horizontal distance between points.
@ GradientReference1IsCentroid
Gradient reference point 1 is centroid.
@ StrokeWidth
Stroke width.
@ GradientReference2IsCentroid
Gradient reference point 2 is centroid.
@ LineDistance
Distance between lines, or length of lines for hash line symbols.
@ Offset
Symbol offset.
@ RandomOffsetX
Random offset X.
@ Height
Symbol height.
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.
virtual Qgis::SymbolLayerFlags flags() const
Returns flags which control the symbol layer's behavior.
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.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for data defined symbology.
bool forceVectorRendering() const
Returns true if symbol must be rendered using vector methods, and optimisations like pre-rendered ima...
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:231
QColor color() const
Returns the symbol's color.
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:294
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.
Qgis::RenderUnit intervalUnit() const
Returns the units for the interval between symbols.
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
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
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:5941
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6024
QMap< QString, QString > QgsStringMap
Definition qgis.h:6562
#define DEFAULT_SIMPLEFILL_JOINSTYLE
#define INF
#define DEFAULT_SIMPLEFILL_COLOR
#define DEFAULT_SIMPLEFILL_STYLE
#define DEFAULT_SIMPLEFILL_BORDERSTYLE
#define DEFAULT_SIMPLEFILL_BORDERCOLOR
#define DEFAULT_SIMPLEFILL_BORDERWIDTH
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
Single variable definition for use within a QgsExpressionContextScope.