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