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