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