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