QGIS API Documentation 3.43.0-Master (ea98b95b755)
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 auto 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 auto 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 auto 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 auto 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
705{
706 mGradientRamp.reset( ramp );
707}
708
710{
711 return QStringLiteral( "GradientFill" );
712}
713
714void QgsGradientFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, const QPolygonF &points )
715{
717 {
718 //shortcut
721 return;
722 }
723
724 bool ok;
725
726 //first gradient color
727 QColor color = mColor;
729 {
732 color.setAlphaF( context.opacity() * color.alphaF() );
733 }
734
735 //second gradient color
736 QColor color2 = mColor2;
738 {
741 color2.setAlphaF( context.opacity() * color2.alphaF() );
742 }
743
744 //gradient rotation angle
745 double angle = mAngle;
747 {
750 }
751
752 //gradient type
755 {
757 if ( ok )
758 {
759 if ( currentType == QObject::tr( "linear" ) )
760 {
762 }
763 else if ( currentType == QObject::tr( "radial" ) )
764 {
766 }
767 else if ( currentType == QObject::tr( "conical" ) )
768 {
770 }
771 }
772 }
773
774 //coordinate mode
777 {
779 if ( ok )
780 {
781 if ( currentCoordMode == QObject::tr( "feature" ) )
782 {
784 }
785 else if ( currentCoordMode == QObject::tr( "viewport" ) )
786 {
788 }
789 }
790 }
791
792 //gradient spread
795 {
797 if ( ok )
798 {
799 if ( currentSpread == QObject::tr( "pad" ) )
800 {
802 }
803 else if ( currentSpread == QObject::tr( "repeat" ) )
804 {
806 }
807 else if ( currentSpread == QObject::tr( "reflect" ) )
808 {
810 }
811 }
812 }
813
814 //reference point 1 x & y
815 double refPoint1X = mReferencePoint1.x();
817 {
818 context.setOriginalValueVariable( refPoint1X );
820 }
821 double refPoint1Y = mReferencePoint1.y();
823 {
824 context.setOriginalValueVariable( refPoint1Y );
826 }
827 bool refPoint1IsCentroid = mReferencePoint1IsCentroid;
829 {
830 context.setOriginalValueVariable( refPoint1IsCentroid );
832 }
833
834 //reference point 2 x & y
835 double refPoint2X = mReferencePoint2.x();
837 {
838 context.setOriginalValueVariable( refPoint2X );
840 }
841 double refPoint2Y = mReferencePoint2.y();
843 {
844 context.setOriginalValueVariable( refPoint2Y );
846 }
847 bool refPoint2IsCentroid = mReferencePoint2IsCentroid;
849 {
850 context.setOriginalValueVariable( refPoint2IsCentroid );
852 }
853
854 if ( refPoint1IsCentroid || refPoint2IsCentroid )
855 {
856 //either the gradient is starting or ending at a centroid, so calculate it
857 QPointF centroid = QgsSymbolLayerUtils::polygonCentroid( points );
858 //centroid coordinates need to be scaled to a range [0, 1] relative to polygon bounds
859 QRectF bbox = points.boundingRect();
860 double centroidX = ( centroid.x() - bbox.left() ) / bbox.width();
861 double centroidY = ( centroid.y() - bbox.top() ) / bbox.height();
862
863 if ( refPoint1IsCentroid )
864 {
865 refPoint1X = centroidX;
866 refPoint1Y = centroidY;
867 }
868 if ( refPoint2IsCentroid )
869 {
870 refPoint2X = centroidX;
871 refPoint2Y = centroidY;
872 }
873 }
874
875 //update gradient with data defined values
877 spread, QPointF( refPoint1X, refPoint1Y ), QPointF( refPoint2X, refPoint2Y ), angle );
878}
879
880QPointF QgsGradientFillSymbolLayer::rotateReferencePoint( QPointF refPoint, double angle )
881{
882 //rotate a reference point by a specified angle around the point (0.5, 0.5)
883
884 //create a line from the centrepoint of a rectangle bounded by (0, 0) and (1, 1) to the reference point
885 QLineF refLine = QLineF( QPointF( 0.5, 0.5 ), refPoint );
886 //rotate this line by the current rotation angle
887 refLine.setAngle( refLine.angle() + angle );
888 //get new end point of line
889 QPointF rotatedReferencePoint = refLine.p2();
890 //make sure coords of new end point is within [0, 1]
891 if ( rotatedReferencePoint.x() > 1 )
892 rotatedReferencePoint.setX( 1 );
893 if ( rotatedReferencePoint.x() < 0 )
894 rotatedReferencePoint.setX( 0 );
895 if ( rotatedReferencePoint.y() > 1 )
896 rotatedReferencePoint.setY( 1 );
897 if ( rotatedReferencePoint.y() < 0 )
898 rotatedReferencePoint.setY( 0 );
899
900 return rotatedReferencePoint;
901}
902
903void QgsGradientFillSymbolLayer::applyGradient( const QgsSymbolRenderContext &context, QBrush &brush,
904 const QColor &color, const QColor &color2, Qgis::GradientColorSource gradientColorType,
905 QgsColorRamp *gradientRamp, Qgis::GradientType gradientType,
906 Qgis::SymbolCoordinateReference coordinateMode, Qgis::GradientSpread gradientSpread,
907 QPointF referencePoint1, QPointF referencePoint2, const double angle )
908{
909 //update alpha of gradient colors
910 QColor fillColor = color;
911 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
912 QColor fillColor2 = color2;
913 fillColor2.setAlphaF( context.opacity() * fillColor2.alphaF() );
914
915 //rotate reference points
916 QPointF rotatedReferencePoint1 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint1, angle ) : referencePoint1;
917 QPointF rotatedReferencePoint2 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint2, angle ) : referencePoint2;
918
919 //create a QGradient with the desired properties
920 QGradient gradient;
921 switch ( gradientType )
922 {
924 gradient = QLinearGradient( rotatedReferencePoint1, rotatedReferencePoint2 );
925 break;
927 gradient = QRadialGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).length() );
928 break;
930 gradient = QConicalGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).angle() );
931 break;
932 }
933 switch ( coordinateMode )
934 {
936 gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
937 break;
939 gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
940 break;
941 }
942 switch ( gradientSpread )
943 {
945 gradient.setSpread( QGradient::PadSpread );
946 break;
948 gradient.setSpread( QGradient::ReflectSpread );
949 break;
951 gradient.setSpread( QGradient::RepeatSpread );
952 break;
953 }
954
955 //add stops to gradient
957 ( gradientRamp->type() == QgsGradientColorRamp::typeString() || gradientRamp->type() == QgsCptCityColorRamp::typeString() ) )
958 {
959 //color ramp gradient
960 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( gradientRamp );
961 gradRamp->addStopsToGradient( &gradient, context.opacity() );
962 }
963 else
964 {
965 //two color gradient
966 gradient.setColorAt( 0.0, fillColor );
967 gradient.setColorAt( 1.0, fillColor2 );
968 }
969
970 //update QBrush use gradient
971 brush = QBrush( gradient );
972}
973
975{
976 QColor selColor = context.renderContext().selectionColor();
977 if ( ! SELECTION_IS_OPAQUE )
978 selColor.setAlphaF( context.opacity() );
979 mSelBrush = QBrush( selColor );
980}
981
983{
984 Q_UNUSED( context )
985}
986
987void QgsGradientFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
988{
989 QPainter *p = context.renderContext().painter();
990 if ( !p )
991 {
992 return;
993 }
994
995 applyDataDefinedSymbology( context, points );
996
997 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
998 p->setBrush( useSelectedColor ? mSelBrush : mBrush );
999 p->setPen( Qt::NoPen );
1000
1001 QPointF offset = mOffset;
1003 {
1006 bool ok = false;
1007 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1008 if ( ok )
1009 offset = res;
1010 }
1011
1012 if ( !offset.isNull() )
1013 {
1016 p->translate( offset );
1017 }
1018
1019 _renderPolygon( p, points, rings, context );
1020
1021 if ( !offset.isNull() )
1022 {
1023 p->translate( -offset );
1024 }
1025}
1026
1028{
1029 QVariantMap map;
1030 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
1031 map[QStringLiteral( "gradient_color2" )] = QgsColorUtils::colorToString( mColor2 );
1032 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
1033 map[QStringLiteral( "type" )] = QString::number( static_cast<int>( mGradientType ) );
1034 map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
1035 map[QStringLiteral( "spread" )] = QString::number( static_cast< int >( mGradientSpread ) );
1036 map[QStringLiteral( "reference_point1" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint1 );
1037 map[QStringLiteral( "reference_point1_iscentroid" )] = QString::number( mReferencePoint1IsCentroid );
1038 map[QStringLiteral( "reference_point2" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint2 );
1039 map[QStringLiteral( "reference_point2_iscentroid" )] = QString::number( mReferencePoint2IsCentroid );
1040 map[QStringLiteral( "angle" )] = QString::number( mAngle );
1041 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1042 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1043 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1044 if ( mGradientRamp )
1045 {
1046 map.insert( mGradientRamp->properties() );
1047 }
1048 return map;
1049}
1050
1052{
1053 auto sl = std::make_unique< QgsGradientFillSymbolLayer >( mColor, mColor2, mGradientColorType, mGradientType, mCoordinateMode, mGradientSpread );
1054 if ( mGradientRamp )
1055 sl->setColorRamp( mGradientRamp->clone() );
1056 sl->setReferencePoint1( mReferencePoint1 );
1057 sl->setReferencePoint1IsCentroid( mReferencePoint1IsCentroid );
1058 sl->setReferencePoint2( mReferencePoint2 );
1059 sl->setReferencePoint2IsCentroid( mReferencePoint2IsCentroid );
1060 sl->setAngle( mAngle );
1061 sl->setOffset( mOffset );
1062 sl->setOffsetUnit( mOffsetUnit );
1063 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1064 copyDataDefinedProperties( sl.get() );
1065 copyPaintEffect( sl.get() );
1066 return sl.release();
1067}
1068
1070{
1071 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1072 return offsetBleed;
1073}
1074
1079
1084
1089
1094
1099
1104
1105//QgsShapeburstFillSymbolLayer
1106
1108 int blurRadius, bool useWholeShape, double maxDistance )
1109 : mBlurRadius( blurRadius )
1110 , mUseWholeShape( useWholeShape )
1111 , mMaxDistance( maxDistance )
1112 , mColorType( colorType )
1113 , mColor2( color2 )
1114{
1115 mColor = color;
1116}
1117
1119
1121{
1122 //default to a two-color gradient
1124 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
1125 int blurRadius = 0;
1126 bool useWholeShape = true;
1127 double maxDistance = 5;
1128 QPointF offset;
1129
1130 //update fill properties from props
1131 if ( props.contains( QStringLiteral( "color_type" ) ) )
1132 {
1133 colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
1134 }
1135 if ( props.contains( QStringLiteral( "shapeburst_color" ) ) )
1136 {
1137 //pre 2.5 projects used "shapeburst_color"
1138 color = QgsColorUtils::colorFromString( props[QStringLiteral( "shapeburst_color" )].toString() );
1139 }
1140 else if ( props.contains( QStringLiteral( "color" ) ) )
1141 {
1142 color = QgsColorUtils::colorFromString( props[QStringLiteral( "color" )].toString() );
1143 }
1144
1145 if ( props.contains( QStringLiteral( "shapeburst_color2" ) ) )
1146 {
1147 //pre 2.5 projects used "shapeburst_color2"
1148 color2 = QgsColorUtils::colorFromString( props[QStringLiteral( "shapeburst_color2" )].toString() );
1149 }
1150 else if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
1151 {
1152 color2 = QgsColorUtils::colorFromString( props[QStringLiteral( "gradient_color2" )].toString() );
1153 }
1154 if ( props.contains( QStringLiteral( "blur_radius" ) ) )
1155 {
1156 blurRadius = props[QStringLiteral( "blur_radius" )].toInt();
1157 }
1158 if ( props.contains( QStringLiteral( "use_whole_shape" ) ) )
1159 {
1160 useWholeShape = props[QStringLiteral( "use_whole_shape" )].toInt();
1161 }
1162 if ( props.contains( QStringLiteral( "max_distance" ) ) )
1163 {
1164 maxDistance = props[QStringLiteral( "max_distance" )].toDouble();
1165 }
1166 if ( props.contains( QStringLiteral( "offset" ) ) )
1167 {
1168 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
1169 }
1170
1171 //attempt to create color ramp from props
1172 QgsColorRamp *gradientRamp = nullptr;
1173 if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
1174 {
1175 gradientRamp = QgsCptCityColorRamp::create( props );
1176 }
1177 else
1178 {
1179 gradientRamp = QgsGradientColorRamp::create( props );
1180 }
1181
1182 //create a new shapeburst fill layer with desired properties
1183 auto sl = std::make_unique< QgsShapeburstFillSymbolLayer >( color, color2, colorType, blurRadius, useWholeShape, maxDistance );
1184 sl->setOffset( offset );
1185 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1186 {
1187 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1188 }
1189 if ( props.contains( QStringLiteral( "distance_unit" ) ) )
1190 {
1191 sl->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "distance_unit" )].toString() ) );
1192 }
1193 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1194 {
1195 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1196 }
1197 if ( props.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
1198 {
1199 sl->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
1200 }
1201 if ( props.contains( QStringLiteral( "ignore_rings" ) ) )
1202 {
1203 sl->setIgnoreRings( props[QStringLiteral( "ignore_rings" )].toInt() );
1204 }
1205 if ( gradientRamp )
1206 {
1207 sl->setColorRamp( gradientRamp );
1208 }
1209
1210 sl->restoreOldDataDefinedProperties( props );
1211
1212 return sl.release();
1213}
1214
1216{
1217 return QStringLiteral( "ShapeburstFill" );
1218}
1219
1224
1226{
1227 if ( mGradientRamp.get() == ramp )
1228 return;
1229
1230 mGradientRamp.reset( ramp );
1231}
1232
1233void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
1234 double &maxDistance, bool &ignoreRings )
1235{
1236 //first gradient color
1237 color = mColor;
1239 {
1242 }
1243
1244 //second gradient color
1245 color2 = mColor2;
1247 {
1250 }
1251
1252 //blur radius
1253 blurRadius = mBlurRadius;
1255 {
1256 context.setOriginalValueVariable( mBlurRadius );
1258 }
1259
1260 //use whole shape
1261 useWholeShape = mUseWholeShape;
1263 {
1264 context.setOriginalValueVariable( mUseWholeShape );
1266 }
1267
1268 //max distance
1269 maxDistance = mMaxDistance;
1271 {
1272 context.setOriginalValueVariable( mMaxDistance );
1274 }
1275
1276 //ignore rings
1277 ignoreRings = mIgnoreRings;
1279 {
1280 context.setOriginalValueVariable( mIgnoreRings );
1282 }
1283
1284}
1285
1287{
1288 //TODO - check this
1289 QColor selColor = context.renderContext().selectionColor();
1290 if ( ! SELECTION_IS_OPAQUE )
1291 selColor.setAlphaF( context.opacity() );
1292 mSelBrush = QBrush( selColor );
1293}
1294
1296{
1297 Q_UNUSED( context )
1298}
1299
1300void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1301{
1302 QPainter *p = context.renderContext().painter();
1303 if ( !p )
1304 {
1305 return;
1306 }
1307
1308 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1309 if ( useSelectedColor )
1310 {
1311 //feature is selected, draw using selection style
1312 p->setBrush( mSelBrush );
1313 QPointF offset = mOffset;
1314
1316 {
1319 bool ok = false;
1320 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1321 if ( ok )
1322 offset = res;
1323 }
1324
1325 if ( !offset.isNull() )
1326 {
1327 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1328 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1329 p->translate( offset );
1330 }
1331 _renderPolygon( p, points, rings, context );
1332 if ( !offset.isNull() )
1333 {
1334 p->translate( -offset );
1335 }
1336 return;
1337 }
1338
1339 QColor color1, color2;
1340 int blurRadius;
1341 bool useWholeShape;
1342 double maxDistance;
1343 bool ignoreRings;
1344 //calculate data defined symbology
1345 applyDataDefinedSymbology( context, color1, color2, blurRadius, useWholeShape, maxDistance, ignoreRings );
1346
1347 //calculate max distance for shapeburst fill to extend from polygon boundary, in pixels
1348 int outputPixelMaxDist = 0;
1349 if ( !useWholeShape && !qgsDoubleNear( maxDistance, 0.0 ) )
1350 {
1351 //convert max distance to pixels
1352 outputPixelMaxDist = static_cast< int >( std::round( context.renderContext().convertToPainterUnits( maxDistance, mDistanceUnit, mDistanceMapUnitScale ) ) );
1353 }
1354
1355 //if we are using the two color mode, create a gradient ramp
1356 std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
1358 {
1359 twoColorGradientRamp = std::make_unique< QgsGradientColorRamp >( color1, color2 );
1360 }
1361
1362 //no stroke for shapeburst fills
1363 p->setPen( QPen( Qt::NoPen ) );
1364
1365 //calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
1366 int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
1367 //create a QImage to draw shapeburst in
1368 int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1369 int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1370 int imWidth = pointsWidth + ( sideBuffer * 2 );
1371 int imHeight = pointsHeight + ( sideBuffer * 2 );
1372
1373 // these are all potentially very expensive operations, so check regularly if the job is canceled and abort responsively
1374 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1375 return;
1376
1377 auto fillImage = std::make_unique< QImage >( imWidth,
1378 imHeight, QImage::Format_ARGB32_Premultiplied );
1379 if ( fillImage->isNull() )
1380 {
1381 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1382 return;
1383 }
1384
1385 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1386 return;
1387
1388 //also create an image to store the alpha channel
1389 auto alphaImage = std::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1390 if ( alphaImage->isNull() )
1391 {
1392 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1393 return;
1394 }
1395
1396 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1397 return;
1398
1399 //Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
1400 //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
1401 //polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
1402 fillImage->fill( Qt::black );
1403
1404 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1405 return;
1406
1407 //initially fill the alpha channel image with a transparent color
1408 alphaImage->fill( Qt::transparent );
1409
1410 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1411 return;
1412
1413 //now, draw the polygon in the alpha channel image
1414 QPainter imgPainter;
1415 imgPainter.begin( alphaImage.get() );
1416 imgPainter.setRenderHint( QPainter::Antialiasing, true );
1417 imgPainter.setBrush( QBrush( Qt::white ) );
1418 imgPainter.setPen( QPen( Qt::black ) );
1419 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1420 _renderPolygon( &imgPainter, points, rings, context );
1421 imgPainter.end();
1422
1423 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1424 return;
1425
1426 //now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
1427 //(this avoids calling _renderPolygon twice, since that can be slow)
1428 imgPainter.begin( fillImage.get() );
1429 if ( !ignoreRings )
1430 {
1431 imgPainter.drawImage( 0, 0, *alphaImage );
1432 }
1433 else
1434 {
1435 //using ignore rings mode, so the alpha image can't be used
1436 //directly as the alpha channel contains polygon rings and we need
1437 //to draw now without any rings
1438 imgPainter.setBrush( QBrush( Qt::white ) );
1439 imgPainter.setPen( QPen( Qt::black ) );
1440 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1441 _renderPolygon( &imgPainter, points, nullptr, context );
1442 }
1443 imgPainter.end();
1444
1445 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1446 return;
1447
1448 //apply distance transform to image, uses the current color ramp to calculate final pixel colors
1449 double *dtArray = distanceTransform( fillImage.get(), context.renderContext() );
1450
1451 //copy distance transform values back to QImage, shading by appropriate color ramp
1452 dtArrayToQImage( dtArray, fillImage.get(), mColorType == Qgis::GradientColorSource::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
1453 context.renderContext(), useWholeShape, outputPixelMaxDist );
1454 if ( context.opacity() < 1 )
1455 {
1456 QgsImageOperation::multiplyOpacity( *fillImage, context.opacity(), context.renderContext().feedback() );
1457 }
1458
1459 //clean up some variables
1460 delete [] dtArray;
1461
1462 //apply blur if desired
1463 if ( blurRadius > 0 )
1464 {
1465 QgsImageOperation::stackBlur( *fillImage, blurRadius, false, context.renderContext().feedback() );
1466 }
1467
1468 //apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1469 imgPainter.begin( fillImage.get() );
1470 imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
1471 imgPainter.drawImage( 0, 0, *alphaImage );
1472 imgPainter.end();
1473 //we're finished with the alpha channel image now
1474 alphaImage.reset();
1475
1476 //draw shapeburst image in correct place in the destination painter
1477
1478 QgsScopedQPainterState painterState( p );
1479 QPointF offset = mOffset;
1481 {
1484 bool ok = false;
1485 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1486 if ( ok )
1487 offset = res;
1488 }
1489 if ( !offset.isNull() )
1490 {
1491 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1492 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1493 p->translate( offset );
1494 }
1495
1496 p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
1497
1498 if ( !offset.isNull() )
1499 {
1500 p->translate( -offset );
1501 }
1502}
1503
1504//fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
1505
1506/* distance transform of a 1d function using squared distance */
1507void QgsShapeburstFillSymbolLayer::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
1508{
1509 int k = 0;
1510 v[0] = 0;
1511 z[0] = -INF;
1512 z[1] = + INF;
1513 for ( int q = 1; q <= n - 1; q++ )
1514 {
1515 double s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1516 while ( s <= z[k] )
1517 {
1518 k--;
1519 s = ( ( f[q] + static_cast< double >( q ) * q ) - ( f[v[k]] + ( static_cast< double >( v[k] ) * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1520 }
1521 k++;
1522 v[k] = q;
1523 z[k] = s;
1524 z[k + 1] = + INF;
1525 }
1526
1527 k = 0;
1528 for ( int q = 0; q <= n - 1; q++ )
1529 {
1530 while ( z[k + 1] < q )
1531 k++;
1532 d[q] = static_cast< double >( q - v[k] ) * ( q - v[k] ) + f[v[k]];
1533 }
1534}
1535
1536/* distance transform of 2d function using squared distance */
1537void QgsShapeburstFillSymbolLayer::distanceTransform2d( double *im, int width, int height, QgsRenderContext &context )
1538{
1539 int maxDimension = std::max( width, height );
1540 double *f = new double[ maxDimension ];
1541 int *v = new int[ maxDimension ];
1542 double *z = new double[ maxDimension + 1 ];
1543 double *d = new double[ maxDimension ];
1544
1545 // transform along columns
1546 for ( int x = 0; x < width; x++ )
1547 {
1548 if ( context.renderingStopped() )
1549 break;
1550
1551 for ( int y = 0; y < height; y++ )
1552 {
1553 f[y] = im[ x + static_cast< std::size_t>( y ) * width ];
1554 }
1555 distanceTransform1d( f, height, v, z, d );
1556 for ( int y = 0; y < height; y++ )
1557 {
1558 im[ x + static_cast< std::size_t>( y ) * width ] = d[y];
1559 }
1560 }
1561
1562 // transform along rows
1563 for ( int y = 0; y < height; y++ )
1564 {
1565 if ( context.renderingStopped() )
1566 break;
1567
1568 for ( int x = 0; x < width; x++ )
1569 {
1570 f[x] = im[ x + static_cast< std::size_t>( y ) * width ];
1571 }
1572 distanceTransform1d( f, width, v, z, d );
1573 for ( int x = 0; x < width; x++ )
1574 {
1575 im[ x + static_cast< std::size_t>( y ) * width ] = d[x];
1576 }
1577 }
1578
1579 delete [] d;
1580 delete [] f;
1581 delete [] v;
1582 delete [] z;
1583}
1584
1585/* distance transform of a binary QImage */
1586double *QgsShapeburstFillSymbolLayer::distanceTransform( QImage *im, QgsRenderContext &context )
1587{
1588 int width = im->width();
1589 int height = im->height();
1590
1591 double *dtArray = new double[static_cast< std::size_t>( width ) * height];
1592
1593 //load qImage to array
1594 QRgb tmpRgb;
1595 std::size_t idx = 0;
1596 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1597 {
1598 if ( context.renderingStopped() )
1599 break;
1600
1601 const QRgb *scanLine = reinterpret_cast< const QRgb * >( im->constScanLine( heightIndex ) );
1602 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1603 {
1604 tmpRgb = scanLine[widthIndex];
1605 if ( qRed( tmpRgb ) == 0 )
1606 {
1607 //black pixel, so zero distance
1608 dtArray[ idx ] = 0;
1609 }
1610 else
1611 {
1612 //white pixel, so initially set distance as infinite
1613 dtArray[ idx ] = INF;
1614 }
1615 idx++;
1616 }
1617 }
1618
1619 //calculate squared distance transform
1620 distanceTransform2d( dtArray, width, height, context );
1621
1622 return dtArray;
1623}
1624
1625void QgsShapeburstFillSymbolLayer::dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, QgsRenderContext &context, bool useWholeShape, int maxPixelDistance )
1626{
1627 int width = im->width();
1628 int height = im->height();
1629
1630 //find maximum distance value
1631 double maxDistanceValue;
1632
1633 if ( useWholeShape )
1634 {
1635 //no max distance specified in symbol properties, so calculate from maximum value in distance transform results
1636 double dtMaxValue = array[0];
1637 for ( std::size_t i = 1; i < static_cast< std::size_t >( width ) * height; ++i )
1638 {
1639 if ( array[i] > dtMaxValue )
1640 {
1641 dtMaxValue = array[i];
1642 }
1643 }
1644
1645 //values in distance transform are squared
1646 maxDistanceValue = std::sqrt( dtMaxValue );
1647 }
1648 else
1649 {
1650 //use max distance set in symbol properties
1651 maxDistanceValue = maxPixelDistance;
1652 }
1653
1654 //update the pixels in the provided QImage
1655 std::size_t idx = 0;
1656 double squaredVal = 0;
1657 double pixVal = 0;
1658
1659 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1660 {
1661 if ( context.renderingStopped() )
1662 break;
1663
1664 QRgb *scanLine = reinterpret_cast< QRgb * >( im->scanLine( heightIndex ) );
1665 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1666 {
1667 //result of distance transform
1668 squaredVal = array[idx];
1669
1670 //scale result to fit in the range [0, 1]
1671 if ( maxDistanceValue > 0 )
1672 {
1673 pixVal = squaredVal > 0 ? std::min( ( std::sqrt( squaredVal ) / maxDistanceValue ), 1.0 ) : 0;
1674 }
1675 else
1676 {
1677 pixVal = 1.0;
1678 }
1679
1680 //convert value to color from ramp
1681 //premultiply ramp color since we are storing this in a ARGB32_Premultiplied QImage
1682 scanLine[widthIndex] = qPremultiply( ramp->color( pixVal ).rgba() );
1683 idx++;
1684 }
1685 }
1686}
1687
1689{
1690 QVariantMap map;
1691 map[QStringLiteral( "color" )] = QgsColorUtils::colorToString( mColor );
1692 map[QStringLiteral( "gradient_color2" )] = QgsColorUtils::colorToString( mColor2 );
1693 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mColorType ) );
1694 map[QStringLiteral( "blur_radius" )] = QString::number( mBlurRadius );
1695 map[QStringLiteral( "use_whole_shape" )] = QString::number( mUseWholeShape );
1696 map[QStringLiteral( "max_distance" )] = QString::number( mMaxDistance );
1697 map[QStringLiteral( "distance_unit" )] = QgsUnitTypes::encodeUnit( mDistanceUnit );
1698 map[QStringLiteral( "distance_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale );
1699 map[QStringLiteral( "ignore_rings" )] = QString::number( mIgnoreRings );
1700 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1701 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1702 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1703 if ( mGradientRamp )
1704 {
1705 map.insert( mGradientRamp->properties() );
1706 }
1707
1708 return map;
1709}
1710
1712{
1713 auto sl = std::make_unique< QgsShapeburstFillSymbolLayer >( mColor, mColor2, mColorType, mBlurRadius, mUseWholeShape, mMaxDistance );
1714 if ( mGradientRamp )
1715 {
1716 sl->setColorRamp( mGradientRamp->clone() );
1717 }
1718 sl->setDistanceUnit( mDistanceUnit );
1719 sl->setDistanceMapUnitScale( mDistanceMapUnitScale );
1720 sl->setIgnoreRings( mIgnoreRings );
1721 sl->setOffset( mOffset );
1722 sl->setOffsetUnit( mOffsetUnit );
1723 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1724 copyDataDefinedProperties( sl.get() );
1725 copyPaintEffect( sl.get() );
1726 return sl.release();
1727}
1728
1730{
1731 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1732 return offsetBleed;
1733}
1734
1739
1741{
1742 mDistanceUnit = unit;
1743 mOffsetUnit = unit;
1744}
1745
1747{
1748 if ( mDistanceUnit == mOffsetUnit )
1749 {
1750 return mDistanceUnit;
1751 }
1753}
1754
1756{
1757 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
1758 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
1759}
1760
1762{
1763 mDistanceMapUnitScale = scale;
1764 mOffsetMapUnitScale = scale;
1765}
1766
1768{
1769 if ( mDistanceMapUnitScale == mOffsetMapUnitScale )
1770 {
1771 return mDistanceMapUnitScale;
1772 }
1773 return QgsMapUnitScale();
1774}
1775
1776
1777//QgsImageFillSymbolLayer
1778
1782
1784
1785void QgsImageFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1786{
1787 QPainter *p = context.renderContext().painter();
1788 if ( !p )
1789 {
1790 return;
1791 }
1792
1794 applyDataDefinedSettings( context );
1795
1796 p->setPen( QPen( Qt::NoPen ) );
1797
1798 QTransform bkTransform = mBrush.transform();
1799 if ( applyBrushTransformFromContext( &context ) && !context.renderContext().textureOrigin().isNull() )
1800 {
1801 QPointF leftCorner = context.renderContext().textureOrigin();
1802 QTransform t = mBrush.transform();
1803 t.translate( leftCorner.x(), leftCorner.y() );
1804 mBrush.setTransform( t );
1805 }
1806 else
1807 {
1808 QTransform t = mBrush.transform();
1809 t.translate( 0, 0 );
1810 mBrush.setTransform( t );
1811 }
1812
1813 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
1814 if ( useSelectedColor )
1815 {
1816 QColor selColor = context.renderContext().selectionColor();
1817 p->setBrush( QBrush( selColor ) );
1818 _renderPolygon( p, points, rings, context );
1819 }
1820
1821 if ( !qgsDoubleNear( mNextAngle, 0.0 ) )
1822 {
1823 QTransform t = mBrush.transform();
1824 t.rotate( mNextAngle );
1825 mBrush.setTransform( t );
1826 }
1827 p->setBrush( mBrush );
1828 _renderPolygon( p, points, rings, context );
1829
1830 mBrush.setTransform( bkTransform );
1831}
1832
1837
1842
1847
1852
1863
1865{
1866 return Qt::SolidLine;
1867#if 0
1868 if ( !mStroke )
1869 {
1870 return Qt::SolidLine;
1871 }
1872 else
1873 {
1874 return mStroke->dxfPenStyle();
1875 }
1876#endif //0
1877}
1878
1880{
1881 QVariantMap map;
1882 map.insert( QStringLiteral( "coordinate_reference" ), QgsSymbolLayerUtils::encodeCoordinateReference( mCoordinateReference ) );
1883 return map;
1884}
1885
1904
1905
1906//QgsSVGFillSymbolLayer
1907
1908QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString &svgFilePath, double width, double angle )
1910 , mPatternWidth( width )
1911{
1912 mStrokeWidth = 0.3;
1913 mAngle = angle;
1914 mColor = QColor( 255, 255, 255 );
1916}
1917
1918QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray &svgData, double width, double angle )
1920 , mPatternWidth( width )
1921 , mSvgData( svgData )
1922{
1923 storeViewBox();
1924 mStrokeWidth = 0.3;
1925 mAngle = angle;
1926 mColor = QColor( 255, 255, 255 );
1927 setDefaultSvgParams();
1928}
1929
1931
1933{
1935 mPatternWidthUnit = unit;
1936 mSvgStrokeWidthUnit = unit;
1937 mStrokeWidthUnit = unit;
1938 if ( mStroke )
1939 mStroke->setOutputUnit( unit );
1940}
1941
1943{
1945 if ( mPatternWidthUnit != unit || mSvgStrokeWidthUnit != unit || mStrokeWidthUnit != unit )
1946 {
1948 }
1949 return unit;
1950}
1951
1953{
1955 mPatternWidthMapUnitScale = scale;
1956 mSvgStrokeWidthMapUnitScale = scale;
1957}
1958
1960{
1961 if ( QgsImageFillSymbolLayer::mapUnitScale() == mPatternWidthMapUnitScale &&
1962 mPatternWidthMapUnitScale == mSvgStrokeWidthMapUnitScale &&
1963 mSvgStrokeWidthMapUnitScale == mStrokeWidthMapUnitScale )
1964 {
1965 return mPatternWidthMapUnitScale;
1966 }
1967 return QgsMapUnitScale();
1968}
1969
1970void QgsSVGFillSymbolLayer::setSvgFilePath( const QString &svgPath )
1971{
1972 mSvgData = QgsApplication::svgCache()->getImageData( svgPath );
1973 storeViewBox();
1974
1975 mSvgFilePath = svgPath;
1976 setDefaultSvgParams();
1977}
1978
1979QgsSymbolLayer *QgsSVGFillSymbolLayer::create( const QVariantMap &properties )
1980{
1981 QByteArray data;
1982 double width = 20;
1983 QString svgFilePath;
1984 double angle = 0.0;
1985
1986 if ( properties.contains( QStringLiteral( "width" ) ) )
1987 {
1988 width = properties[QStringLiteral( "width" )].toDouble();
1989 }
1990 if ( properties.contains( QStringLiteral( "svgFile" ) ) )
1991 {
1992 svgFilePath = properties[QStringLiteral( "svgFile" )].toString();
1993 }
1994 if ( properties.contains( QStringLiteral( "angle" ) ) )
1995 {
1996 angle = properties[QStringLiteral( "angle" )].toDouble();
1997 }
1998
1999 std::unique_ptr< QgsSVGFillSymbolLayer > symbolLayer;
2000 if ( !svgFilePath.isEmpty() )
2001 {
2002 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( svgFilePath, width, angle );
2003 }
2004 else
2005 {
2006 if ( properties.contains( QStringLiteral( "data" ) ) )
2007 {
2008 data = QByteArray::fromHex( properties[QStringLiteral( "data" )].toString().toLocal8Bit() );
2009 }
2010 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( data, width, angle );
2011 }
2012
2013 //svg parameters
2014 if ( properties.contains( QStringLiteral( "svgFillColor" ) ) )
2015 {
2016 //pre 2.5 projects used "svgFillColor"
2017 symbolLayer->setSvgFillColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "svgFillColor" )].toString() ) );
2018 }
2019 else if ( properties.contains( QStringLiteral( "color" ) ) )
2020 {
2021 symbolLayer->setSvgFillColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "color" )].toString() ) );
2022 }
2023 if ( properties.contains( QStringLiteral( "svgOutlineColor" ) ) )
2024 {
2025 //pre 2.5 projects used "svgOutlineColor"
2026 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "svgOutlineColor" )].toString() ) );
2027 }
2028 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2029 {
2030 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "outline_color" )].toString() ) );
2031 }
2032 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2033 {
2034 symbolLayer->setSvgStrokeColor( QgsColorUtils::colorFromString( properties[QStringLiteral( "line_color" )].toString() ) );
2035 }
2036 if ( properties.contains( QStringLiteral( "svgOutlineWidth" ) ) )
2037 {
2038 //pre 2.5 projects used "svgOutlineWidth"
2039 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "svgOutlineWidth" )].toDouble() );
2040 }
2041 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2042 {
2043 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "outline_width" )].toDouble() );
2044 }
2045 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2046 {
2047 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "line_width" )].toDouble() );
2048 }
2049
2050 //units
2051 if ( properties.contains( QStringLiteral( "pattern_width_unit" ) ) )
2052 {
2053 symbolLayer->setPatternWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "pattern_width_unit" )].toString() ) );
2054 }
2055 if ( properties.contains( QStringLiteral( "pattern_width_map_unit_scale" ) ) )
2056 {
2057 symbolLayer->setPatternWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "pattern_width_map_unit_scale" )].toString() ) );
2058 }
2059 if ( properties.contains( QStringLiteral( "svg_outline_width_unit" ) ) )
2060 {
2061 symbolLayer->setSvgStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "svg_outline_width_unit" )].toString() ) );
2062 }
2063 if ( properties.contains( QStringLiteral( "svg_outline_width_map_unit_scale" ) ) )
2064 {
2065 symbolLayer->setSvgStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "svg_outline_width_map_unit_scale" )].toString() ) );
2066 }
2067 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2068 {
2069 symbolLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2070 }
2071 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2072 {
2073 symbolLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2074 }
2075
2076 if ( properties.contains( QStringLiteral( "parameters" ) ) )
2077 {
2078 const QVariantMap parameters = properties[QStringLiteral( "parameters" )].toMap();
2079 symbolLayer->setParameters( QgsProperty::variantMapToPropertyMap( parameters ) );
2080 }
2081
2082 symbolLayer->restoreOldDataDefinedProperties( properties );
2083
2084 return symbolLayer.release();
2085}
2086
2087void QgsSVGFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2088{
2089 QVariantMap::iterator it = properties.find( QStringLiteral( "svgFile" ) );
2090 if ( it != properties.end() )
2091 {
2092 if ( saving )
2093 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2094 else
2095 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2096 }
2097}
2098
2100{
2101 return QStringLiteral( "SVGFill" );
2102}
2103
2104void QgsSVGFillSymbolLayer::applyPattern( QBrush &brush, const QString &svgFilePath, double patternWidth, Qgis::RenderUnit patternWidthUnit,
2105 const QColor &svgFillColor, const QColor &svgStrokeColor, double svgStrokeWidth,
2106 Qgis::RenderUnit svgStrokeWidthUnit, const QgsSymbolRenderContext &context,
2107 const QgsMapUnitScale &patternWidthMapUnitScale, const QgsMapUnitScale &svgStrokeWidthMapUnitScale, const QgsStringMap svgParameters )
2108{
2109 if ( mSvgViewBox.isNull() )
2110 {
2111 return;
2112 }
2113
2115
2116 if ( static_cast< int >( size ) < 1.0 || 10000.0 < size )
2117 {
2118 brush.setTextureImage( QImage() );
2119 }
2120 else
2121 {
2122 bool fitsInCache = true;
2124 QImage patternImage = QgsApplication::svgCache()->svgAsImage( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2125 context.renderContext().scaleFactor(), fitsInCache, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), svgParameters );
2126 if ( !fitsInCache )
2127 {
2128 QPicture patternPict = QgsApplication::svgCache()->svgAsPicture( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2129 context.renderContext().scaleFactor(), false, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
2130 double hwRatio = 1.0;
2131 if ( patternPict.width() > 0 )
2132 {
2133 hwRatio = static_cast< double >( patternPict.height() ) / static_cast< double >( patternPict.width() );
2134 }
2135 patternImage = QImage( static_cast< int >( size ), static_cast< int >( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
2136 patternImage.fill( 0 ); // transparent background
2137
2138 QPainter p( &patternImage );
2139 p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
2140 }
2141
2142 QTransform brushTransform;
2143 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2144 {
2145 QImage transparentImage = patternImage.copy();
2146 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2147 brush.setTextureImage( transparentImage );
2148 }
2149 else
2150 {
2151 brush.setTextureImage( patternImage );
2152 }
2153 brush.setTransform( brushTransform );
2154 }
2155}
2156
2158{
2159 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2160
2161 applyPattern( mBrush, mSvgFilePath, mPatternWidth, mPatternWidthUnit, mColor, mSvgStrokeColor, mSvgStrokeWidth, mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2162
2163 if ( mStroke )
2164 {
2165 mStroke->setRenderHints( mStroke->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
2166 mStroke->startRender( context.renderContext(), context.fields() );
2167 }
2168}
2169
2171{
2172 if ( mStroke )
2173 {
2174 mStroke->stopRender( context.renderContext() );
2175 }
2176}
2177
2178void QgsSVGFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
2179{
2180 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
2181
2182 if ( mStroke )
2183 {
2184 const bool useSelectedColor = SELECT_FILL_BORDER && shouldRenderUsingSelectionColor( context );
2185 mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, useSelectedColor );
2186 if ( rings )
2187 {
2188 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
2189 {
2190 mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, useSelectedColor );
2191 }
2192 }
2193 }
2194}
2195
2197{
2198 QVariantMap map;
2199 if ( !mSvgFilePath.isEmpty() )
2200 {
2201 map.insert( QStringLiteral( "svgFile" ), mSvgFilePath );
2202 }
2203 else
2204 {
2205 map.insert( QStringLiteral( "data" ), QString( mSvgData.toHex() ) );
2206 }
2207
2208 map.insert( QStringLiteral( "width" ), QString::number( mPatternWidth ) );
2209 map.insert( QStringLiteral( "angle" ), QString::number( mAngle ) );
2210
2211 //svg parameters
2212 map.insert( QStringLiteral( "color" ), QgsColorUtils::colorToString( mColor ) );
2213 map.insert( QStringLiteral( "outline_color" ), QgsColorUtils::colorToString( mSvgStrokeColor ) );
2214 map.insert( QStringLiteral( "outline_width" ), QString::number( mSvgStrokeWidth ) );
2215
2216 //units
2217 map.insert( QStringLiteral( "pattern_width_unit" ), QgsUnitTypes::encodeUnit( mPatternWidthUnit ) );
2218 map.insert( QStringLiteral( "pattern_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mPatternWidthMapUnitScale ) );
2219 map.insert( QStringLiteral( "svg_outline_width_unit" ), QgsUnitTypes::encodeUnit( mSvgStrokeWidthUnit ) );
2220 map.insert( QStringLiteral( "svg_outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mSvgStrokeWidthMapUnitScale ) );
2221 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
2222 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
2223
2224 map[QStringLiteral( "parameters" )] = QgsProperty::propertyMapToVariantMap( mParameters );
2225
2226 return map;
2227}
2228
2230{
2231 std::unique_ptr< QgsSVGFillSymbolLayer > clonedLayer;
2232 if ( !mSvgFilePath.isEmpty() )
2233 {
2234 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgFilePath, mPatternWidth, mAngle );
2235 clonedLayer->setSvgFillColor( mColor );
2236 clonedLayer->setSvgStrokeColor( mSvgStrokeColor );
2237 clonedLayer->setSvgStrokeWidth( mSvgStrokeWidth );
2238 }
2239 else
2240 {
2241 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgData, mPatternWidth, mAngle );
2242 }
2243
2244 clonedLayer->setPatternWidthUnit( mPatternWidthUnit );
2245 clonedLayer->setPatternWidthMapUnitScale( mPatternWidthMapUnitScale );
2246 clonedLayer->setSvgStrokeWidthUnit( mSvgStrokeWidthUnit );
2247 clonedLayer->setSvgStrokeWidthMapUnitScale( mSvgStrokeWidthMapUnitScale );
2248 clonedLayer->setStrokeWidthUnit( mStrokeWidthUnit );
2249 clonedLayer->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
2250
2251 clonedLayer->setParameters( mParameters );
2252
2253 if ( mStroke )
2254 {
2255 clonedLayer->setSubSymbol( mStroke->clone() );
2256 }
2257 copyDataDefinedProperties( clonedLayer.get() );
2258 copyPaintEffect( clonedLayer.get() );
2259 return clonedLayer.release();
2260}
2261
2262void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2263{
2264 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
2265 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2266 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2267 element.appendChild( symbolizerElem );
2268
2269 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2270
2271 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
2272 symbolizerElem.appendChild( fillElem );
2273
2274 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
2275 fillElem.appendChild( graphicFillElem );
2276
2277 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2278 graphicFillElem.appendChild( graphicElem );
2279
2280 if ( !mSvgFilePath.isEmpty() )
2281 {
2282 // encode a parametric SVG reference
2283 double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
2284 double strokeWidth = QgsSymbolLayerUtils::rescaleUom( mSvgStrokeWidth, mSvgStrokeWidthUnit, props );
2285 QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgStrokeColor, strokeWidth );
2286 }
2287 else
2288 {
2289 // TODO: create svg from data
2290 // <se:InlineContent>
2291 symbolizerElem.appendChild( doc.createComment( QStringLiteral( "SVG from data not implemented yet" ) ) );
2292 }
2293
2294 // <Rotation>
2295 QString angleFunc;
2296 bool ok;
2297 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
2298 if ( !ok )
2299 {
2300 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
2301 }
2302 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2303 {
2304 angleFunc = QString::number( angle + mAngle );
2305 }
2306 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
2307
2308 if ( mStroke )
2309 {
2310 // the stroke sub symbol should be stored within the Stroke element,
2311 // but it will be stored in a separated LineSymbolizer because it could
2312 // have more than one layer
2313 mStroke->toSld( doc, element, props );
2314 }
2315}
2316
2318{
2319 return mPatternWidthUnit == Qgis::RenderUnit::MapUnits || mPatternWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2320 || mSvgStrokeWidthUnit == Qgis::RenderUnit::MapUnits || mSvgStrokeWidthUnit == Qgis::RenderUnit::MetersInMapUnits;
2321}
2322
2324{
2325 return mStroke.get();
2326}
2327
2329{
2330 if ( !symbol ) //unset current stroke
2331 {
2332 mStroke.reset( nullptr );
2333 return true;
2334 }
2335
2336 if ( symbol->type() != Qgis::SymbolType::Line )
2337 {
2338 delete symbol;
2339 return false;
2340 }
2341
2342 QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
2343 if ( lineSymbol )
2344 {
2345 mStroke.reset( lineSymbol );
2346 return true;
2347 }
2348
2349 delete symbol;
2350 return false;
2351}
2352
2354{
2355 if ( mStroke && mStroke->symbolLayer( 0 ) )
2356 {
2357 double subLayerBleed = mStroke->symbolLayer( 0 )->estimateMaxBleed( context );
2358 return subLayerBleed;
2359 }
2360 return 0;
2361}
2362
2364{
2365 Q_UNUSED( context )
2366 if ( !mStroke )
2367 {
2368 return QColor( Qt::black );
2369 }
2370 return mStroke->color();
2371}
2372
2373QSet<QString> QgsSVGFillSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2374{
2375 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2376 if ( mStroke )
2377 attr.unite( mStroke->usedAttributes( context ) );
2378 return attr;
2379}
2380
2382{
2384 return true;
2385 if ( mStroke && mStroke->hasDataDefinedProperties() )
2386 return true;
2387 return false;
2388}
2389
2391{
2392 QString path, mimeType;
2393 QColor fillColor, strokeColor;
2394 Qt::PenStyle penStyle;
2395 double size, strokeWidth;
2396
2397 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
2398 if ( fillElem.isNull() )
2399 return nullptr;
2400
2401 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
2402 if ( graphicFillElem.isNull() )
2403 return nullptr;
2404
2405 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
2406 if ( graphicElem.isNull() )
2407 return nullptr;
2408
2409 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
2410 return nullptr;
2411
2412 if ( mimeType != QLatin1String( "image/svg+xml" ) )
2413 return nullptr;
2414
2415 QgsSymbolLayerUtils::lineFromSld( graphicElem, penStyle, strokeColor, strokeWidth );
2416
2417 double scaleFactor = 1.0;
2418 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2419 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2420 size = size * scaleFactor;
2421 strokeWidth = strokeWidth * scaleFactor;
2422
2423 double angle = 0.0;
2424 QString angleFunc;
2425 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2426 {
2427 bool ok;
2428 double d = angleFunc.toDouble( &ok );
2429 if ( ok )
2430 angle = d;
2431 }
2432
2433 auto sl = std::make_unique< QgsSVGFillSymbolLayer >( path, size, angle );
2434 sl->setOutputUnit( sldUnitSize );
2435 sl->setSvgFillColor( fillColor );
2436 sl->setSvgStrokeColor( strokeColor );
2437 sl->setSvgStrokeWidth( strokeWidth );
2438
2439 // try to get the stroke
2440 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2441 if ( !strokeElem.isNull() )
2442 {
2443 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createLineLayerFromSld( strokeElem );
2444 if ( l )
2445 {
2446 QgsSymbolLayerList layers;
2447 layers.append( l.release() );
2448 sl->setSubSymbol( new QgsLineSymbol( layers ) );
2449 }
2450 }
2451
2452 return sl.release();
2453}
2454
2456{
2460 {
2461 return; //no data defined settings
2462 }
2463
2465 {
2468 }
2469
2470 double width = mPatternWidth;
2472 {
2473 context.setOriginalValueVariable( mPatternWidth );
2475 }
2476 QString svgFile = mSvgFilePath;
2478 {
2479 context.setOriginalValueVariable( mSvgFilePath );
2481 context.renderContext().pathResolver() );
2482 }
2483 QColor svgFillColor = mColor;
2485 {
2488 }
2489 QColor svgStrokeColor = mSvgStrokeColor;
2491 {
2492 context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2494 }
2495 double strokeWidth = mSvgStrokeWidth;
2497 {
2498 context.setOriginalValueVariable( mSvgStrokeWidth );
2500 }
2501 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2502
2503 applyPattern( mBrush, svgFile, width, mPatternWidthUnit, svgFillColor, svgStrokeColor, strokeWidth,
2504 mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2505
2506}
2507
2508void QgsSVGFillSymbolLayer::storeViewBox()
2509{
2510 if ( !mSvgData.isEmpty() )
2511 {
2512 QSvgRenderer r( mSvgData );
2513 if ( r.isValid() )
2514 {
2515 mSvgViewBox = r.viewBoxF();
2516 return;
2517 }
2518 }
2519
2520 mSvgViewBox = QRectF();
2521}
2522
2523void QgsSVGFillSymbolLayer::setDefaultSvgParams()
2524{
2525 if ( mSvgFilePath.isEmpty() )
2526 {
2527 return;
2528 }
2529
2530 bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
2531 bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
2532 QColor defaultFillColor, defaultStrokeColor;
2533 double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
2534 QgsApplication::svgCache()->containsParams( mSvgFilePath, hasFillParam, hasDefaultFillColor, defaultFillColor,
2535 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
2536 hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2537 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
2538 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
2539
2540 double newFillOpacity = hasFillOpacityParam ? mColor.alphaF() : 1.0;
2541 double newStrokeOpacity = hasStrokeOpacityParam ? mSvgStrokeColor.alphaF() : 1.0;
2542
2543 if ( hasDefaultFillColor )
2544 {
2545 mColor = defaultFillColor;
2546 mColor.setAlphaF( newFillOpacity );
2547 }
2548 if ( hasDefaultFillOpacity )
2549 {
2550 mColor.setAlphaF( defaultFillOpacity );
2551 }
2552 if ( hasDefaultStrokeColor )
2553 {
2554 mSvgStrokeColor = defaultStrokeColor;
2555 mSvgStrokeColor.setAlphaF( newStrokeOpacity );
2556 }
2557 if ( hasDefaultStrokeOpacity )
2558 {
2559 mSvgStrokeColor.setAlphaF( defaultStrokeOpacity );
2560 }
2561 if ( hasDefaultStrokeWidth )
2562 {
2563 mSvgStrokeWidth = defaultStrokeWidth;
2564 }
2565}
2566
2567void QgsSVGFillSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2568{
2569 mParameters = parameters;
2570}
2571
2572
2575{
2576 mFillLineSymbol = std::make_unique<QgsLineSymbol>( );
2577 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
2578}
2579
2581
2583{
2584 mFillLineSymbol->setWidth( w );
2585 mLineWidth = w;
2586}
2587
2589{
2590 mFillLineSymbol->setColor( c );
2591 mColor = c;
2592}
2593
2595{
2596 return mFillLineSymbol ? mFillLineSymbol->color() : mColor;
2597}
2598
2600{
2601 if ( !symbol )
2602 {
2603 return false;
2604 }
2605
2606 if ( symbol->type() == Qgis::SymbolType::Line )
2607 {
2608 mFillLineSymbol.reset( qgis::down_cast<QgsLineSymbol *>( symbol ) );
2609 return true;
2610 }
2611 delete symbol;
2612 return false;
2613}
2614
2616{
2617 return mFillLineSymbol.get();
2618}
2619
2621{
2622 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2623 if ( mFillLineSymbol )
2624 attr.unite( mFillLineSymbol->usedAttributes( context ) );
2625 return attr;
2626}
2627
2629{
2631 return true;
2632 if ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
2633 return true;
2634 return false;
2635}
2636
2638{
2639 installMasks( context, true );
2640
2641 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2642}
2643
2645{
2646 removeMasks( context, true );
2647
2648 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
2649}
2650
2652{
2653
2654 double lineAngleRad { qDegreesToRadians( mLineAngle ) };
2655
2656 const int quadrant { static_cast<int>( lineAngleRad / M_PI_2 ) };
2657 Q_ASSERT( quadrant >= 0 && quadrant <= 3 );
2658
2659 switch ( quadrant )
2660 {
2661 case 0:
2662 {
2663 break;
2664 }
2665 case 1:
2666 {
2667 lineAngleRad -= M_PI / 2;
2668 break;
2669 }
2670 case 2:
2671 {
2672 lineAngleRad -= M_PI;
2673 break;
2674 }
2675 case 3:
2676 {
2677 lineAngleRad -= M_PI + M_PI_2;
2678 break;
2679 }
2680 }
2681
2682
2683 double distancePx { QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, {} ) };
2684
2685 QSize size { static_cast<int>( distancePx ), static_cast<int>( distancePx ) };
2686
2687 if ( static_cast<int>( mLineAngle ) % 90 != 0 )
2688 {
2689 size = QSize( static_cast<int>( distancePx / std::sin( lineAngleRad ) ), static_cast<int>( distancePx / std::cos( lineAngleRad ) ) );
2690 }
2691
2692 QPixmap pixmap( size );
2693 pixmap.fill( Qt::transparent );
2694 QPainter painter;
2695 painter.begin( &pixmap );
2696 painter.setRenderHint( QPainter::Antialiasing );
2697 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
2701 renderContext.setForceVectorOutput( true );
2702 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
2703
2704 std::unique_ptr< QgsLinePatternFillSymbolLayer > layerClone( clone() );
2705 layerClone->setOffset( 0 );
2706 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
2707 painter.end();
2708 return pixmap.toImage();
2709}
2710
2712{
2713 return 0;
2714}
2715
2717{
2719 mDistanceUnit = unit;
2720 mLineWidthUnit = unit;
2721 mOffsetUnit = unit;
2722
2723 if ( mFillLineSymbol )
2724 mFillLineSymbol->setOutputUnit( unit );
2725}
2726
2728{
2730 if ( mDistanceUnit != unit || mLineWidthUnit != unit || ( mOffsetUnit != unit && mOffsetUnit != Qgis::RenderUnit::Percentage ) )
2731 {
2733 }
2734 return unit;
2735}
2736
2738{
2739 return mDistanceUnit == Qgis::RenderUnit::MapUnits || mDistanceUnit == Qgis::RenderUnit::MetersInMapUnits
2740 || mLineWidthUnit == Qgis::RenderUnit::MapUnits || mLineWidthUnit == Qgis::RenderUnit::MetersInMapUnits
2741 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
2742}
2743
2745{
2747 mDistanceMapUnitScale = scale;
2748 mLineWidthMapUnitScale = scale;
2749 mOffsetMapUnitScale = scale;
2750}
2751
2753{
2754 if ( QgsImageFillSymbolLayer::mapUnitScale() == mDistanceMapUnitScale &&
2755 mDistanceMapUnitScale == mLineWidthMapUnitScale &&
2756 mLineWidthMapUnitScale == mOffsetMapUnitScale )
2757 {
2758 return mDistanceMapUnitScale;
2759 }
2760 return QgsMapUnitScale();
2761}
2762
2764{
2765 auto patternLayer = std::make_unique< QgsLinePatternFillSymbolLayer >();
2766
2767 //default values
2768 double lineAngle = 45;
2769 double distance = 5;
2770 double lineWidth = 0.5;
2771 QColor color( Qt::black );
2772 double offset = 0.0;
2773
2774 if ( properties.contains( QStringLiteral( "lineangle" ) ) )
2775 {
2776 //pre 2.5 projects used "lineangle"
2777 lineAngle = properties[QStringLiteral( "lineangle" )].toDouble();
2778 }
2779 else if ( properties.contains( QStringLiteral( "angle" ) ) )
2780 {
2781 lineAngle = properties[QStringLiteral( "angle" )].toDouble();
2782 }
2783 patternLayer->setLineAngle( lineAngle );
2784
2785 if ( properties.contains( QStringLiteral( "distance" ) ) )
2786 {
2787 distance = properties[QStringLiteral( "distance" )].toDouble();
2788 }
2789 patternLayer->setDistance( distance );
2790
2791 if ( properties.contains( QStringLiteral( "linewidth" ) ) )
2792 {
2793 //pre 2.5 projects used "linewidth"
2794 lineWidth = properties[QStringLiteral( "linewidth" )].toDouble();
2795 }
2796 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2797 {
2798 lineWidth = properties[QStringLiteral( "outline_width" )].toDouble();
2799 }
2800 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2801 {
2802 lineWidth = properties[QStringLiteral( "line_width" )].toDouble();
2803 }
2804 patternLayer->setLineWidth( lineWidth );
2805
2806 if ( properties.contains( QStringLiteral( "color" ) ) )
2807 {
2808 color = QgsColorUtils::colorFromString( properties[QStringLiteral( "color" )].toString() );
2809 }
2810 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2811 {
2812 color = QgsColorUtils::colorFromString( properties[QStringLiteral( "outline_color" )].toString() );
2813 }
2814 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2815 {
2816 color = QgsColorUtils::colorFromString( properties[QStringLiteral( "line_color" )].toString() );
2817 }
2818 patternLayer->setColor( color );
2819
2820 if ( properties.contains( QStringLiteral( "offset" ) ) )
2821 {
2822 offset = properties[QStringLiteral( "offset" )].toDouble();
2823 }
2824 patternLayer->setOffset( offset );
2825
2826
2827 if ( properties.contains( QStringLiteral( "distance_unit" ) ) )
2828 {
2829 patternLayer->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_unit" )].toString() ) );
2830 }
2831 if ( properties.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
2832 {
2833 patternLayer->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
2834 }
2835 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
2836 {
2837 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
2838 }
2839 else if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2840 {
2841 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2842 }
2843 if ( properties.contains( QStringLiteral( "line_width_map_unit_scale" ) ) )
2844 {
2845 patternLayer->setLineWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "line_width_map_unit_scale" )].toString() ) );
2846 }
2847 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
2848 {
2849 patternLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
2850 }
2851 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
2852 {
2853 patternLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
2854 }
2855 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2856 {
2857 patternLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2858 }
2859 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2860 {
2861 patternLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2862 }
2863 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
2864 {
2865 patternLayer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
2866 }
2867 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
2868 {
2869 patternLayer->setClipMode( QgsSymbolLayerUtils::decodeLineClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
2870 }
2871
2872 patternLayer->restoreOldDataDefinedProperties( properties );
2873
2874 return patternLayer.release();
2875}
2876
2878{
2879 return QStringLiteral( "LinePatternFill" );
2880}
2881
2882bool QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double lineAngle, double distance )
2883{
2884 mBrush.setTextureImage( QImage() ); // set empty in case we have to return
2885
2886 if ( !mFillLineSymbol )
2887 {
2888 return true;
2889 }
2890 // We have to make a copy because marker intervals will have to be adjusted
2891 std::unique_ptr< QgsLineSymbol > fillLineSymbol( mFillLineSymbol->clone() );
2892 if ( !fillLineSymbol )
2893 {
2894 return true;
2895 }
2896
2897 const QgsRenderContext &ctx = context.renderContext();
2898 //double strokePixelWidth = lineWidth * QgsSymbolLayerUtils::pixelSizeScaleFactor( ctx, mLineWidthUnit, mLineWidthMapUnitScale );
2899 double outputPixelDist = ctx.convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
2900 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDist * mOffset / 100
2901 : ctx.convertToPainterUnits( mOffset, mOffsetUnit, mOffsetMapUnitScale );
2902
2903 // NOTE: this may need to be modified if we ever change from a forced rasterized/brush approach,
2904 // because potentially we may want to allow vector based line pattern fills where the first line
2905 // is offset by a large distance
2906
2907 // fix truncated pattern with larger offsets
2908 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDist );
2909 if ( outputPixelOffset > outputPixelDist / 2.0 )
2910 outputPixelOffset -= outputPixelDist;
2911
2912 // To get all patterns into image, we have to consider symbols size (estimateMaxBleed()).
2913 // For marker lines we have to get markers interval.
2914 double outputPixelBleed = 0;
2915 double outputPixelInterval = 0; // maximum interval
2916 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2917 {
2918 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2919 double outputPixelLayerBleed = layer->estimateMaxBleed( context.renderContext() );
2920 outputPixelBleed = std::max( outputPixelBleed, outputPixelLayerBleed );
2921
2922 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2923 if ( markerLineLayer )
2924 {
2925 double outputPixelLayerInterval = ctx.convertToPainterUnits( markerLineLayer->interval(), markerLineLayer->intervalUnit(), markerLineLayer->intervalMapUnitScale() );
2926
2927 // There may be multiple marker lines with different intervals.
2928 // In theory we should find the least common multiple, but that could be too
2929 // big (multiplication of intervals in the worst case).
2930 // Because patterns without small common interval would look strange, we
2931 // believe that the longest interval should usually be sufficient.
2932 outputPixelInterval = std::max( outputPixelInterval, outputPixelLayerInterval );
2933 }
2934 }
2935
2936 if ( outputPixelInterval > 0 )
2937 {
2938 // We have to adjust marker intervals to integer pixel size to get
2939 // repeatable pattern.
2940 double intervalScale = std::round( outputPixelInterval ) / outputPixelInterval;
2941 outputPixelInterval = std::round( outputPixelInterval );
2942
2943 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2944 {
2945 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2946
2947 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2948 if ( markerLineLayer )
2949 {
2950 markerLineLayer->setInterval( intervalScale * markerLineLayer->interval() );
2951 }
2952 }
2953 }
2954
2955 //create image
2956 int height, width;
2957 lineAngle = std::fmod( lineAngle, 360 );
2958 if ( lineAngle < 0 )
2959 lineAngle += 360;
2960 if ( qgsDoubleNear( lineAngle, 0 ) || qgsDoubleNear( lineAngle, 360 ) || qgsDoubleNear( lineAngle, 180 ) )
2961 {
2962 height = outputPixelDist;
2963 width = outputPixelInterval > 0 ? outputPixelInterval : height;
2964 }
2965 else if ( qgsDoubleNear( lineAngle, 90 ) || qgsDoubleNear( lineAngle, 270 ) )
2966 {
2967 width = outputPixelDist;
2968 height = outputPixelInterval > 0 ? outputPixelInterval : width;
2969 }
2970 else
2971 {
2972 height = outputPixelDist / std::cos( lineAngle * M_PI / 180 ); //keep perpendicular distance between lines constant
2973 width = outputPixelDist / std::sin( lineAngle * M_PI / 180 );
2974
2975 // recalculate real angle and distance after rounding to pixels
2976 lineAngle = 180 * std::atan2( static_cast< double >( height ), static_cast< double >( width ) ) / M_PI;
2977 if ( lineAngle < 0 )
2978 {
2979 lineAngle += 360.;
2980 }
2981
2982 height = std::abs( height );
2983 width = std::abs( width );
2984
2985 outputPixelDist = std::abs( height * std::cos( lineAngle * M_PI / 180 ) );
2986
2987 // Round offset to correspond to one pixel height, otherwise lines may
2988 // be shifted on tile border if offset falls close to pixel center
2989 int offsetHeight = static_cast< int >( std::round( outputPixelOffset / std::cos( lineAngle * M_PI / 180 ) ) );
2990 outputPixelOffset = offsetHeight * std::cos( lineAngle * M_PI / 180 );
2991 }
2992
2993 //depending on the angle, we might need to render into a larger image and use a subset of it
2994 double dx = 0;
2995 double dy = 0;
2996
2997 // Add buffer based on bleed but keep precisely the height/width ratio (angle)
2998 // thus we add integer multiplications of width and height covering the bleed
2999 int bufferMulti = static_cast< int >( std::max( std::ceil( outputPixelBleed / width ), std::ceil( outputPixelBleed / width ) ) );
3000
3001 // Always buffer at least once so that center of line marker in upper right corner
3002 // does not fall outside due to representation error
3003 bufferMulti = std::max( bufferMulti, 1 );
3004
3005 int xBuffer = width * bufferMulti;
3006 int yBuffer = height * bufferMulti;
3007 int innerWidth = width;
3008 int innerHeight = height;
3009 width += 2 * xBuffer;
3010 height += 2 * yBuffer;
3011
3012 //protect from zero width/height image and symbol layer from eating too much memory
3013 if ( width > 2000 || height > 2000 || width == 0 || height == 0 )
3014 {
3015 return false;
3016 }
3017
3018 QImage patternImage( width, height, QImage::Format_ARGB32 );
3019 patternImage.fill( 0 );
3020
3021 QPointF p1, p2, p3, p4, p5, p6;
3022 if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
3023 {
3024 p1 = QPointF( 0, yBuffer );
3025 p2 = QPointF( width, yBuffer );
3026 p3 = QPointF( 0, yBuffer + innerHeight );
3027 p4 = QPointF( width, yBuffer + innerHeight );
3028 }
3029 else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
3030 {
3031 p1 = QPointF( xBuffer, height );
3032 p2 = QPointF( xBuffer, 0 );
3033 p3 = QPointF( xBuffer + innerWidth, height );
3034 p4 = QPointF( xBuffer + innerWidth, 0 );
3035 }
3036 else if ( lineAngle > 0 && lineAngle < 90 )
3037 {
3038 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3039 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3040 p1 = QPointF( 0, height );
3041 p2 = QPointF( width, 0 );
3042 p3 = QPointF( -dx, height - dy );
3043 p4 = QPointF( width - dx, -dy );
3044 p5 = QPointF( dx, height + dy );
3045 p6 = QPointF( width + dx, dy );
3046 }
3047 else if ( lineAngle > 180 && lineAngle < 270 )
3048 {
3049 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3050 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3051 p1 = QPointF( width, 0 );
3052 p2 = QPointF( 0, height );
3053 p3 = QPointF( width - dx, -dy );
3054 p4 = QPointF( -dx, height - dy );
3055 p5 = QPointF( width + dx, dy );
3056 p6 = QPointF( dx, height + dy );
3057 }
3058 else if ( lineAngle > 90 && lineAngle < 180 )
3059 {
3060 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3061 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3062 p1 = QPointF( 0, 0 );
3063 p2 = QPointF( width, height );
3064 p5 = QPointF( dx, -dy );
3065 p6 = QPointF( width + dx, height - dy );
3066 p3 = QPointF( -dx, dy );
3067 p4 = QPointF( width - dx, height + dy );
3068 }
3069 else if ( lineAngle > 270 && lineAngle < 360 )
3070 {
3071 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3072 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3073 p1 = QPointF( width, height );
3074 p2 = QPointF( 0, 0 );
3075 p5 = QPointF( width + dx, height - dy );
3076 p6 = QPointF( dx, -dy );
3077 p3 = QPointF( width - dx, height + dy );
3078 p4 = QPointF( -dx, dy );
3079 }
3080
3081 if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
3082 {
3083 QPointF tempPt;
3084 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
3085 p3 = QPointF( tempPt.x(), tempPt.y() );
3086 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
3087 p4 = QPointF( tempPt.x(), tempPt.y() );
3088 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
3089 p5 = QPointF( tempPt.x(), tempPt.y() );
3090 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
3091 p6 = QPointF( tempPt.x(), tempPt.y() );
3092
3093 //update p1, p2 last
3094 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
3095 p1 = QPointF( tempPt.x(), tempPt.y() );
3096 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
3097 p2 = QPointF( tempPt.x(), tempPt.y() );
3098 }
3099
3100 QPainter p( &patternImage );
3101
3102#if 0
3103 // DEBUG: Draw rectangle
3104 p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
3105 QPen pen( QColor( Qt::black ) );
3106 pen.setWidthF( 0.1 );
3107 pen.setCapStyle( Qt::FlatCap );
3108 p.setPen( pen );
3109
3110 // To see this rectangle, comment buffer cut below.
3111 // Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
3112 QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 );
3113 p.drawPolygon( polygon );
3114
3115 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 );
3116 p.drawPolygon( polygon );
3117#endif
3118
3119 // Use antialiasing because without antialiasing lines are rendered to the
3120 // right and below the mathematically defined points (not symmetrical)
3121 // and such tiles become useless for are filling
3122 p.setRenderHint( QPainter::Antialiasing, true );
3123
3124 // line rendering needs context for drawing on patternImage
3125 QgsRenderContext lineRenderContext;
3126 lineRenderContext.setPainter( &p );
3127 lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3129 lineRenderContext.setMapToPixel( mtp );
3130 lineRenderContext.setForceVectorOutput( false );
3131 lineRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3133 lineRenderContext.setDisabledSymbolLayersV2( context.renderContext().disabledSymbolLayersV2() );
3134
3135 fillLineSymbol->setRenderHints( fillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3136 fillLineSymbol->startRender( lineRenderContext, context.fields() );
3137
3138 QVector<QPolygonF> polygons;
3139 polygons.append( QPolygonF() << p1 << p2 );
3140 polygons.append( QPolygonF() << p3 << p4 );
3141 if ( !qgsDoubleNear( lineAngle, 0 ) && !qgsDoubleNear( lineAngle, 360 ) && !qgsDoubleNear( lineAngle, 90 ) && !qgsDoubleNear( lineAngle, 180 ) && !qgsDoubleNear( lineAngle, 270 ) )
3142 {
3143 polygons.append( QPolygonF() << p5 << p6 );
3144 }
3145
3146 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3147 for ( const QPolygonF &polygon : std::as_const( polygons ) )
3148 {
3149 fillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, useSelectedColor );
3150 }
3151
3152 fillLineSymbol->stopRender( lineRenderContext );
3153 p.end();
3154
3155 // Cut off the buffer
3156 patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
3157
3158 //set image to mBrush
3159 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3160 {
3161 QImage transparentImage = patternImage.copy();
3162 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3163 brush.setTextureImage( transparentImage );
3164 }
3165 else
3166 {
3167 brush.setTextureImage( patternImage );
3168 }
3169
3170 QTransform brushTransform;
3171 brush.setTransform( brushTransform );
3172
3173 return true;
3174}
3175
3177{
3178 // if we are using a vector based output, we need to render points as vectors
3179 // (OR if the line has data defined symbology, in which case we need to evaluate this line-by-line)
3180 mRenderUsingLines = context.forceVectorRendering()
3181 || ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
3184
3185 if ( !mRenderUsingLines )
3186 {
3187 // optimised render for screen only, use image based brush
3188 // (fallback to line rendering when pattern image will result in too large a memory footprint)
3189 mRenderUsingLines = !applyPattern( context, mBrush, mLineAngle, mDistance );
3190 }
3191
3192 if ( mRenderUsingLines && mFillLineSymbol )
3193 {
3194 mFillLineSymbol->setRenderHints( mFillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3195 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3196 mFillLineSymbolRenderStarted = true;
3197 }
3198}
3199
3201{
3202 if ( mFillLineSymbolRenderStarted )
3203 {
3204 mFillLineSymbol->stopRender( context.renderContext() );
3205 mFillLineSymbolRenderStarted = false;
3206 }
3207}
3208
3209void QgsLinePatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3210{
3211 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
3212 if ( !useSelectedColor && !mRenderUsingLines )
3213 {
3214 // use image based brush for speed
3215 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3216 return;
3217 }
3218
3219 if ( !mFillLineSymbolRenderStarted && mFillLineSymbol )
3220 {
3221 mFillLineSymbol->setRenderHints( mFillLineSymbol->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
3222 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3223 mFillLineSymbolRenderStarted = true;
3224 }
3225
3226 // vector based output - so draw line by line!
3227 QPainter *p = context.renderContext().painter();
3228 if ( !p )
3229 {
3230 return;
3231 }
3232
3233 double lineAngle = mLineAngle;
3235 {
3236 context.setOriginalValueVariable( mLineAngle );
3238 }
3239
3240 double distance = mDistance;
3242 {
3243 context.setOriginalValueVariable( mDistance );
3245 }
3246 const double outputPixelDistance = context.renderContext().convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
3247
3248 double offset = mOffset;
3249 double outputPixelOffset = mOffsetUnit == Qgis::RenderUnit::Percentage ? outputPixelDistance * offset / 100
3250 : context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3251
3252 // fix truncated pattern with larger offsets
3253 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDistance );
3254 if ( outputPixelOffset > outputPixelDistance / 2.0 )
3255 outputPixelOffset -= outputPixelDistance;
3256
3257 p->setPen( QPen( Qt::NoPen ) );
3258
3259 // if invalid parameters, skip out
3260 if ( qgsDoubleNear( distance, 0 ) )
3261 return;
3262
3263 p->save();
3264
3265 Qgis::LineClipMode clipMode = mClipMode;
3267 {
3269 bool ok = false;
3270 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::LineClipping, context.renderContext().expressionContext(), QString(), &ok );
3271 if ( ok )
3272 {
3273 Qgis::LineClipMode decodedMode = QgsSymbolLayerUtils::decodeLineClipMode( valueString, &ok );
3274 if ( ok )
3275 clipMode = decodedMode;
3276 }
3277 }
3278
3279 std::unique_ptr< QgsPolygon > shapePolygon;
3280 std::unique_ptr< QgsGeometryEngine > shapeEngine;
3281 switch ( clipMode )
3282 {
3284 break;
3285
3287 {
3288 shapePolygon = std::make_unique< QgsPolygon >();
3289 std::unique_ptr< QgsLineString > fromPolygon = QgsLineString::fromQPolygonF( points );
3290 shapePolygon->setExteriorRing( fromPolygon.release() );
3291 if ( rings )
3292 {
3293 for ( const QPolygonF &ring : *rings )
3294 {
3295 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
3296 shapePolygon->addInteriorRing( fromRing.release() );
3297 }
3298 }
3299 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3300 shapeEngine->prepareGeometry();
3301 break;
3302 }
3303
3305 {
3306 QPainterPath path;
3307 path.addPolygon( points );
3308 if ( rings )
3309 {
3310 for ( const QPolygonF &ring : *rings )
3311 {
3312 path.addPolygon( ring );
3313 }
3314 }
3315 p->setClipPath( path, Qt::IntersectClip );
3316 break;
3317 }
3318 }
3319
3320 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3321 const QRectF boundingRect = points.boundingRect();
3322
3323 QTransform invertedRotateTransform;
3324 double left;
3325 double top;
3326 double right;
3327 double bottom;
3328
3329 QTransform transform;
3330 if ( applyBrushTransform )
3331 {
3332 // rotation applies around center of feature
3333 transform.translate( -boundingRect.center().x(),
3334 -boundingRect.center().y() );
3335 transform.rotate( lineAngle );
3336 transform.translate( boundingRect.center().x(),
3337 boundingRect.center().y() );
3338 }
3339 else
3340 {
3341 // rotation applies around top of viewport
3342 transform.rotate( lineAngle );
3343 }
3344
3345 const QRectF transformedBounds = transform.map( points ).boundingRect();
3346
3347 // bounds are expanded out a bit to account for maximum line width
3348 const double buffer = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFillLineSymbol.get(), context.renderContext() );
3349 left = transformedBounds.left() - buffer * 2;
3350 top = transformedBounds.top() - buffer * 2;
3351 right = transformedBounds.right() + buffer * 2;
3352 bottom = transformedBounds.bottom() + buffer * 2;
3353 invertedRotateTransform = transform.inverted();
3354
3355 if ( !applyBrushTransform )
3356 {
3357 top -= transformedBounds.top() - ( outputPixelDistance * std::floor( transformedBounds.top() / outputPixelDistance ) );
3358 }
3359
3361 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
3362 const bool needsExpressionContext = mFillLineSymbol->hasDataDefinedProperties();
3363
3364 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3366
3367 int currentLine = 0;
3368 for ( double currentY = top; currentY <= bottom; currentY += outputPixelDistance )
3369 {
3370 if ( context.renderContext().renderingStopped() )
3371 break;
3372
3373 if ( needsExpressionContext )
3374 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_line_number" ), ++currentLine, true ) );
3375
3376 double x1 = left;
3377 double y1 = currentY;
3378 double x2 = left;
3379 double y2 = currentY;
3380 invertedRotateTransform.map( left, currentY - outputPixelOffset, &x1, &y1 );
3381 invertedRotateTransform.map( right, currentY - outputPixelOffset, &x2, &y2 );
3382
3383 if ( shapeEngine )
3384 {
3385 QgsLineString ls( QgsPoint( x1, y1 ), QgsPoint( x2, y2 ) );
3386 std::unique_ptr< QgsAbstractGeometry > intersection( shapeEngine->intersection( &ls ) );
3387 for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
3388 {
3389 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( *it ) )
3390 {
3391 mFillLineSymbol->renderPolyline( ls->asQPolygonF(), context.feature(), context.renderContext(), -1, useSelectedColor );
3392 }
3393 }
3394 }
3395 else
3396 {
3397 mFillLineSymbol->renderPolyline( QPolygonF() << QPointF( x1, y1 ) << QPointF( x2, y2 ), context.feature(), context.renderContext(), -1, useSelectedColor );
3398 }
3399 }
3400
3401 p->restore();
3402
3404}
3405
3407{
3408 QVariantMap map = QgsImageFillSymbolLayer::properties();
3409 map.insert( QStringLiteral( "angle" ), QString::number( mLineAngle ) );
3410 map.insert( QStringLiteral( "distance" ), QString::number( mDistance ) );
3411 map.insert( QStringLiteral( "line_width" ), QString::number( mLineWidth ) );
3412 map.insert( QStringLiteral( "color" ), QgsColorUtils::colorToString( mColor ) );
3413 map.insert( QStringLiteral( "offset" ), QString::number( mOffset ) );
3414 map.insert( QStringLiteral( "distance_unit" ), QgsUnitTypes::encodeUnit( mDistanceUnit ) );
3415 map.insert( QStringLiteral( "line_width_unit" ), QgsUnitTypes::encodeUnit( mLineWidthUnit ) );
3416 map.insert( QStringLiteral( "offset_unit" ), QgsUnitTypes::encodeUnit( mOffsetUnit ) );
3417 map.insert( QStringLiteral( "distance_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale ) );
3418 map.insert( QStringLiteral( "line_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineWidthMapUnitScale ) );
3419 map.insert( QStringLiteral( "offset_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ) );
3420 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3421 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3422 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeLineClipMode( mClipMode ) );
3423 return map;
3424}
3425
3427{
3429 if ( mFillLineSymbol )
3430 {
3431 clonedLayer->setSubSymbol( mFillLineSymbol->clone() );
3432 }
3433 copyPaintEffect( clonedLayer );
3434 copyDataDefinedProperties( clonedLayer );
3435 return clonedLayer;
3436}
3437
3438void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3439{
3440 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
3441 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
3442 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
3443 element.appendChild( symbolizerElem );
3444
3445 // <Geometry>
3446 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
3447
3448 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
3449 symbolizerElem.appendChild( fillElem );
3450
3451 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
3452 fillElem.appendChild( graphicFillElem );
3453
3454 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
3455 graphicFillElem.appendChild( graphicElem );
3456
3457 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
3458
3459 // Export to PNG (TODO: SVG)
3460 bool exportOk { false };
3461 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
3462 {
3463 const QImage image { toTiledPatternImage() };
3464 if ( ! image.isNull() )
3465 {
3466 const QFileInfo info { context.exportFilePath() };
3467 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
3468 pngPath = QgsFileUtils::uniquePath( pngPath );
3469 image.save( pngPath );
3470 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
3471 exportOk = true;
3472 }
3473 }
3474
3475 if ( ! exportOk )
3476 {
3477 //line properties must be inside the graphic definition
3478 QColor lineColor = mFillLineSymbol ? mFillLineSymbol->color() : QColor();
3479 double lineWidth = mFillLineSymbol ? mFillLineSymbol->width() : 0.0;
3480 lineWidth = QgsSymbolLayerUtils::rescaleUom( lineWidth, mLineWidthUnit, props );
3481 double distance = QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, props );
3482 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "horline" ), QColor(), lineColor, Qt::SolidLine, lineWidth, distance );
3483
3484 // <Rotation>
3485 QString angleFunc;
3486 bool ok;
3487 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
3488 if ( !ok )
3489 {
3490 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mLineAngle );
3491 }
3492 else if ( !qgsDoubleNear( angle + mLineAngle, 0.0 ) )
3493 {
3494 angleFunc = QString::number( angle + mLineAngle );
3495 }
3496 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
3497
3498 // <se:Displacement>
3499 QPointF lineOffset( std::sin( mLineAngle ) * mOffset, std::cos( mLineAngle ) * mOffset );
3500 lineOffset = QgsSymbolLayerUtils::rescaleUom( lineOffset, mOffsetUnit, props );
3501 QgsSymbolLayerUtils::createDisplacementElement( doc, graphicElem, lineOffset );
3502 }
3503}
3504
3505QString QgsLinePatternFillSymbolLayer::ogrFeatureStyleWidth( double widthScaleFactor ) const
3506{
3507 QString featureStyle;
3508 featureStyle.append( "Brush(" );
3509 featureStyle.append( QStringLiteral( "fc:%1" ).arg( mColor.name() ) );
3510 featureStyle.append( QStringLiteral( ",bc:%1" ).arg( QLatin1String( "#00000000" ) ) ); //transparent background
3511 featureStyle.append( ",id:\"ogr-brush-2\"" );
3512 featureStyle.append( QStringLiteral( ",a:%1" ).arg( mLineAngle ) );
3513 featureStyle.append( QStringLiteral( ",s:%1" ).arg( mLineWidth * widthScaleFactor ) );
3514 featureStyle.append( ",dx:0mm" );
3515 featureStyle.append( QStringLiteral( ",dy:%1mm" ).arg( mDistance * widthScaleFactor ) );
3516 featureStyle.append( ')' );
3517 return featureStyle;
3518}
3519
3521{
3523 && ( !mFillLineSymbol || !mFillLineSymbol->hasDataDefinedProperties() ) )
3524 {
3525 return; //no data defined settings
3526 }
3527
3528 double lineAngle = mLineAngle;
3530 {
3531 context.setOriginalValueVariable( mLineAngle );
3533 }
3534 double distance = mDistance;
3536 {
3537 context.setOriginalValueVariable( mDistance );
3539 }
3540 applyPattern( context, mBrush, lineAngle, distance );
3541}
3542
3544{
3545 QString name;
3546 QColor fillColor, lineColor;
3547 double size, lineWidth;
3548 Qt::PenStyle lineStyle;
3549
3550 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
3551 if ( fillElem.isNull() )
3552 return nullptr;
3553
3554 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
3555 if ( graphicFillElem.isNull() )
3556 return nullptr;
3557
3558 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
3559 if ( graphicElem.isNull() )
3560 return nullptr;
3561
3562 if ( !QgsSymbolLayerUtils::wellKnownMarkerFromSld( graphicElem, name, fillColor, lineColor, lineStyle, lineWidth, size ) )
3563 return nullptr;
3564
3565 if ( name != QLatin1String( "horline" ) )
3566 return nullptr;
3567
3568 double angle = 0.0;
3569 QString angleFunc;
3570 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3571 {
3572 bool ok;
3573 double d = angleFunc.toDouble( &ok );
3574 if ( ok )
3575 angle = d;
3576 }
3577
3578 double offset = 0.0;
3579 QPointF vectOffset;
3580 if ( QgsSymbolLayerUtils::displacementFromSldElement( graphicElem, vectOffset ) )
3581 {
3582 offset = std::sqrt( std::pow( vectOffset.x(), 2 ) + std::pow( vectOffset.y(), 2 ) );
3583 }
3584
3585 double scaleFactor = 1.0;
3586 const QString uom = element.attribute( QStringLiteral( "uom" ) );
3587 Qgis::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
3588 size = size * scaleFactor;
3589 lineWidth = lineWidth * scaleFactor;
3590
3591 auto sl = std::make_unique< QgsLinePatternFillSymbolLayer >();
3592 sl->setOutputUnit( sldUnitSize );
3593 sl->setColor( lineColor );
3594 sl->setLineWidth( lineWidth );
3595 sl->setLineAngle( angle );
3596 sl->setOffset( offset );
3597 sl->setDistance( size );
3598
3599 // try to get the stroke
3600 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
3601 if ( !strokeElem.isNull() )
3602 {
3603 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createLineLayerFromSld( strokeElem );
3604 if ( l )
3605 {
3606 QgsSymbolLayerList layers;
3607 layers.append( l.release() );
3608 sl->setSubSymbol( new QgsLineSymbol( layers ) );
3609 }
3610 }
3611
3612 return sl.release();
3613}
3614
3615
3617
3620{
3621 mMarkerSymbol = std::make_unique<QgsMarkerSymbol>();
3622 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
3623}
3624
3626
3628{
3630 mDistanceXUnit = unit;
3631 mDistanceYUnit = unit;
3632 // don't change "percentage" units -- since they adapt directly to whatever other unit is set
3634 mDisplacementXUnit = unit;
3636 mDisplacementYUnit = unit;
3638 mOffsetXUnit = unit;
3640 mOffsetYUnit = unit;
3642 mRandomDeviationXUnit = unit;
3644 mRandomDeviationYUnit = unit;
3645
3646 if ( mMarkerSymbol )
3647 {
3648 mMarkerSymbol->setOutputUnit( unit );
3649 }
3650}
3651
3668
3680
3693
3709
3711{
3712 auto layer = std::make_unique< QgsPointPatternFillSymbolLayer >();
3713 if ( properties.contains( QStringLiteral( "distance_x" ) ) )
3714 {
3715 layer->setDistanceX( properties[QStringLiteral( "distance_x" )].toDouble() );
3716 }
3717 if ( properties.contains( QStringLiteral( "distance_y" ) ) )
3718 {
3719 layer->setDistanceY( properties[QStringLiteral( "distance_y" )].toDouble() );
3720 }
3721 if ( properties.contains( QStringLiteral( "displacement_x" ) ) )
3722 {
3723 layer->setDisplacementX( properties[QStringLiteral( "displacement_x" )].toDouble() );
3724 }
3725 if ( properties.contains( QStringLiteral( "displacement_y" ) ) )
3726 {
3727 layer->setDisplacementY( properties[QStringLiteral( "displacement_y" )].toDouble() );
3728 }
3729 if ( properties.contains( QStringLiteral( "offset_x" ) ) )
3730 {
3731 layer->setOffsetX( properties[QStringLiteral( "offset_x" )].toDouble() );
3732 }
3733 if ( properties.contains( QStringLiteral( "offset_y" ) ) )
3734 {
3735 layer->setOffsetY( properties[QStringLiteral( "offset_y" )].toDouble() );
3736 }
3737
3738 if ( properties.contains( QStringLiteral( "distance_x_unit" ) ) )
3739 {
3740 layer->setDistanceXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_x_unit" )].toString() ) );
3741 }
3742 if ( properties.contains( QStringLiteral( "distance_x_map_unit_scale" ) ) )
3743 {
3744 layer->setDistanceXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_x_map_unit_scale" )].toString() ) );
3745 }
3746 if ( properties.contains( QStringLiteral( "distance_y_unit" ) ) )
3747 {
3748 layer->setDistanceYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_y_unit" )].toString() ) );
3749 }
3750 if ( properties.contains( QStringLiteral( "distance_y_map_unit_scale" ) ) )
3751 {
3752 layer->setDistanceYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_y_map_unit_scale" )].toString() ) );
3753 }
3754 if ( properties.contains( QStringLiteral( "displacement_x_unit" ) ) )
3755 {
3756 layer->setDisplacementXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_x_unit" )].toString() ) );
3757 }
3758 if ( properties.contains( QStringLiteral( "displacement_x_map_unit_scale" ) ) )
3759 {
3760 layer->setDisplacementXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_x_map_unit_scale" )].toString() ) );
3761 }
3762 if ( properties.contains( QStringLiteral( "displacement_y_unit" ) ) )
3763 {
3764 layer->setDisplacementYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_y_unit" )].toString() ) );
3765 }
3766 if ( properties.contains( QStringLiteral( "displacement_y_map_unit_scale" ) ) )
3767 {
3768 layer->setDisplacementYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_y_map_unit_scale" )].toString() ) );
3769 }
3770 if ( properties.contains( QStringLiteral( "offset_x_unit" ) ) )
3771 {
3772 layer->setOffsetXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_x_unit" )].toString() ) );
3773 }
3774 if ( properties.contains( QStringLiteral( "offset_x_map_unit_scale" ) ) )
3775 {
3776 layer->setOffsetXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_x_map_unit_scale" )].toString() ) );
3777 }
3778 if ( properties.contains( QStringLiteral( "offset_y_unit" ) ) )
3779 {
3780 layer->setOffsetYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_y_unit" )].toString() ) );
3781 }
3782 if ( properties.contains( QStringLiteral( "offset_y_map_unit_scale" ) ) )
3783 {
3784 layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_y_map_unit_scale" )].toString() ) );
3785 }
3786
3787 if ( properties.contains( QStringLiteral( "random_deviation_x" ) ) )
3788 {
3789 layer->setMaximumRandomDeviationX( properties[QStringLiteral( "random_deviation_x" )].toDouble() );
3790 }
3791 if ( properties.contains( QStringLiteral( "random_deviation_y" ) ) )
3792 {
3793 layer->setMaximumRandomDeviationY( properties[QStringLiteral( "random_deviation_y" )].toDouble() );
3794 }
3795 if ( properties.contains( QStringLiteral( "random_deviation_x_unit" ) ) )
3796 {
3797 layer->setRandomDeviationXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_x_unit" )].toString() ) );
3798 }
3799 if ( properties.contains( QStringLiteral( "random_deviation_x_map_unit_scale" ) ) )
3800 {
3801 layer->setRandomDeviationXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_x_map_unit_scale" )].toString() ) );
3802 }
3803 if ( properties.contains( QStringLiteral( "random_deviation_y_unit" ) ) )
3804 {
3805 layer->setRandomDeviationYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_y_unit" )].toString() ) );
3806 }
3807 if ( properties.contains( QStringLiteral( "random_deviation_y_map_unit_scale" ) ) )
3808 {
3809 layer->setRandomDeviationYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_y_map_unit_scale" )].toString() ) );
3810 }
3811 unsigned long seed = 0;
3812 if ( properties.contains( QStringLiteral( "seed" ) ) )
3813 seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
3814 else
3815 {
3816 // if we a creating a new point pattern fill from scratch, we default to a random seed
3817 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
3818 std::random_device rd;
3819 std::mt19937 mt( seed == 0 ? rd() : seed );
3820 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
3821 seed = uniformDist( mt );
3822 }
3823 layer->setSeed( seed );
3824
3825 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
3826 {
3827 layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
3828 }
3829 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
3830 {
3831 layer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
3832 }
3833 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
3834 {
3835 layer->setClipMode( QgsSymbolLayerUtils::decodeMarkerClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
3836 }
3837 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
3838 {
3839 layer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
3840 }
3841
3842 if ( properties.contains( QStringLiteral( "angle" ) ) )
3843 {
3844 layer->setAngle( properties[QStringLiteral( "angle" )].toDouble() );
3845 }
3846
3848
3849 return layer.release();
3850}
3851
3853{
3854 return QStringLiteral( "PointPatternFill" );
3855}
3856
3857bool QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double distanceX, double distanceY,
3858 double displacementX, double displacementY, double offsetX, double offsetY )
3859{
3860 //render 3 rows and columns in one go to easily incorporate displacement
3861 const QgsRenderContext &ctx = context.renderContext();
3864
3865 double widthOffset = std::fmod(
3866 mOffsetXUnit == Qgis::RenderUnit::Percentage ? ( width * offsetX / 200 ) : ctx.convertToPainterUnits( offsetX, mOffsetXUnit, mOffsetXMapUnitScale ),
3867 width );
3868 double heightOffset = std::fmod(
3869 mOffsetYUnit == Qgis::RenderUnit::Percentage ? ( height * offsetY / 200 ) : ctx.convertToPainterUnits( offsetY, mOffsetYUnit, mOffsetYMapUnitScale ),
3870 height );
3871
3872 if ( width > 2000 || height > 2000 ) //protect symbol layer from eating too much memory
3873 {
3874 brush.setTextureImage( QImage() );
3875 return false;
3876 }
3877
3878 QImage patternImage( width, height, QImage::Format_ARGB32 );
3879 patternImage.fill( 0 );
3880 if ( patternImage.isNull() )
3881 {
3882 brush.setTextureImage( QImage() );
3883 return false;
3884 }
3885 if ( mMarkerSymbol )
3886 {
3887 QPainter p( &patternImage );
3888
3889 //marker rendering needs context for drawing on patternImage
3890 QgsRenderContext pointRenderContext;
3891 pointRenderContext.setRendererScale( context.renderContext().rendererScale() );
3892 pointRenderContext.setPainter( &p );
3893 pointRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3894
3897 pointRenderContext.setMapToPixel( mtp );
3898 pointRenderContext.setForceVectorOutput( false );
3899 pointRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3901
3903 mMarkerSymbol->startRender( pointRenderContext, context.fields() );
3904
3905 //render points on distance grid
3906 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3907 {
3908 for ( double currentY = -height; currentY <= height * 2.0; currentY += height )
3909 {
3910 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset, currentY + heightOffset ), context.feature(), pointRenderContext );
3911 }
3912 }
3913
3914 //render displaced points
3915 double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
3916 ? ( width * displacementX / 200 )
3917 : ctx.convertToPainterUnits( displacementX, mDisplacementXUnit, mDisplacementXMapUnitScale );
3918 double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
3919 ? ( height * displacementY / 200 )
3920 : ctx.convertToPainterUnits( displacementY, mDisplacementYUnit, mDisplacementYMapUnitScale );
3921 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3922 {
3923 for ( double currentY = -height / 2.0; currentY <= height * 2.0; currentY += height )
3924 {
3925 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + displacementPixelX, currentY + heightOffset ), context.feature(), pointRenderContext );
3926 }
3927 }
3928
3929 for ( double currentX = -width / 2.0; currentX <= width * 2.0; currentX += width )
3930 {
3931 for ( double currentY = -height; currentY <= height * 2.0; currentY += height / 2.0 )
3932 {
3933 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + ( std::fmod( currentY, height ) != 0 ? displacementPixelX : 0 ), currentY + heightOffset - displacementPixelY ), context.feature(), pointRenderContext );
3934 }
3935 }
3936
3937 mMarkerSymbol->stopRender( pointRenderContext );
3938 }
3939
3940 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3941 {
3942 QImage transparentImage = patternImage.copy();
3943 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3944 brush.setTextureImage( transparentImage );
3945 }
3946 else
3947 {
3948 brush.setTextureImage( patternImage );
3949 }
3950 QTransform brushTransform;
3951 brush.setTransform( brushTransform );
3952
3953 return true;
3954}
3955
3957{
3958 // if we are using a vector based output, we need to render points as vectors
3959 // (OR if the marker has data defined symbology, in which case we need to evaluate this point-by-point)
3960 mRenderUsingMarkers = context.forceVectorRendering()
3961 || ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
3965 || mClipMode != Qgis::MarkerClipMode::Shape
3968 || !qgsDoubleNear( mAngle, 0 )
3970
3971 if ( !mRenderUsingMarkers )
3972 {
3973 // optimised render for screen only, use image based brush
3974 // (fallback to line rendering when pattern image will result in too large a memory footprint)
3975 mRenderUsingMarkers = !applyPattern( context, mBrush, mDistanceX, mDistanceY, mDisplacementX, mDisplacementY, mOffsetX, mOffsetY );
3976 }
3977
3978 if ( mRenderUsingMarkers && mMarkerSymbol )
3979 {
3981 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
3983 }
3984}
3985
3987{
3989 {
3990 mMarkerSymbol->stopRender( context.renderContext() );
3992 }
3993}
3994
3996{
3997 installMasks( context, true );
3998
3999 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4000 // Otherwise generators used in the subsymbol will only render a single point per feature (they
4001 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
4002}
4003
4005{
4006 removeMasks( context, true );
4007
4008 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
4009 // Otherwise generators used in the subsymbol will only render a single point per feature (they
4010 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
4011}
4012
4013void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4014{
4015 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4016 if ( !useSelectedColor && !mRenderUsingMarkers )
4017 {
4018 // use image based brush for speed
4019 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
4020 return;
4021 }
4022
4024 {
4026 mMarkerSymbol->startRender( context.renderContext(), context.fields() );
4028 }
4029
4030 // vector based output - so draw dot by dot!
4031 QPainter *p = context.renderContext().painter();
4032 if ( !p )
4033 {
4034 return;
4035 }
4036
4037 double angle = mAngle;
4039 {
4042 }
4043
4044 double distanceX = mDistanceX;
4046 {
4049 }
4051
4052 double distanceY = mDistanceY;
4054 {
4057 }
4059
4060 double offsetX = mOffsetX;
4062 {
4065 }
4066 const double widthOffset = std::fmod(
4068 ? ( offsetX * width / 100 )
4070 width );
4071
4072 double offsetY = mOffsetY;
4074 {
4077 }
4078 const double heightOffset = std::fmod(
4080 ? ( offsetY * height / 100 )
4082 height );
4083
4086 {
4089 }
4090 const double displacementPixelX = mDisplacementXUnit == Qgis::RenderUnit::Percentage
4091 ? ( displacementX * width / 100 )
4093
4096 {
4099 }
4100 const double displacementPixelY = mDisplacementYUnit == Qgis::RenderUnit::Percentage
4101 ? ( displacementY * height / 100 )
4103
4104 p->setPen( QPen( Qt::NoPen ) );
4105
4106 // if invalid parameters, skip out
4107 if ( qgsDoubleNear( width, 0 ) || qgsDoubleNear( height, 0 ) || width < 0 || height < 0 )
4108 return;
4109
4110 p->save();
4111
4112 Qgis::MarkerClipMode clipMode = mClipMode;
4114 {
4116 bool ok = false;
4117 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::Property::MarkerClipping, context.renderContext().expressionContext(), QString(), &ok );
4118 if ( ok )
4119 {
4120 Qgis::MarkerClipMode decodedMode = QgsSymbolLayerUtils::decodeMarkerClipMode( valueString, &ok );
4121 if ( ok )
4122 clipMode = decodedMode;
4123 }
4124 }
4125
4126 std::unique_ptr< QgsPolygon > shapePolygon;
4127 std::unique_ptr< QgsGeometryEngine > shapeEngine;
4128 switch ( clipMode )
4129 {
4133 {
4134 shapePolygon = std::make_unique< QgsPolygon >();
4135 std::unique_ptr< QgsLineString > fromPolygon = QgsLineString::fromQPolygonF( points );
4136 shapePolygon->setExteriorRing( fromPolygon.release() );
4137 if ( rings )
4138 {
4139 for ( const QPolygonF &ring : *rings )
4140 {
4141 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
4142 shapePolygon->addInteriorRing( fromRing.release() );
4143 }
4144 }
4145 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
4146 shapeEngine->prepareGeometry();
4147 break;
4148 }
4149
4151 {
4152 QPainterPath path;
4153 path.addPolygon( points );
4154 if ( rings )
4155 {
4156 for ( const QPolygonF &ring : *rings )
4157 {
4158 path.addPolygon( ring );
4159 }
4160 }
4161 p->setClipPath( path, Qt::IntersectClip );
4162 break;
4163 }
4164 }
4165
4166 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
4167 const QRectF boundingRect = points.boundingRect();
4168
4169 QTransform invertedRotateTransform;
4170 double left;
4171 double top;
4172 double right;
4173 double bottom;
4174
4175 if ( !qgsDoubleNear( angle, 0 ) )
4176 {
4177 QTransform transform;
4178 if ( applyBrushTransform )
4179 {
4180 // rotation applies around center of feature
4181 transform.translate( -boundingRect.center().x(),
4182 -boundingRect.center().y() );
4183 transform.rotate( -angle );
4184 transform.translate( boundingRect.center().x(),
4185 boundingRect.center().y() );
4186 }
4187 else
4188 {
4189 // rotation applies around top of viewport
4190 transform.rotate( -angle );
4191 }
4192
4193 const QRectF transformedBounds = transform.map( points ).boundingRect();
4194 left = transformedBounds.left() - 2 * width;
4195 top = transformedBounds.top() - 2 * height;
4196 right = transformedBounds.right() + 2 * width;
4197 bottom = transformedBounds.bottom() + 2 * height;
4198 invertedRotateTransform = transform.inverted();
4199
4200 if ( !applyBrushTransform )
4201 {
4202 left -= transformedBounds.left() - ( width * std::floor( transformedBounds.left() / width ) );
4203 top -= transformedBounds.top() - ( height * std::floor( transformedBounds.top() / height ) );
4204 }
4205 }
4206 else
4207 {
4208 left = boundingRect.left() - 2 * width;
4209 top = boundingRect.top() - 2 * height;
4210 right = boundingRect.right() + 2 * width;
4211 bottom = boundingRect.bottom() + 2 * height;
4212
4213 if ( !applyBrushTransform )
4214 {
4215 left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
4216 top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
4217 }
4218 }
4219
4220 unsigned long seed = mSeed;
4222 {
4223 context.renderContext().expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
4225 }
4226
4227 double maxRandomDeviationX = mRandomDeviationX;
4229 {
4230 context.setOriginalValueVariable( maxRandomDeviationX );
4231 maxRandomDeviationX = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::RandomOffsetX, context.renderContext().expressionContext(), maxRandomDeviationX );
4232 }
4233 const double maxRandomDeviationPixelX = mRandomDeviationXUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationX * width / 100 )
4235
4236 double maxRandomDeviationY = mRandomDeviationY;
4238 {
4239 context.setOriginalValueVariable( maxRandomDeviationY );
4240 maxRandomDeviationY = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::Property::RandomOffsetY, context.renderContext().expressionContext(), maxRandomDeviationY );
4241 }
4242 const double maxRandomDeviationPixelY = mRandomDeviationYUnit == Qgis::RenderUnit::Percentage ? ( maxRandomDeviationY * height / 100 )
4244
4245 std::random_device rd;
4246 std::mt19937 mt( seed == 0 ? rd() : seed );
4247 std::uniform_real_distribution<> uniformDist( 0, 1 );
4248 const bool useRandomShift = !qgsDoubleNear( maxRandomDeviationPixelX, 0 ) || !qgsDoubleNear( maxRandomDeviationPixelY, 0 );
4249
4251 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
4252 int pointNum = 0;
4253 const bool needsExpressionContext = mMarkerSymbol->hasDataDefinedProperties();
4254
4255 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4257
4258 const double prevOpacity = mMarkerSymbol->opacity();
4259 mMarkerSymbol->setOpacity( mMarkerSymbol->opacity() * context.opacity() );
4260
4261 bool alternateColumn = false;
4262 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
4263 for ( double currentX = left; currentX <= right; currentX += width, alternateColumn = !alternateColumn )
4264 {
4265 if ( context.renderContext().renderingStopped() )
4266 break;
4267
4268 if ( needsExpressionContext )
4269 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_column" ), ++currentCol, true ) );
4270
4271 bool alternateRow = false;
4272 const double columnX = currentX + widthOffset;
4273 int currentRow = -3;
4274 for ( double currentY = top; currentY <= bottom; currentY += height, alternateRow = !alternateRow )
4275 {
4276 if ( context.renderContext().renderingStopped() )
4277 break;
4278
4279 double y = currentY + heightOffset;
4280 double x = columnX;
4281 if ( alternateRow )
4282 x += displacementPixelX;
4283
4284 if ( !alternateColumn )
4285 y -= displacementPixelY;
4286
4287 if ( !qgsDoubleNear( angle, 0 ) )
4288 {
4289 double xx = x;
4290 double yy = y;
4291 invertedRotateTransform.map( xx, yy, &x, &y );
4292 }
4293
4294 if ( useRandomShift )
4295 {
4296 x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
4297 y += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelY;
4298 }
4299
4300 if ( needsExpressionContext )
4301 {
4303 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_row" ), ++currentRow, true ) );
4304 }
4305
4306 if ( shapeEngine )
4307 {
4308 bool renderPoint = true;
4309 switch ( clipMode )
4310 {
4312 {
4313 // 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
4314 const QgsRectangle markerRect = QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) );
4315 QgsPoint p( markerRect.center() );
4316 renderPoint = shapeEngine->intersects( &p );
4317 break;
4318 }
4319
4322 {
4323 const QgsGeometry markerBounds = QgsGeometry::fromRect( QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) ) );
4324
4326 renderPoint = shapeEngine->contains( markerBounds.constGet() );
4327 else
4328 renderPoint = shapeEngine->intersects( markerBounds.constGet() );
4329 break;
4330 }
4331
4333 break;
4334 }
4335
4336 if ( !renderPoint )
4337 continue;
4338 }
4339
4340 mMarkerSymbol->renderPoint( QPointF( x, y ), context.feature(), context.renderContext(), -1, useSelectedColor );
4341 }
4342 }
4343
4344 mMarkerSymbol->setOpacity( prevOpacity );
4345
4346 p->restore();
4347
4349}
4350
4352{
4353 QVariantMap map = QgsImageFillSymbolLayer::properties();
4354 map.insert( QStringLiteral( "distance_x" ), QString::number( mDistanceX ) );
4355 map.insert( QStringLiteral( "distance_y" ), QString::number( mDistanceY ) );
4356 map.insert( QStringLiteral( "displacement_x" ), QString::number( mDisplacementX ) );
4357 map.insert( QStringLiteral( "displacement_y" ), QString::number( mDisplacementY ) );
4358 map.insert( QStringLiteral( "offset_x" ), QString::number( mOffsetX ) );
4359 map.insert( QStringLiteral( "offset_y" ), QString::number( mOffsetY ) );
4360 map.insert( QStringLiteral( "distance_x_unit" ), QgsUnitTypes::encodeUnit( mDistanceXUnit ) );
4361 map.insert( QStringLiteral( "distance_y_unit" ), QgsUnitTypes::encodeUnit( mDistanceYUnit ) );
4362 map.insert( QStringLiteral( "displacement_x_unit" ), QgsUnitTypes::encodeUnit( mDisplacementXUnit ) );
4363 map.insert( QStringLiteral( "displacement_y_unit" ), QgsUnitTypes::encodeUnit( mDisplacementYUnit ) );
4364 map.insert( QStringLiteral( "offset_x_unit" ), QgsUnitTypes::encodeUnit( mOffsetXUnit ) );
4365 map.insert( QStringLiteral( "offset_y_unit" ), QgsUnitTypes::encodeUnit( mOffsetYUnit ) );
4366 map.insert( QStringLiteral( "distance_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceXMapUnitScale ) );
4367 map.insert( QStringLiteral( "distance_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceYMapUnitScale ) );
4368 map.insert( QStringLiteral( "displacement_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementXMapUnitScale ) );
4369 map.insert( QStringLiteral( "displacement_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementYMapUnitScale ) );
4370 map.insert( QStringLiteral( "offset_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetXMapUnitScale ) );
4371 map.insert( QStringLiteral( "offset_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetYMapUnitScale ) );
4372 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
4373 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
4374 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeMarkerClipMode( mClipMode ) );
4375 map.insert( QStringLiteral( "random_deviation_x" ), QString::number( mRandomDeviationX ) );
4376 map.insert( QStringLiteral( "random_deviation_y" ), QString::number( mRandomDeviationY ) );
4377 map.insert( QStringLiteral( "random_deviation_x_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationXUnit ) );
4378 map.insert( QStringLiteral( "random_deviation_y_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationYUnit ) );
4379 map.insert( QStringLiteral( "random_deviation_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationXMapUnitScale ) );
4380 map.insert( QStringLiteral( "random_deviation_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationYMapUnitScale ) );
4381 map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
4382 map.insert( QStringLiteral( "angle" ), mAngle );
4383 return map;
4384}
4385
4387{
4389 if ( mMarkerSymbol )
4390 {
4391 clonedLayer->setSubSymbol( mMarkerSymbol->clone() );
4392 }
4393 clonedLayer->setClipMode( mClipMode );
4394 copyDataDefinedProperties( clonedLayer );
4395 copyPaintEffect( clonedLayer );
4396 return clonedLayer;
4397}
4398
4399void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4400{
4401 for ( int symbolLayerIdx = 0; symbolLayerIdx < mMarkerSymbol->symbolLayerCount(); symbolLayerIdx++ )
4402 {
4403 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
4404 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
4405 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
4406 element.appendChild( symbolizerElem );
4407
4408 // <Geometry>
4409 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
4410
4411 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
4412 symbolizerElem.appendChild( fillElem );
4413
4414 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
4415 fillElem.appendChild( graphicFillElem );
4416
4417 QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( symbolLayerIdx );
4418
4419 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
4420
4421 // Export to PNG (TODO: SVG)
4422 bool exportOk { false };
4423 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
4424 {
4425 const QImage image { toTiledPatternImage( ) };
4426 if ( ! image.isNull() )
4427 {
4428 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
4429 graphicFillElem.appendChild( graphicElem );
4430 const QFileInfo info { context.exportFilePath() };
4431 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
4432 pngPath = QgsFileUtils::uniquePath( pngPath );
4433 image.save( pngPath );
4434 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
4435 exportOk = true;
4436 }
4437 }
4438
4439 if ( ! exportOk )
4440 {
4441 // Converts to GeoServer "graphic-margin": symbol size must be subtracted from distance and then divided by 2
4442 const double markerSize { mMarkerSymbol->size() };
4443
4444 // store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
4447 // From: https://docs.geoserver.org/stable/en/user/styling/sld/extensions/margins.html
4448 // top-bottom,right-left (two values, top and bottom sharing the same value)
4449 const QString marginSpec = QString( "%1 %2" ).arg( qgsDoubleToString( ( dy - markerSize ) / 2, 2 ), qgsDoubleToString( ( dx - markerSize ) / 2, 2 ) );
4450
4451 QDomElement graphicMarginElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), marginSpec );
4452 symbolizerElem.appendChild( graphicMarginElem );
4453
4454 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
4455 {
4456 markerLayer->writeSldMarker( doc, graphicFillElem, props );
4457 }
4458 else if ( layer )
4459 {
4460 QString errorMsg = QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() );
4461 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4462 }
4463 else
4464 {
4465 QString errorMsg = QStringLiteral( "Missing point pattern symbol layer. Skip it." );
4466 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4467 }
4468 }
4469 }
4470}
4471
4473{
4474
4475 double angleRads { qDegreesToRadians( mAngle ) };
4476
4477 int distanceXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, {} ) ) };
4478 int distanceYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, {} ) ) };
4479
4480 const int displacementXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementX, mDisplacementXUnit, {} ) ) };
4481 const int displacementYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDisplacementY, mDisplacementYUnit, {} ) ) };
4482
4483 // Consider displacement, double the distance.
4484 if ( displacementXPx != 0 )
4485 {
4486 distanceXPx *= 2;
4487 }
4488
4489 if ( displacementYPx != 0 )
4490 {
4491 distanceYPx *= 2;
4492 }
4493
4494 const QSize size { QgsSymbolLayerUtils::tileSize( distanceXPx, distanceYPx, angleRads ) };
4495
4496 QPixmap pixmap( size );
4497 pixmap.fill( Qt::transparent );
4498 QPainter painter;
4499 painter.begin( &pixmap );
4500 painter.setRenderHint( QPainter::Antialiasing );
4501 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
4505 renderContext.setForceVectorOutput( true );
4506 QgsSymbolRenderContext symbolContext( renderContext, Qgis::RenderUnit::Pixels, 1.0, false, Qgis::SymbolRenderHints() );
4507
4508 std::unique_ptr< QgsPointPatternFillSymbolLayer > layerClone( clone() );
4509
4510 layerClone->setAngle( qRadiansToDegrees( angleRads ) );
4511
4512 // No way we can export a random pattern, disable it.
4513 layerClone->setMaximumRandomDeviationX( 0 );
4514 layerClone->setMaximumRandomDeviationY( 0 );
4515
4516 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
4517 painter.end();
4518 return pixmap.toImage();
4519}
4520
4522{
4523
4524 // input element is PolygonSymbolizer
4525
4526 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
4527 if ( fillElem.isNull() )
4528 return nullptr;
4529
4530 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
4531 if ( graphicFillElem.isNull() )
4532 return nullptr;
4533
4534 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
4535 if ( graphicElem.isNull() )
4536 return nullptr;
4537
4538 std::unique_ptr< QgsSymbolLayer > simpleMarkerSl = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicFillElem );
4539 if ( !simpleMarkerSl )
4540 return nullptr;
4541
4542 QgsSymbolLayerList layers;
4543 layers.append( simpleMarkerSl.release() );
4544
4545 auto marker = std::make_unique< QgsMarkerSymbol >( layers );
4546
4547 // Converts from GeoServer "graphic-margin": symbol size must be added and margin doubled
4548 const double markerSize { marker->size() };
4549
4550 auto pointPatternFillSl = std::make_unique< QgsPointPatternFillSymbolLayer >();
4551 pointPatternFillSl->setSubSymbol( marker.release() );
4552 // This may not be correct in all cases, TODO: check "uom"
4553 pointPatternFillSl->setDistanceXUnit( Qgis::RenderUnit::Pixels );
4554 pointPatternFillSl->setDistanceYUnit( Qgis::RenderUnit::Pixels );
4555
4556 auto distanceParser = [ & ]( const QStringList & values )
4557 {
4558 switch ( values.count( ) )
4559 {
4560 case 1: // top-right-bottom-left (single value for all four margins)
4561 {
4562 bool ok;
4563 const double v { values.at( 0 ).toDouble( &ok ) };
4564 if ( ok )
4565 {
4566 pointPatternFillSl->setDistanceX( v * 2 + markerSize );
4567 pointPatternFillSl->setDistanceY( v * 2 + markerSize );
4568 }
4569 break;
4570 }
4571 case 2: // top-bottom,right-left (two values, top and bottom sharing the same value)
4572 {
4573 bool ok;
4574 const double vX { values.at( 1 ).toDouble( &ok ) };
4575 if ( ok )
4576 {
4577 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4578 }
4579 const double vY { values.at( 0 ).toDouble( &ok ) };
4580 if ( ok )
4581 {
4582 pointPatternFillSl->setDistanceY( vY * 2 + markerSize );
4583 }
4584 break;
4585 }
4586 case 3: // top,right-left,bottom (three values, with right and left sharing the same value)
4587 {
4588 bool ok;
4589 const double vX { values.at( 1 ).toDouble( &ok ) };
4590 if ( ok )
4591 {
4592 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4593 }
4594 const double vYt { values.at( 0 ).toDouble( &ok ) };
4595 if ( ok )
4596 {
4597 const double vYb { values.at( 2 ).toDouble( &ok ) };
4598 if ( ok )
4599 {
4600 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4601 }
4602 }
4603 break;
4604 }
4605 case 4: // top,right,bottom,left (one explicit value per margin)
4606 {
4607 bool ok;
4608 const double vYt { values.at( 0 ).toDouble( &ok ) };
4609 if ( ok )
4610 {
4611 const double vYb { values.at( 2 ).toDouble( &ok ) };
4612 if ( ok )
4613 {
4614 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4615 }
4616 }
4617 const double vXr { values.at( 1 ).toDouble( &ok ) };
4618 if ( ok )
4619 {
4620 const double vXl { values.at( 3 ).toDouble( &ok ) };
4621 if ( ok )
4622 {
4623 pointPatternFillSl->setDistanceX( ( vXr + vXl ) + markerSize );
4624 }
4625 }
4626 break;
4627 }
4628 default:
4629 break;
4630 }
4631 };
4632
4633 // Set distance X and Y from vendor options, or from Size if no vendor options are set
4634 bool distanceFromVendorOption { false };
4635 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
4636 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
4637 {
4638 // Legacy
4639 if ( it.key() == QLatin1String( "distance" ) )
4640 {
4641 distanceParser( it.value().split( ',' ) );
4642 distanceFromVendorOption = true;
4643 }
4644 // GeoServer
4645 else if ( it.key() == QLatin1String( "graphic-margin" ) )
4646 {
4647 distanceParser( it.value().split( ' ' ) );
4648 distanceFromVendorOption = true;
4649 }
4650 }
4651
4652 // Get distances from size
4653 if ( ! distanceFromVendorOption && ! graphicFillElem.elementsByTagName( QStringLiteral( "Size" ) ).isEmpty() )
4654 {
4655 const QDomElement sizeElement { graphicFillElem.elementsByTagName( QStringLiteral( "Size" ) ).at( 0 ).toElement() };
4656 bool ok;
4657 const double size { sizeElement.text().toDouble( &ok ) };
4658 if ( ok )
4659 {
4660 pointPatternFillSl->setDistanceX( size );
4661 pointPatternFillSl->setDistanceY( size );
4662 }
4663 }
4664
4665 return pointPatternFillSl.release();
4666}
4667
4669{
4670 if ( !symbol )
4671 {
4672 return false;
4673 }
4674
4675 if ( symbol->type() == Qgis::SymbolType::Marker )
4676 {
4677 QgsMarkerSymbol *markerSymbol = static_cast<QgsMarkerSymbol *>( symbol );
4678 mMarkerSymbol.reset( markerSymbol );
4679 }
4680 return true;
4681}
4682
4687
4689{
4693 && ( !mMarkerSymbol || !mMarkerSymbol->hasDataDefinedProperties() ) )
4694 {
4695 return;
4696 }
4697
4698 double distanceX = mDistanceX;
4700 {
4703 }
4704 double distanceY = mDistanceY;
4706 {
4709 }
4712 {
4715 }
4718 {
4721 }
4722 double offsetX = mOffsetX;
4724 {
4727 }
4728 double offsetY = mOffsetY;
4730 {
4733 }
4734 applyPattern( context, mBrush, distanceX, distanceY, displacementX, displacementY, offsetX, offsetY );
4735}
4736
4738{
4739 return 0;
4740}
4741
4743{
4744 QSet<QString> attributes = QgsImageFillSymbolLayer::usedAttributes( context );
4745
4746 if ( mMarkerSymbol )
4747 attributes.unite( mMarkerSymbol->usedAttributes( context ) );
4748
4749 return attributes;
4750}
4751
4753{
4755 return true;
4756 if ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
4757 return true;
4758 return false;
4759}
4760
4762{
4763 mColor = c;
4764 if ( mMarkerSymbol )
4765 mMarkerSymbol->setColor( c );
4766}
4767
4769{
4770 return mMarkerSymbol ? mMarkerSymbol->color() : mColor;
4771}
4772
4774
4775
4780
4782
4784{
4785 auto sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4786
4787 if ( properties.contains( QStringLiteral( "point_on_surface" ) ) )
4788 sl->setPointOnSurface( properties[QStringLiteral( "point_on_surface" )].toInt() != 0 );
4789 if ( properties.contains( QStringLiteral( "point_on_all_parts" ) ) )
4790 sl->setPointOnAllParts( properties[QStringLiteral( "point_on_all_parts" )].toInt() != 0 );
4791 if ( properties.contains( QStringLiteral( "clip_points" ) ) )
4792 sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() != 0 );
4793 if ( properties.contains( QStringLiteral( "clip_on_current_part_only" ) ) )
4794 sl->setClipOnCurrentPartOnly( properties[QStringLiteral( "clip_on_current_part_only" )].toInt() != 0 );
4795
4796 sl->restoreOldDataDefinedProperties( properties );
4797
4798 return sl.release();
4799}
4800
4802{
4803 return QStringLiteral( "CentroidFill" );
4804}
4805
4806void QgsCentroidFillSymbolLayer::setColor( const QColor &color )
4807{
4808 mMarker->setColor( color );
4809 mColor = color;
4810}
4811
4813{
4814 return mMarker ? mMarker->color() : mColor;
4815}
4816
4818{
4819 mMarker->setRenderHints( mMarker->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
4820 mMarker->startRender( context.renderContext(), context.fields() );
4821}
4822
4824{
4825 mMarker->stopRender( context.renderContext() );
4826}
4827
4828void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4829{
4830 Part part;
4831 part.exterior = points;
4832 if ( rings )
4833 part.rings = *rings;
4834
4835 if ( mRenderingFeature )
4836 {
4837 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
4838 // until after we've received the final part
4839 mFeatureSymbolOpacity = context.opacity();
4841 mCurrentParts << part;
4842 }
4843 else
4844 {
4845 // not rendering a feature, so we can just render the polygon immediately
4846 const double prevOpacity = mMarker->opacity();
4847 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
4848 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
4849 render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
4850 mMarker->setOpacity( prevOpacity );
4851 }
4852}
4853
4855{
4856 installMasks( context, true );
4857
4858 mRenderingFeature = true;
4859 mCurrentParts.clear();
4860}
4861
4863{
4864 mRenderingFeature = false;
4865
4866 const double prevOpacity = mMarker->opacity();
4867 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
4868
4869 render( context, mCurrentParts, feature, mUseSelectedColor );
4871 mUseSelectedColor = false;
4872 mMarker->setOpacity( prevOpacity );
4873
4874 removeMasks( context, true );
4875}
4876
4877void QgsCentroidFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsCentroidFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
4878{
4881 bool clipPoints = mClipPoints;
4883
4884 // TODO add expressions support
4885
4886 QVector< QgsGeometry > geometryParts;
4887 geometryParts.reserve( parts.size() );
4888 QPainterPath globalPath;
4889
4890 int maxArea = 0;
4891 int maxAreaPartIdx = 0;
4892
4893 for ( int i = 0; i < parts.size(); i++ )
4894 {
4895 const Part part = parts[i];
4896 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
4897
4898 if ( !geom.isNull() && !part.rings.empty() )
4899 {
4900 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
4901
4902 if ( !pointOnAllParts )
4903 {
4904 int area = poly->area();
4905
4906 if ( area > maxArea )
4907 {
4908 maxArea = area;
4909 maxAreaPartIdx = i;
4910 }
4911 }
4912 }
4913
4915 {
4916 globalPath.addPolygon( part.exterior );
4917 for ( const QPolygonF &ring : part.rings )
4918 {
4919 globalPath.addPolygon( ring );
4920 }
4921 }
4922 }
4923
4924 for ( int i = 0; i < parts.size(); i++ )
4925 {
4926 if ( !pointOnAllParts && i != maxAreaPartIdx )
4927 continue;
4928
4929 const Part part = parts[i];
4930
4931 if ( clipPoints )
4932 {
4933 QPainterPath path;
4934
4936 {
4937 path.addPolygon( part.exterior );
4938 for ( const QPolygonF &ring : part.rings )
4939 {
4940 path.addPolygon( ring );
4941 }
4942 }
4943 else
4944 {
4945 path = globalPath;
4946 }
4947
4948 context.painter()->save();
4949 context.painter()->setClipPath( path );
4950 }
4951
4952 QPointF centroid = pointOnSurface ? QgsSymbolLayerUtils::polygonPointOnSurface( part.exterior, &part.rings ) : QgsSymbolLayerUtils::polygonCentroid( part.exterior );
4953
4954 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4956 mMarker->renderPoint( centroid, feature.isValid() ? &feature : nullptr, context, -1, selected );
4957 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
4958
4959 if ( clipPoints )
4960 {
4961 context.painter()->restore();
4962 }
4963 }
4964}
4965
4967{
4968 QVariantMap map;
4969 map[QStringLiteral( "point_on_surface" )] = QString::number( mPointOnSurface );
4970 map[QStringLiteral( "point_on_all_parts" )] = QString::number( mPointOnAllParts );
4971 map[QStringLiteral( "clip_points" )] = QString::number( mClipPoints );
4972 map[QStringLiteral( "clip_on_current_part_only" )] = QString::number( mClipOnCurrentPartOnly );
4973 return map;
4974}
4975
4977{
4978 auto x = std::make_unique< QgsCentroidFillSymbolLayer >();
4979 x->mAngle = mAngle;
4980 x->mColor = mColor;
4981 x->setSubSymbol( mMarker->clone() );
4982 x->setPointOnSurface( mPointOnSurface );
4983 x->setPointOnAllParts( mPointOnAllParts );
4984 x->setClipPoints( mClipPoints );
4985 x->setClipOnCurrentPartOnly( mClipOnCurrentPartOnly );
4986 copyDataDefinedProperties( x.get() );
4987 copyPaintEffect( x.get() );
4988 return x.release();
4989}
4990
4991void QgsCentroidFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4992{
4993 // SLD 1.0 specs says: "if a line, polygon, or raster geometry is
4994 // used with PointSymbolizer, then the semantic is to use the centroid
4995 // of the geometry, or any similar representative point.
4996 mMarker->toSld( doc, element, props );
4997}
4998
5000{
5001 std::unique_ptr< QgsSymbolLayer > l = QgsSymbolLayerUtils::createMarkerLayerFromSld( element );
5002 if ( !l )
5003 return nullptr;
5004
5005 QgsSymbolLayerList layers;
5006 layers.append( l.release() );
5007 auto marker = std::make_unique<QgsMarkerSymbol>( layers );
5008
5009 auto sl = std::make_unique< QgsCentroidFillSymbolLayer >();
5010 sl->setSubSymbol( marker.release() );
5011 sl->setPointOnAllParts( false );
5012 return sl.release();
5013}
5014
5015
5020
5022{
5023 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5024 {
5025 delete symbol;
5026 return false;
5027 }
5028
5029 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5030 mColor = mMarker->color();
5031 return true;
5032}
5033
5035{
5036 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5037
5038 if ( mMarker )
5039 attributes.unite( mMarker->usedAttributes( context ) );
5040
5041 return attributes;
5042}
5043
5045{
5047 return true;
5048 if ( mMarker && mMarker->hasDataDefinedProperties() )
5049 return true;
5050 return false;
5051}
5052
5057
5059{
5060 if ( mMarker )
5061 {
5062 mMarker->setOutputUnit( unit );
5063 }
5064}
5065
5067{
5068 if ( mMarker )
5069 {
5070 return mMarker->outputUnit();
5071 }
5072 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5073}
5074
5076{
5077 if ( mMarker )
5078 {
5079 return mMarker->usesMapUnits();
5080 }
5081 return false;
5082}
5083
5085{
5086 if ( mMarker )
5087 {
5088 mMarker->setMapUnitScale( scale );
5089 }
5090}
5091
5093{
5094 if ( mMarker )
5095 {
5096 return mMarker->mapUnitScale();
5097 }
5098 return QgsMapUnitScale();
5099}
5100
5101
5102
5103
5106 , mImageFilePath( imageFilePath )
5107{
5108 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //disable sub symbol
5110}
5111
5113
5114QgsSymbolLayer *QgsRasterFillSymbolLayer::create( const QVariantMap &properties )
5115{
5117 double alpha = 1.0;
5118 QPointF offset;
5119 double angle = 0.0;
5120 double width = 0.0;
5121
5122 QString imagePath;
5123 if ( properties.contains( QStringLiteral( "imageFile" ) ) )
5124 {
5125 imagePath = properties[QStringLiteral( "imageFile" )].toString();
5126 }
5127 if ( properties.contains( QStringLiteral( "coordinate_mode" ) ) )
5128 {
5129 mode = static_cast< Qgis::SymbolCoordinateReference >( properties[QStringLiteral( "coordinate_mode" )].toInt() );
5130 }
5131 if ( properties.contains( QStringLiteral( "alpha" ) ) )
5132 {
5133 alpha = properties[QStringLiteral( "alpha" )].toDouble();
5134 }
5135 if ( properties.contains( QStringLiteral( "offset" ) ) )
5136 {
5137 offset = QgsSymbolLayerUtils::decodePoint( properties[QStringLiteral( "offset" )].toString() );
5138 }
5139 if ( properties.contains( QStringLiteral( "angle" ) ) )
5140 {
5141 angle = properties[QStringLiteral( "angle" )].toDouble();
5142 }
5143 if ( properties.contains( QStringLiteral( "width" ) ) )
5144 {
5145 width = properties[QStringLiteral( "width" )].toDouble();
5146 }
5147 auto symbolLayer = std::make_unique< QgsRasterFillSymbolLayer >( imagePath );
5148 symbolLayer->setCoordinateMode( mode );
5149 symbolLayer->setOpacity( alpha );
5150 symbolLayer->setOffset( offset );
5151 symbolLayer->setAngle( angle );
5152 symbolLayer->setWidth( width );
5153 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
5154 {
5155 symbolLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
5156 }
5157 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
5158 {
5159 symbolLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
5160 }
5161 if ( properties.contains( QStringLiteral( "width_unit" ) ) )
5162 {
5163 symbolLayer->setSizeUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "width_unit" )].toString() ) );
5164 }
5165 if ( properties.contains( QStringLiteral( "width_map_unit_scale" ) ) )
5166 {
5167 symbolLayer->setSizeMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "width_map_unit_scale" )].toString() ) );
5168 }
5169
5170 if ( properties.contains( QStringLiteral( "height" ) ) )
5171 {
5172 symbolLayer->setHeight( properties[QStringLiteral( "height" )].toDouble() );
5173 }
5174
5175 symbolLayer->restoreOldDataDefinedProperties( properties );
5176
5177 return symbolLayer.release();
5178}
5179
5181{
5182 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
5183 if ( fillElem.isNull() )
5184 return nullptr;
5185
5186 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
5187 if ( graphicFillElem.isNull() )
5188 return nullptr;
5189
5190 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
5191 if ( graphicElem.isNull() )
5192 return nullptr;
5193
5194 QString path, mimeType;
5195 double size;
5196 QColor fillColor;
5197
5198 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
5199 return nullptr;
5200
5201 // Try to correct the path, this is a wild guess but we have not access to the SLD path here.
5202 if ( ! QFile::exists( path ) )
5203 {
5204 path = QgsProject::instance()->pathResolver().readPath( path ); // skip-keyword-check
5205 }
5206
5207 auto sl = std::make_unique< QgsRasterFillSymbolLayer>( path );
5208
5209 return sl.release();
5210}
5211
5212void QgsRasterFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
5213{
5214 QVariantMap::iterator it = properties.find( QStringLiteral( "imageFile" ) );
5215 if ( it != properties.end() )
5216 {
5217 if ( saving )
5218 it.value() = pathResolver.writePath( it.value().toString() );
5219 else
5220 it.value() = pathResolver.readPath( it.value().toString() );
5221 }
5222}
5223
5225{
5226 Q_UNUSED( symbol )
5227 return true;
5228}
5229
5231{
5232 return QStringLiteral( "RasterFill" );
5233}
5234
5239
5240void QgsRasterFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5241{
5242 QPainter *p = context.renderContext().painter();
5243 if ( !p )
5244 {
5245 return;
5246 }
5247
5248 QPointF offset = mOffset;
5250 {
5253 bool ok = false;
5254 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
5255 if ( ok )
5256 offset = res;
5257 }
5258 if ( !offset.isNull() )
5259 {
5260 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
5261 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
5262 p->translate( offset );
5263 }
5264 if ( mCoordinateMode == Qgis::SymbolCoordinateReference::Feature )
5265 {
5266 QRectF boundingRect = points.boundingRect();
5267 mBrush.setTransform( mBrush.transform().translate( boundingRect.left() - mBrush.transform().dx(),
5268 boundingRect.top() - mBrush.transform().dy() ) );
5269 }
5270
5271 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
5272 if ( !offset.isNull() )
5273 {
5274 p->translate( -offset );
5275 }
5276}
5277
5279{
5280 applyPattern( mBrush, mImageFilePath, mWidth, mHeight, mOpacity * context.opacity(), context );
5281}
5282
5284{
5285 Q_UNUSED( context )
5286}
5287
5289{
5290 QVariantMap map;
5291 map[QStringLiteral( "imageFile" )] = mImageFilePath;
5292 map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
5293 map[QStringLiteral( "alpha" )] = QString::number( mOpacity );
5294 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
5295 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
5296 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
5297 map[QStringLiteral( "angle" )] = QString::number( mAngle );
5298
5299 map[QStringLiteral( "width" )] = QString::number( mWidth );
5300 map[QStringLiteral( "height" )] = QString::number( mHeight );
5301 map[QStringLiteral( "width_unit" )] = QgsUnitTypes::encodeUnit( mSizeUnit );
5302 map[QStringLiteral( "width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mSizeMapUnitScale );
5303
5304 return map;
5305}
5306
5308{
5309 auto sl = std::make_unique< QgsRasterFillSymbolLayer >( mImageFilePath );
5310 sl->setCoordinateMode( mCoordinateMode );
5311 sl->setOpacity( mOpacity );
5312 sl->setOffset( mOffset );
5313 sl->setOffsetUnit( mOffsetUnit );
5314 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
5315 sl->setAngle( mAngle );
5316 sl->setWidth( mWidth );
5317 sl->setHeight( mHeight );
5318 sl->setSizeUnit( mSizeUnit );
5319 sl->setSizeMapUnitScale( mSizeMapUnitScale );
5320
5321 copyDataDefinedProperties( sl.get() );
5322 copyPaintEffect( sl.get() );
5323 return sl.release();
5324}
5325
5327{
5328 return context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
5329}
5330
5332{
5333 return mSizeUnit == Qgis::RenderUnit::MapUnits || mSizeUnit == Qgis::RenderUnit::MetersInMapUnits
5334 || mOffsetUnit == Qgis::RenderUnit::MapUnits || mOffsetUnit == Qgis::RenderUnit::MetersInMapUnits;
5335}
5336
5338{
5339 return QColor();
5340}
5341
5343{
5345 mOffsetUnit = unit;
5346 mSizeUnit = unit;
5347}
5348
5349void QgsRasterFillSymbolLayer::setImageFilePath( const QString &imagePath )
5350{
5351 mImageFilePath = imagePath;
5352}
5353
5355{
5356 mCoordinateMode = mode;
5357}
5358
5359void QgsRasterFillSymbolLayer::setOpacity( const double opacity )
5360{
5361 mOpacity = opacity;
5362}
5363
5365{
5366 if ( !dataDefinedProperties().hasActiveProperties() )
5367 return; // shortcut
5368
5369 const bool hasWidthExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Width );
5370 const bool hasHeightExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Height );
5371 const bool hasFileExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::File );
5372 const bool hasOpacityExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Opacity );
5373 const bool hasAngleExpression = mDataDefinedProperties.isActive( QgsSymbolLayer::Property::Angle );
5374
5375 if ( !hasWidthExpression && !hasHeightExpression && !hasAngleExpression && !hasOpacityExpression && !hasFileExpression )
5376 {
5377 return; //no data defined settings
5378 }
5379
5380 bool ok;
5381 if ( hasAngleExpression )
5382 {
5385 if ( ok )
5386 mNextAngle = nextAngle;
5387 }
5388
5389 if ( !hasWidthExpression && !hasHeightExpression && !hasOpacityExpression && !hasFileExpression )
5390 {
5391 return; //nothing further to do
5392 }
5393
5394 double width = mWidth;
5395 if ( hasWidthExpression )
5396 {
5397 context.setOriginalValueVariable( mWidth );
5399 }
5400 double height = mHeight;
5401 if ( hasHeightExpression )
5402 {
5403 context.setOriginalValueVariable( mHeight );
5405 }
5406 double opacity = mOpacity;
5407 if ( hasOpacityExpression )
5408 {
5409 context.setOriginalValueVariable( mOpacity );
5411 }
5412 QString file = mImageFilePath;
5413 if ( hasFileExpression )
5414 {
5415 context.setOriginalValueVariable( mImageFilePath );
5417 }
5418 applyPattern( mBrush, file, width, height, opacity, context );
5419}
5420
5425
5426void QgsRasterFillSymbolLayer::applyPattern( QBrush &brush, const QString &imageFilePath, const double width, const double height, const double alpha, const QgsSymbolRenderContext &context )
5427{
5428 double imageWidth = 0;
5429 double imageHeight = 0;
5430
5431 // defer retrieval of original size till we actually NEED it
5432 QSize originalSize;
5433
5434 if ( width > 0 )
5435 {
5436 if ( mSizeUnit != Qgis::RenderUnit::Percentage )
5437 {
5438 imageWidth = context.renderContext().convertToPainterUnits( width, mSizeUnit, mSizeMapUnitScale );
5439 }
5440 else
5441 {
5442 // RenderPercentage Unit Type takes original image size
5444 if ( originalSize.isEmpty() )
5445 return;
5446
5447 imageWidth = ( width * originalSize.width() ) / 100.0;
5448
5449 // don't render symbols with size below one or above 10,000 pixels
5450 if ( static_cast< int >( imageWidth ) < 1 || 10000.0 < imageWidth )
5451 return;
5452 }
5453 }
5454 if ( height > 0 )
5455 {
5456 if ( mSizeUnit != Qgis::RenderUnit::Percentage )
5457 {
5458 imageHeight = context.renderContext().convertToPainterUnits( height, mSizeUnit, mSizeMapUnitScale );
5459 }
5460 else
5461 {
5462 // RenderPercentage Unit Type takes original image size
5463 if ( !originalSize.isValid() )
5465
5466 if ( originalSize.isEmpty() )
5467 return;
5468
5469 imageHeight = ( height * originalSize.height() ) / 100.0;
5470
5471 // don't render symbols with size below one or above 10,000 pixels
5472 if ( static_cast< int >( imageHeight ) < 1 || 10000.0 < imageHeight )
5473 return;
5474 }
5475 }
5476
5477 if ( width == 0 && imageHeight > 0 )
5478 {
5479 if ( !originalSize.isValid() )
5481
5482 imageWidth = imageHeight * originalSize.width() / originalSize.height();
5483 }
5484 else if ( height == 0 && imageWidth > 0 )
5485 {
5486 if ( !originalSize.isValid() )
5488
5489 imageHeight = imageWidth * originalSize.height() / originalSize.width();
5490 }
5491 if ( imageWidth == 0 || imageHeight == 0 )
5492 {
5493 if ( !originalSize.isValid() )
5495
5496 imageWidth = originalSize.width();
5497 imageHeight = originalSize.height();
5498 }
5499
5500 bool cached;
5501 QImage img = QgsApplication::imageCache()->pathAsImage( imageFilePath, QSize( std::round< int >( imageWidth ), std::round< int >( imageHeight ) ), false, alpha, cached, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
5502 if ( img.isNull() )
5503 return;
5504
5505 brush.setTextureImage( img );
5506}
5507
5508
5509//
5510// QgsRandomMarkerFillSymbolLayer
5511//
5512
5513QgsRandomMarkerFillSymbolLayer::QgsRandomMarkerFillSymbolLayer( int pointCount, Qgis::PointCountMethod method, double densityArea, unsigned long seed )
5514 : mCountMethod( method )
5515 , mPointCount( pointCount )
5516 , mDensityArea( densityArea )
5517 , mSeed( seed )
5518{
5520}
5521
5523
5525{
5526 const Qgis::PointCountMethod countMethod = static_cast< Qgis::PointCountMethod >( properties.value( QStringLiteral( "count_method" ), QStringLiteral( "0" ) ).toInt() );
5527 const int pointCount = properties.value( QStringLiteral( "point_count" ), QStringLiteral( "10" ) ).toInt();
5528 const double densityArea = properties.value( QStringLiteral( "density_area" ), QStringLiteral( "250.0" ) ).toDouble();
5529
5530 unsigned long seed = 0;
5531 if ( properties.contains( QStringLiteral( "seed" ) ) )
5532 seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
5533 else
5534 {
5535 // if we a creating a new random marker fill from scratch, we default to a random seed
5536 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
5537 std::random_device rd;
5538 std::mt19937 mt( seed == 0 ? rd() : seed );
5539 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
5540 seed = uniformDist( mt );
5541 }
5542
5543 auto sl = std::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, countMethod, densityArea, seed );
5544
5545 if ( properties.contains( QStringLiteral( "density_area_unit" ) ) )
5546 sl->setDensityAreaUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "density_area_unit" )].toString() ) );
5547 if ( properties.contains( QStringLiteral( "density_area_unit_scale" ) ) )
5548 sl->setDensityAreaUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "density_area_unit_scale" )].toString() ) );
5549
5550 if ( properties.contains( QStringLiteral( "clip_points" ) ) )
5551 {
5552 sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() );
5553 }
5554
5555 return sl.release();
5556}
5557
5559{
5560 return QStringLiteral( "RandomMarkerFill" );
5561}
5562
5564{
5565 mMarker->setColor( color );
5566 mColor = color;
5567}
5568
5570{
5571 return mMarker ? mMarker->color() : mColor;
5572}
5573
5575{
5576 mMarker->setRenderHints( mMarker->renderHints() | Qgis::SymbolRenderHint::IsSymbolLayerSubSymbol );
5577 mMarker->startRender( context.renderContext(), context.fields() );
5578}
5579
5581{
5582 mMarker->stopRender( context.renderContext() );
5583}
5584
5585void QgsRandomMarkerFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
5586{
5587 Part part;
5588 part.exterior = points;
5589 if ( rings )
5590 part.rings = *rings;
5591
5592 if ( mRenderingFeature )
5593 {
5594 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
5595 // until after we've received the final part
5596 mFeatureSymbolOpacity = context.opacity();
5597 mCurrentParts << part;
5598 }
5599 else
5600 {
5601 // not rendering a feature, so we can just render the polygon immediately
5602 const double prevOpacity = mMarker->opacity();
5603 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
5604 const bool useSelectedColor = shouldRenderUsingSelectionColor( context );
5605 render( context.renderContext(), QVector< Part>() << part, context.feature() ? *context.feature() : QgsFeature(), useSelectedColor );
5606 mMarker->setOpacity( prevOpacity );
5607 }
5608}
5609
5610void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QVector<QgsRandomMarkerFillSymbolLayer::Part> &parts, const QgsFeature &feature, bool selected )
5611{
5612 bool clipPoints = mClipPoints;
5614 {
5617 }
5618
5619 QVector< QgsGeometry > geometryParts;
5620 geometryParts.reserve( parts.size() );
5621 QPainterPath path;
5622
5623 for ( const Part &part : parts )
5624 {
5625 QgsGeometry geom = QgsGeometry::fromQPolygonF( part.exterior );
5626 if ( !geom.isNull() && !part.rings.empty() )
5627 {
5628 QgsPolygon *poly = qgsgeometry_cast< QgsPolygon * >( geom.get() );
5629 for ( const QPolygonF &ring : part.rings )
5630 {
5631 std::unique_ptr< QgsLineString > fromRing = QgsLineString::fromQPolygonF( ring );
5632 poly->addInteriorRing( fromRing.release() );
5633 }
5634 }
5635 if ( !geom.isGeosValid() )
5636 {
5637 geom = geom.buffer( 0, 0 );
5638 }
5639 geometryParts << geom;
5640
5641 if ( clipPoints )
5642 {
5643 path.addPolygon( part.exterior );
5644 for ( const QPolygonF &ring : part.rings )
5645 {
5646 path.addPolygon( ring );
5647 }
5648 }
5649 }
5650
5651 const QgsGeometry geom = geometryParts.count() != 1 ? QgsGeometry::unaryUnion( geometryParts ) : geometryParts.at( 0 );
5652
5653 if ( clipPoints )
5654 {
5655 context.painter()->save();
5656 context.painter()->setClipPath( path );
5657 }
5658
5659
5660 int count = mPointCount;
5662 {
5665 }
5666
5667 switch ( mCountMethod )
5668 {
5670 {
5671 double densityArea = mDensityArea;
5673 {
5676 }
5677 densityArea = context.convertToPainterUnits( std::sqrt( densityArea ), mDensityAreaUnit, mDensityAreaUnitScale );
5678 densityArea = std::pow( densityArea, 2 );
5679 count = std::max( 0.0, std::ceil( count * ( geom.area() / densityArea ) ) );
5680 break;
5681 }
5683 break;
5684 }
5685
5686 unsigned long seed = mSeed;
5688 {
5689 context.expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
5691 }
5692
5693 QVector< QgsPointXY > randomPoints = geom.randomPointsInPolygon( count, seed );
5694#if 0
5695 // in some cases rendering from top to bottom is nice (e.g. randomised tree markers), but in other cases it's not wanted..
5696 // TODO consider exposing this as an option
5697 std::sort( randomPoints.begin(), randomPoints.end(), []( const QgsPointXY & a, const QgsPointXY & b )->bool
5698 {
5699 return a.y() < b.y();
5700 } );
5701#endif
5703 QgsExpressionContextScopePopper scopePopper( context.expressionContext(), scope );
5704 int pointNum = 0;
5705 const bool needsExpressionContext = mMarker->hasDataDefinedProperties();
5706
5707 const bool prevIsSubsymbol = context.flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
5709
5710 for ( const QgsPointXY &p : std::as_const( randomPoints ) )
5711 {
5712 if ( needsExpressionContext )
5714 mMarker->renderPoint( QPointF( p.x(), p.y() ), feature.isValid() ? &feature : nullptr, context, -1, selected );
5715 }
5716
5717 context.setFlag( Qgis::RenderContextFlag::RenderingSubSymbol, prevIsSubsymbol );
5718
5719 if ( clipPoints )
5720 {
5721 context.painter()->restore();
5722 }
5723}
5724
5726{
5727 QVariantMap map;
5728 map.insert( QStringLiteral( "count_method" ), QString::number( static_cast< int >( mCountMethod ) ) );
5729 map.insert( QStringLiteral( "point_count" ), QString::number( mPointCount ) );
5730 map.insert( QStringLiteral( "density_area" ), QString::number( mDensityArea ) );
5731 map.insert( QStringLiteral( "density_area_unit" ), QgsUnitTypes::encodeUnit( mDensityAreaUnit ) );
5732 map.insert( QStringLiteral( "density_area_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDensityAreaUnitScale ) );
5733 map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
5734 map.insert( QStringLiteral( "clip_points" ), QString::number( mClipPoints ) );
5735 return map;
5736}
5737
5739{
5740 auto res = std::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mCountMethod, mDensityArea, mSeed );
5741 res->mAngle = mAngle;
5742 res->mColor = mColor;
5743 res->setDensityAreaUnit( mDensityAreaUnit );
5744 res->setDensityAreaUnitScale( mDensityAreaUnitScale );
5745 res->mClipPoints = mClipPoints;
5746 res->setSubSymbol( mMarker->clone() );
5747 copyDataDefinedProperties( res.get() );
5748 copyPaintEffect( res.get() );
5749 return res.release();
5750}
5751
5756
5758{
5759 return mMarker.get();
5760}
5761
5763{
5764 if ( !symbol || symbol->type() != Qgis::SymbolType::Marker )
5765 {
5766 delete symbol;
5767 return false;
5768 }
5769
5770 mMarker.reset( static_cast<QgsMarkerSymbol *>( symbol ) );
5771 mColor = mMarker->color();
5772 return true;
5773}
5774
5776{
5777 QSet<QString> attributes = QgsFillSymbolLayer::usedAttributes( context );
5778
5779 if ( mMarker )
5780 attributes.unite( mMarker->usedAttributes( context ) );
5781
5782 return attributes;
5783}
5784
5786{
5788 return true;
5789 if ( mMarker && mMarker->hasDataDefinedProperties() )
5790 return true;
5791 return false;
5792}
5793
5795{
5796 return mPointCount;
5797}
5798
5800{
5801 mPointCount = pointCount;
5802}
5803
5805{
5806 return mSeed;
5807}
5808
5810{
5811 mSeed = seed;
5812}
5813
5815{
5816 return mClipPoints;
5817}
5818
5820{
5821 mClipPoints = clipPoints;
5822}
5823
5825{
5826 return mCountMethod;
5827}
5828
5830{
5831 mCountMethod = method;
5832}
5833
5835{
5836 return mDensityArea;
5837}
5838
5840{
5841 mDensityArea = area;
5842}
5843
5845{
5846 installMasks( context, true );
5847
5848 mRenderingFeature = true;
5849 mCurrentParts.clear();
5850}
5851
5853{
5854 mRenderingFeature = false;
5855
5856 const double prevOpacity = mMarker->opacity();
5857 mMarker->setOpacity( mMarker->opacity() * mFeatureSymbolOpacity );
5858
5859 render( context, mCurrentParts, feature, false );
5860
5861 mFeatureSymbolOpacity = 1;
5862 mMarker->setOpacity( prevOpacity );
5863
5864 removeMasks( context, true );
5865}
5866
5867
5869{
5870 mDensityAreaUnit = unit;
5871 if ( mMarker )
5872 {
5873 mMarker->setOutputUnit( unit );
5874 }
5875}
5876
5878{
5879 if ( mMarker )
5880 {
5881 return mMarker->outputUnit();
5882 }
5883 return Qgis::RenderUnit::Unknown; //mOutputUnit;
5884}
5885
5887{
5888 if ( mMarker )
5889 {
5890 return mMarker->usesMapUnits();
5891 }
5892 return false;
5893}
5894
5896{
5897 if ( mMarker )
5898 {
5899 mMarker->setMapUnitScale( scale );
5900 }
5901}
5902
5904{
5905 if ( mMarker )
5906 {
5907 return mMarker->mapUnitScale();
5908 }
5909 return QgsMapUnitScale();
5910}
MarkerClipMode
Marker clipping modes.
Definition qgis.h:3143
@ 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:3157
@ 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:3072
@ 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:3116
@ 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:3131
@ 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:851
RenderUnit
Rendering size units.
Definition qgis.h:5014
@ 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:3086
@ 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:3101
@ 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 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].
std::unique_ptr< QgsColorRamp > mGradientRamp
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 fill symbol layers which fill polygons with a repeated image.
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, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
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)
Represents a 2D point.
Definition qgspointxy.h:60
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
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 fill symbol layer which fills polygons 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 fill symbol layer which fills polygons 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.
A fill symbol layer which applies a gradient from the outer edges of a symbol to the inside.
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)
Holds SLD export options and other information related to SLD export of a QGIS layer style.
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 std::unique_ptr< QgsSymbolLayer > createMarkerLayerFromSld(QDomElement &element)
Creates a new marker layer from a SLD DOM element.
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 std::unique_ptr< QgsSymbolLayer > createLineLayerFromSld(QDomElement &element)
Creates a new line layer from a SLD DOM element.
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)
Extracts properties from an SLD marker definition.
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 QString encodePenJoinStyle(Qt::PenJoinStyle style)
static QgsStringMap getVendorOptionList(QDomElement &element)
static QPointF decodePoint(const QString &string)
Decodes a QSizeF from a string.
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.
Abstract base class for symbol layers.
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.
void restoreOldDataDefinedProperties(const QVariantMap &stringMap)
Restores older data defined properties from string map.
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.
Encapsulates the context in which a symbol is being rendered.
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:6204
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6287
QMap< QString, QString > QgsStringMap
Definition qgis.h:6781
#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.