QGIS API Documentation 3.29.0-Master (19d7edcfed)
qgsfillsymbollayer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsfillsymbollayer.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsfileutils.h"
17#include "qgsfillsymbollayer.h"
18#include "qgslinesymbollayer.h"
20#include "qgssldexportcontext.h"
21#include "qgssymbollayerutils.h"
22#include "qgsdxfexport.h"
23#include "qgsexpression.h"
24#include "qgsgeometry.h"
26#include "qgsimagecache.h"
27#include "qgsrendercontext.h"
28#include "qgsproject.h"
29#include "qgssvgcache.h"
30#include "qgslogger.h"
31#include "qgscolorramp.h"
32#include "qgscolorrampimpl.h"
33#include "qgsunittypes.h"
34#include "qgsmessagelog.h"
35#include "qgsapplication.h"
36#include "qgsimageoperation.h"
37#include "qgspolygon.h"
38#include "qgslinestring.h"
40#include "qgssymbol.h"
41#include "qgsmarkersymbol.h"
42#include "qgslinesymbol.h"
43#include "qgsfeedback.h"
44#include "qgsgeometryengine.h"
45
46#include <QPainter>
47#include <QFile>
48#include <QSvgRenderer>
49#include <QDomDocument>
50#include <QDomElement>
51#include <QtMath>
52#include <random>
53
54#ifndef QT_NO_PRINTER
55#include <QPrinter>
56#endif
57
58QgsSimpleFillSymbolLayer::QgsSimpleFillSymbolLayer( const QColor &color, Qt::BrushStyle style, const QColor &strokeColor, Qt::PenStyle strokeStyle, double strokeWidth,
59 Qt::PenJoinStyle penJoinStyle )
60 : mBrushStyle( style )
61 , mStrokeColor( strokeColor )
62 , mStrokeStyle( strokeStyle )
63 , mStrokeWidth( strokeWidth )
64 , mPenJoinStyle( penJoinStyle )
65{
66 mColor = color;
67}
68
70
72{
73 mStrokeWidthUnit = unit;
74 mOffsetUnit = unit;
75}
76
78{
80 if ( mOffsetUnit != unit )
81 {
83 }
84 return unit;
85}
86
88{
91}
92
94{
96 mOffsetMapUnitScale = scale;
97}
98
100{
102 {
104 }
105 return QgsMapUnitScale();
106}
107
108void QgsSimpleFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QBrush &brush, QPen &pen, QPen &selPen )
109{
110 if ( !dataDefinedProperties().hasActiveProperties() )
111 return; // shortcut
112
113 bool ok;
114
116 {
119 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
120 brush.setColor( fillColor );
121 }
123 {
126 if ( !QgsVariantUtils::isNull( exprVal ) )
127 brush.setStyle( QgsSymbolLayerUtils::decodeBrushStyle( exprVal.toString() ) );
128 }
130 {
133 penColor.setAlphaF( context.opacity() * penColor.alphaF() );
134 pen.setColor( penColor );
135 }
137 {
140 if ( !QgsVariantUtils::isNull( exprVal ) )
141 {
142 double width = exprVal.toDouble( &ok );
143 if ( ok )
144 {
146 pen.setWidthF( width );
147 selPen.setWidthF( width );
148 }
149 }
150 }
152 {
155 if ( ok )
156 {
157 pen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
158 selPen.setStyle( QgsSymbolLayerUtils::decodePenStyle( style ) );
159 }
160 }
162 {
165 if ( ok )
166 {
167 pen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
168 selPen.setJoinStyle( QgsSymbolLayerUtils::decodePenJoinStyle( style ) );
169 }
170 }
171}
172
173
175{
177 Qt::BrushStyle style = DEFAULT_SIMPLEFILL_STYLE;
182 QPointF offset;
183
184 if ( props.contains( QStringLiteral( "color" ) ) )
185 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
186 if ( props.contains( QStringLiteral( "style" ) ) )
187 style = QgsSymbolLayerUtils::decodeBrushStyle( props[QStringLiteral( "style" )].toString() );
188 if ( props.contains( QStringLiteral( "color_border" ) ) )
189 {
190 //pre 2.5 projects used "color_border"
191 strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color_border" )].toString() );
192 }
193 else if ( props.contains( QStringLiteral( "outline_color" ) ) )
194 {
195 strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "outline_color" )].toString() );
196 }
197 else if ( props.contains( QStringLiteral( "line_color" ) ) )
198 {
199 strokeColor = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "line_color" )].toString() );
200 }
201
202 if ( props.contains( QStringLiteral( "style_border" ) ) )
203 {
204 //pre 2.5 projects used "style_border"
205 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "style_border" )].toString() );
206 }
207 else if ( props.contains( QStringLiteral( "outline_style" ) ) )
208 {
209 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "outline_style" )].toString() );
210 }
211 else if ( props.contains( QStringLiteral( "line_style" ) ) )
212 {
213 strokeStyle = QgsSymbolLayerUtils::decodePenStyle( props[QStringLiteral( "line_style" )].toString() );
214 }
215 if ( props.contains( QStringLiteral( "width_border" ) ) )
216 {
217 //pre 2.5 projects used "width_border"
218 strokeWidth = props[QStringLiteral( "width_border" )].toDouble();
219 }
220 else if ( props.contains( QStringLiteral( "outline_width" ) ) )
221 {
222 strokeWidth = props[QStringLiteral( "outline_width" )].toDouble();
223 }
224 else if ( props.contains( QStringLiteral( "line_width" ) ) )
225 {
226 strokeWidth = props[QStringLiteral( "line_width" )].toDouble();
227 }
228 if ( props.contains( QStringLiteral( "offset" ) ) )
229 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
230 if ( props.contains( QStringLiteral( "joinstyle" ) ) )
231 penJoinStyle = QgsSymbolLayerUtils::decodePenJoinStyle( props[QStringLiteral( "joinstyle" )].toString() );
232
233 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, style, strokeColor, strokeStyle, strokeWidth, penJoinStyle );
234 sl->setOffset( offset );
235 if ( props.contains( QStringLiteral( "border_width_unit" ) ) )
236 {
237 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "border_width_unit" )].toString() ) );
238 }
239 else if ( props.contains( QStringLiteral( "outline_width_unit" ) ) )
240 {
241 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "outline_width_unit" )].toString() ) );
242 }
243 else if ( props.contains( QStringLiteral( "line_width_unit" ) ) )
244 {
245 sl->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "line_width_unit" )].toString() ) );
246 }
247 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
248 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
249
250 if ( props.contains( QStringLiteral( "border_width_map_unit_scale" ) ) )
251 sl->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "border_width_map_unit_scale" )].toString() ) );
252 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
253 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
254
255 sl->restoreOldDataDefinedProperties( props );
256
257 return sl.release();
258}
259
260
262{
263 return QStringLiteral( "SimpleFill" );
264}
265
267{
268 QColor fillColor = mColor;
269 fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
270 mBrush = QBrush( fillColor, mBrushStyle );
271
272 QColor selColor = context.renderContext().selectionColor();
273 QColor selPenColor = selColor == mColor ? selColor : mStrokeColor;
274 if ( ! SELECTION_IS_OPAQUE )
275 selColor.setAlphaF( context.opacity() );
276 mSelBrush = QBrush( selColor );
277 // N.B. unless a "selection line color" is implemented in addition to the "selection color" option
278 // this would mean symbols with "no fill" look the same whether or not they are selected
279 if ( SELECT_FILL_STYLE )
280 mSelBrush.setStyle( mBrushStyle );
281
282 QColor strokeColor = mStrokeColor;
283 strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
284 mPen = QPen( strokeColor );
285 mSelPen = QPen( selPenColor );
286 mPen.setStyle( mStrokeStyle );
288 mPen.setJoinStyle( mPenJoinStyle );
289}
290
292{
293 Q_UNUSED( context )
294}
295
296void QgsSimpleFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
297{
298 QPainter *p = context.renderContext().painter();
299 if ( !p )
300 {
301 return;
302 }
303
304 QColor fillColor = mColor;
305 fillColor.setAlphaF( context.opacity() * mColor.alphaF() );
306 mBrush.setColor( fillColor );
307 QColor strokeColor = mStrokeColor;
308 strokeColor.setAlphaF( context.opacity() * mStrokeColor.alphaF() );
309 mPen.setColor( strokeColor );
310
311 applyDataDefinedSymbology( context, mBrush, mPen, mSelPen );
312
313 QPointF offset = mOffset;
314
316 {
318 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
319 bool ok = false;
320 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
321 if ( ok )
322 offset = res;
323 }
324
325 if ( !offset.isNull() )
326 {
329 p->translate( offset );
330 }
331
332#ifndef QT_NO_PRINTER
333 if ( mBrush.style() == Qt::SolidPattern || mBrush.style() == Qt::NoBrush || !dynamic_cast<QPrinter *>( p->device() ) )
334#endif
335 {
336 p->setPen( context.selected() ? mSelPen : mPen );
337 p->setBrush( context.selected() ? mSelBrush : mBrush );
338 _renderPolygon( p, points, rings, context );
339 }
340#ifndef QT_NO_PRINTER
341 else
342 {
343 // workaround upstream issue https://github.com/qgis/QGIS/issues/36580
344 // when a non-solid brush is set with opacity, the opacity incorrectly applies to the pen
345 // when exporting to PDF/print devices
346 p->setBrush( context.selected() ? mSelBrush : mBrush );
347 p->setPen( Qt::NoPen );
348 _renderPolygon( p, points, rings, context );
349
350 p->setPen( context.selected() ? mSelPen : mPen );
351 p->setBrush( Qt::NoBrush );
352 _renderPolygon( p, points, rings, context );
353 }
354#endif
355
356 if ( !offset.isNull() )
357 {
358 p->translate( -offset );
359 }
360}
361
363{
364 QVariantMap map;
365 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
366 map[QStringLiteral( "style" )] = QgsSymbolLayerUtils::encodeBrushStyle( mBrushStyle );
367 map[QStringLiteral( "outline_color" )] = QgsSymbolLayerUtils::encodeColor( mStrokeColor );
368 map[QStringLiteral( "outline_style" )] = QgsSymbolLayerUtils::encodePenStyle( mStrokeStyle );
369 map[QStringLiteral( "outline_width" )] = QString::number( mStrokeWidth );
370 map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( mStrokeWidthUnit );
371 map[QStringLiteral( "border_width_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale );
372 map[QStringLiteral( "joinstyle" )] = QgsSymbolLayerUtils::encodePenJoinStyle( mPenJoinStyle );
373 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
374 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
375 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
376 return map;
377}
378
380{
381 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( mColor, mBrushStyle, mStrokeColor, mStrokeStyle, mStrokeWidth, mPenJoinStyle );
382 sl->setOffset( mOffset );
383 sl->setOffsetUnit( mOffsetUnit );
384 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
385 sl->setStrokeWidthUnit( mStrokeWidthUnit );
386 sl->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
387 copyDataDefinedProperties( sl.get() );
388 copyPaintEffect( sl.get() );
389 return sl.release();
390}
391
392void QgsSimpleFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
393{
394 if ( mBrushStyle == Qt::NoBrush && mStrokeStyle == Qt::NoPen )
395 return;
396
397 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
398 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
399 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
400 element.appendChild( symbolizerElem );
401
402 // <Geometry>
403 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
404
405 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
406
407
408 // Export to PNG
409 bool exportOk { false };
410 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) && mBrush.style() != Qt::NoBrush )
411 {
412 const QImage image { toTiledPatternImage( ) };
413 if ( ! image.isNull() )
414 {
415 // <Fill>
416 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
417 symbolizerElem.appendChild( fillElem );
418 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
419 fillElem.appendChild( graphicFillElem );
420 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
421 graphicFillElem.appendChild( graphicElem );
422 QgsRenderContext renderContext;
423 const QFileInfo info { context.exportFilePath() };
424 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
425 pngPath = QgsFileUtils::uniquePath( pngPath );
426 image.save( pngPath );
427 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
428 exportOk = true;
429 }
430 }
431
432 if ( ! exportOk )
433 {
434 if ( mBrushStyle != Qt::NoBrush )
435 {
436
437 QColor color { mColor };
438
439 // Apply alpha from symbol
440 bool ok;
441 const double alpha { props.value( QStringLiteral( "alpha" ), QVariant() ).toDouble( &ok ) };
442 if ( ok )
443 {
444 color.setAlphaF( color.alphaF() * alpha );
445 }
446 // <Fill>
447 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
448 symbolizerElem.appendChild( fillElem );
450 }
451
452 if ( mStrokeStyle != Qt::NoPen )
453 {
454 // <Stroke>
455 QDomElement strokeElem = doc.createElement( QStringLiteral( "se:Stroke" ) );
456 symbolizerElem.appendChild( strokeElem );
458 // Apply alpha from symbol
459 bool ok;
460 const double alpha { props.value( QStringLiteral( "alpha" ), QVariant() ).toDouble( &ok ) };
461 QColor strokeColor { mStrokeColor };
462 if ( ok )
463 {
464 strokeColor.setAlphaF( strokeColor.alphaF() * alpha );
465 }
467 }
468 }
469
470 // <se:Displacement>
473}
474
475QString QgsSimpleFillSymbolLayer::ogrFeatureStyle( double mmScaleFactor, double mapUnitScaleFactor ) const
476{
477 //brush
478 QString symbolStyle;
479 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStyleBrush( mColor ) );
480 symbolStyle.append( ';' );
481 //pen
482 symbolStyle.append( QgsSymbolLayerUtils::ogrFeatureStylePen( mStrokeWidth, mmScaleFactor, mapUnitScaleFactor, mStrokeColor, mPenJoinStyle ) );
483 return symbolStyle;
484}
485
487{
488 QColor color, strokeColor;
489 Qt::BrushStyle fillStyle;
490 Qt::PenStyle strokeStyle;
491 double strokeWidth;
492
493 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
494 QgsSymbolLayerUtils::fillFromSld( fillElem, fillStyle, color );
495
496 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
498
499 QPointF offset;
501
502 double scaleFactor = 1.0;
503 const QString uom = element.attribute( QStringLiteral( "uom" ) );
504 QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
505 offset.setX( offset.x() * scaleFactor );
506 offset.setY( offset.y() * scaleFactor );
507 strokeWidth = strokeWidth * scaleFactor;
508
509 std::unique_ptr< QgsSimpleFillSymbolLayer > sl = std::make_unique< QgsSimpleFillSymbolLayer >( color, fillStyle, strokeColor, strokeStyle, strokeWidth );
510 sl->setOutputUnit( sldUnitSize );
511 sl->setOffset( offset );
512 return sl.release();
513}
514
516{
517 double penBleed = context.convertToPainterUnits( mStrokeStyle == Qt::NoPen ? 0 : ( mStrokeWidth / 2.0 ), mStrokeWidthUnit, mStrokeWidthMapUnitScale );
518 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
519 return penBleed + offsetBleed;
520}
521
523{
524 double width = mStrokeWidth;
526 {
529 }
531}
532
534{
535 QColor c = mStrokeColor;
537 {
540 }
541 return c;
542}
543
545{
546 double angle = mAngle;
548 {
551 }
552 return angle;
553}
554
556{
557 return mStrokeStyle;
558}
559
561{
562 QColor c = mColor;
564 {
566 }
567 return c;
568}
569
571{
572 return mBrushStyle;
573}
574
576{
577 QPixmap pixmap( QSize( 32, 32 ) );
578 pixmap.fill( Qt::transparent );
579 QPainter painter;
580 painter.begin( &pixmap );
581 painter.setRenderHint( QPainter::Antialiasing );
582 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
586 renderContext.setForceVectorOutput( true );
587 QgsSymbolRenderContext symbolContext( renderContext, QgsUnitTypes::RenderUnit::RenderPixels, 1.0, false, Qgis::SymbolRenderHints() );
588
589 std::unique_ptr< QgsSimpleFillSymbolLayer > layerClone( clone() );
590 layerClone->setStrokeStyle( Qt::PenStyle::NoPen );
591 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
592 painter.end();
593 return pixmap.toImage();
594}
595
596//QgsGradientFillSymbolLayer
597
598QgsGradientFillSymbolLayer::QgsGradientFillSymbolLayer( const QColor &color, const QColor &color2,
599 Qgis::GradientColorSource colorType, Qgis::GradientType gradientType,
601 : mGradientColorType( colorType )
602 , mGradientType( gradientType )
603 , mCoordinateMode( coordinateMode )
604 , mGradientSpread( spread )
605 , mReferencePoint1( QPointF( 0.5, 0 ) )
606 , mReferencePoint2( QPointF( 0.5, 1 ) )
607{
608 mColor = color;
609 mColor2 = color2;
610}
611
613{
614 delete mGradientRamp;
615}
616
618{
619 //default to a two-color, linear gradient with feature mode and pad spreading
624 //default to gradient from the default fill color to white
625 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
626 QPointF referencePoint1 = QPointF( 0.5, 0 );
627 bool refPoint1IsCentroid = false;
628 QPointF referencePoint2 = QPointF( 0.5, 1 );
629 bool refPoint2IsCentroid = false;
630 double angle = 0;
631 QPointF offset;
632
633 //update gradient properties from props
634 if ( props.contains( QStringLiteral( "type" ) ) )
635 type = static_cast< Qgis::GradientType >( props[QStringLiteral( "type" )].toInt() );
636 if ( props.contains( QStringLiteral( "coordinate_mode" ) ) )
637 coordinateMode = static_cast< Qgis::SymbolCoordinateReference >( props[QStringLiteral( "coordinate_mode" )].toInt() );
638 if ( props.contains( QStringLiteral( "spread" ) ) )
639 gradientSpread = static_cast< Qgis::GradientSpread >( props[QStringLiteral( "spread" )].toInt() );
640 if ( props.contains( QStringLiteral( "color_type" ) ) )
641 colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
642 if ( props.contains( QStringLiteral( "gradient_color" ) ) )
643 {
644 //pre 2.5 projects used "gradient_color"
645 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color" )].toString() );
646 }
647 else if ( props.contains( QStringLiteral( "color" ) ) )
648 {
649 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
650 }
651 if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
652 {
653 color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
654 }
655
656 if ( props.contains( QStringLiteral( "reference_point1" ) ) )
657 referencePoint1 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point1" )].toString() );
658 if ( props.contains( QStringLiteral( "reference_point1_iscentroid" ) ) )
659 refPoint1IsCentroid = props[QStringLiteral( "reference_point1_iscentroid" )].toInt();
660 if ( props.contains( QStringLiteral( "reference_point2" ) ) )
661 referencePoint2 = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "reference_point2" )].toString() );
662 if ( props.contains( QStringLiteral( "reference_point2_iscentroid" ) ) )
663 refPoint2IsCentroid = props[QStringLiteral( "reference_point2_iscentroid" )].toInt();
664 if ( props.contains( QStringLiteral( "angle" ) ) )
665 angle = props[QStringLiteral( "angle" )].toDouble();
666
667 if ( props.contains( QStringLiteral( "offset" ) ) )
668 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
669
670 //attempt to create color ramp from props
671 QgsColorRamp *gradientRamp = nullptr;
672 if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
673 {
674 gradientRamp = QgsCptCityColorRamp::create( props );
675 }
676 else
677 {
678 gradientRamp = QgsGradientColorRamp::create( props );
679 }
680
681 //create a new gradient fill layer with desired properties
682 std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( color, color2, colorType, type, coordinateMode, gradientSpread );
683 sl->setOffset( offset );
684 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
685 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
686 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
687 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
688 sl->setReferencePoint1( referencePoint1 );
689 sl->setReferencePoint1IsCentroid( refPoint1IsCentroid );
690 sl->setReferencePoint2( referencePoint2 );
691 sl->setReferencePoint2IsCentroid( refPoint2IsCentroid );
692 sl->setAngle( angle );
693 if ( gradientRamp )
694 sl->setColorRamp( gradientRamp );
695
696 sl->restoreOldDataDefinedProperties( props );
697
698 return sl.release();
699}
700
702{
703 delete mGradientRamp;
704 mGradientRamp = ramp;
705}
706
708{
709 return QStringLiteral( "GradientFill" );
710}
711
712void QgsGradientFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, const QPolygonF &points )
713{
715 {
716 //shortcut
719 return;
720 }
721
722 bool ok;
723
724 //first gradient color
725 QColor color = mColor;
727 {
730 color.setAlphaF( context.opacity() * color.alphaF() );
731 }
732
733 //second gradient color
734 QColor color2 = mColor2;
736 {
739 color2.setAlphaF( context.opacity() * color2.alphaF() );
740 }
741
742 //gradient rotation angle
743 double angle = mAngle;
745 {
748 }
749
750 //gradient type
753 {
755 if ( ok )
756 {
757 if ( currentType == QObject::tr( "linear" ) )
758 {
760 }
761 else if ( currentType == QObject::tr( "radial" ) )
762 {
764 }
765 else if ( currentType == QObject::tr( "conical" ) )
766 {
768 }
769 }
770 }
771
772 //coordinate mode
775 {
776 QString currentCoordMode = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyCoordinateMode, context.renderContext().expressionContext(), QString(), &ok );
777 if ( ok )
778 {
779 if ( currentCoordMode == QObject::tr( "feature" ) )
780 {
782 }
783 else if ( currentCoordMode == QObject::tr( "viewport" ) )
784 {
786 }
787 }
788 }
789
790 //gradient spread
793 {
795 if ( ok )
796 {
797 if ( currentSpread == QObject::tr( "pad" ) )
798 {
800 }
801 else if ( currentSpread == QObject::tr( "repeat" ) )
802 {
804 }
805 else if ( currentSpread == QObject::tr( "reflect" ) )
806 {
808 }
809 }
810 }
811
812 //reference point 1 x & y
813 double refPoint1X = mReferencePoint1.x();
815 {
816 context.setOriginalValueVariable( refPoint1X );
818 }
819 double refPoint1Y = mReferencePoint1.y();
821 {
822 context.setOriginalValueVariable( refPoint1Y );
824 }
825 bool refPoint1IsCentroid = mReferencePoint1IsCentroid;
827 {
828 context.setOriginalValueVariable( refPoint1IsCentroid );
830 }
831
832 //reference point 2 x & y
833 double refPoint2X = mReferencePoint2.x();
835 {
836 context.setOriginalValueVariable( refPoint2X );
838 }
839 double refPoint2Y = mReferencePoint2.y();
841 {
842 context.setOriginalValueVariable( refPoint2Y );
844 }
845 bool refPoint2IsCentroid = mReferencePoint2IsCentroid;
847 {
848 context.setOriginalValueVariable( refPoint2IsCentroid );
850 }
851
852 if ( refPoint1IsCentroid || refPoint2IsCentroid )
853 {
854 //either the gradient is starting or ending at a centroid, so calculate it
856 //centroid coordinates need to be scaled to a range [0, 1] relative to polygon bounds
857 QRectF bbox = points.boundingRect();
858 double centroidX = ( centroid.x() - bbox.left() ) / bbox.width();
859 double centroidY = ( centroid.y() - bbox.top() ) / bbox.height();
860
861 if ( refPoint1IsCentroid )
862 {
863 refPoint1X = centroidX;
864 refPoint1Y = centroidY;
865 }
866 if ( refPoint2IsCentroid )
867 {
868 refPoint2X = centroidX;
869 refPoint2Y = centroidY;
870 }
871 }
872
873 //update gradient with data defined values
875 spread, QPointF( refPoint1X, refPoint1Y ), QPointF( refPoint2X, refPoint2Y ), angle );
876}
877
878QPointF QgsGradientFillSymbolLayer::rotateReferencePoint( QPointF refPoint, double angle )
879{
880 //rotate a reference point by a specified angle around the point (0.5, 0.5)
881
882 //create a line from the centrepoint of a rectangle bounded by (0, 0) and (1, 1) to the reference point
883 QLineF refLine = QLineF( QPointF( 0.5, 0.5 ), refPoint );
884 //rotate this line by the current rotation angle
885 refLine.setAngle( refLine.angle() + angle );
886 //get new end point of line
887 QPointF rotatedReferencePoint = refLine.p2();
888 //make sure coords of new end point is within [0, 1]
889 if ( rotatedReferencePoint.x() > 1 )
890 rotatedReferencePoint.setX( 1 );
891 if ( rotatedReferencePoint.x() < 0 )
892 rotatedReferencePoint.setX( 0 );
893 if ( rotatedReferencePoint.y() > 1 )
894 rotatedReferencePoint.setY( 1 );
895 if ( rotatedReferencePoint.y() < 0 )
896 rotatedReferencePoint.setY( 0 );
897
898 return rotatedReferencePoint;
899}
900
901void QgsGradientFillSymbolLayer::applyGradient( const QgsSymbolRenderContext &context, QBrush &brush,
902 const QColor &color, const QColor &color2, Qgis::GradientColorSource gradientColorType,
903 QgsColorRamp *gradientRamp, Qgis::GradientType gradientType,
904 Qgis::SymbolCoordinateReference coordinateMode, Qgis::GradientSpread gradientSpread,
905 QPointF referencePoint1, QPointF referencePoint2, const double angle )
906{
907 //update alpha of gradient colors
908 QColor fillColor = color;
909 fillColor.setAlphaF( context.opacity() * fillColor.alphaF() );
910 QColor fillColor2 = color2;
911 fillColor2.setAlphaF( context.opacity() * fillColor2.alphaF() );
912
913 //rotate reference points
914 QPointF rotatedReferencePoint1 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint1, angle ) : referencePoint1;
915 QPointF rotatedReferencePoint2 = !qgsDoubleNear( angle, 0.0 ) ? rotateReferencePoint( referencePoint2, angle ) : referencePoint2;
916
917 //create a QGradient with the desired properties
918 QGradient gradient;
919 switch ( gradientType )
920 {
922 gradient = QLinearGradient( rotatedReferencePoint1, rotatedReferencePoint2 );
923 break;
925 gradient = QRadialGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).length() );
926 break;
928 gradient = QConicalGradient( rotatedReferencePoint1, QLineF( rotatedReferencePoint1, rotatedReferencePoint2 ).angle() );
929 break;
930 }
931 switch ( coordinateMode )
932 {
934 gradient.setCoordinateMode( QGradient::ObjectBoundingMode );
935 break;
937 gradient.setCoordinateMode( QGradient::StretchToDeviceMode );
938 break;
939 }
940 switch ( gradientSpread )
941 {
943 gradient.setSpread( QGradient::PadSpread );
944 break;
946 gradient.setSpread( QGradient::ReflectSpread );
947 break;
949 gradient.setSpread( QGradient::RepeatSpread );
950 break;
951 }
952
953 //add stops to gradient
955 ( gradientRamp->type() == QgsGradientColorRamp::typeString() || gradientRamp->type() == QgsCptCityColorRamp::typeString() ) )
956 {
957 //color ramp gradient
958 QgsGradientColorRamp *gradRamp = static_cast<QgsGradientColorRamp *>( gradientRamp );
959 gradRamp->addStopsToGradient( &gradient, context.opacity() );
960 }
961 else
962 {
963 //two color gradient
964 gradient.setColorAt( 0.0, fillColor );
965 gradient.setColorAt( 1.0, fillColor2 );
966 }
967
968 //update QBrush use gradient
969 brush = QBrush( gradient );
970}
971
973{
974 QColor selColor = context.renderContext().selectionColor();
975 if ( ! SELECTION_IS_OPAQUE )
976 selColor.setAlphaF( context.opacity() );
977 mSelBrush = QBrush( selColor );
978}
979
981{
982 Q_UNUSED( context )
983}
984
985void QgsGradientFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
986{
987 QPainter *p = context.renderContext().painter();
988 if ( !p )
989 {
990 return;
991 }
992
993 applyDataDefinedSymbology( context, points );
994
995 p->setBrush( context.selected() ? mSelBrush : mBrush );
996 p->setPen( Qt::NoPen );
997
998 QPointF offset = mOffset;
1000 {
1002 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1003 bool ok = false;
1004 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1005 if ( ok )
1006 offset = res;
1007 }
1008
1009 if ( !offset.isNull() )
1010 {
1013 p->translate( offset );
1014 }
1015
1016 _renderPolygon( p, points, rings, context );
1017
1018 if ( !offset.isNull() )
1019 {
1020 p->translate( -offset );
1021 }
1022}
1023
1025{
1026 QVariantMap map;
1027 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
1028 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
1029 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mGradientColorType ) );
1030 map[QStringLiteral( "type" )] = QString::number( static_cast<int>( mGradientType ) );
1031 map[QStringLiteral( "coordinate_mode" )] = QString::number( static_cast< int >( mCoordinateMode ) );
1032 map[QStringLiteral( "spread" )] = QString::number( static_cast< int >( mGradientSpread ) );
1033 map[QStringLiteral( "reference_point1" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint1 );
1034 map[QStringLiteral( "reference_point1_iscentroid" )] = QString::number( mReferencePoint1IsCentroid );
1035 map[QStringLiteral( "reference_point2" )] = QgsSymbolLayerUtils::encodePoint( mReferencePoint2 );
1036 map[QStringLiteral( "reference_point2_iscentroid" )] = QString::number( mReferencePoint2IsCentroid );
1037 map[QStringLiteral( "angle" )] = QString::number( mAngle );
1038 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1039 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1040 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1041 if ( mGradientRamp )
1042 {
1043#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1044 map.unite( mGradientRamp->properties() );
1045#else
1046 map.insert( mGradientRamp->properties() );
1047#endif
1048 }
1049 return map;
1050}
1051
1053{
1054 std::unique_ptr< QgsGradientFillSymbolLayer > sl = std::make_unique< QgsGradientFillSymbolLayer >( mColor, mColor2, mGradientColorType, mGradientType, mCoordinateMode, mGradientSpread );
1055 if ( mGradientRamp )
1056 sl->setColorRamp( mGradientRamp->clone() );
1057 sl->setReferencePoint1( mReferencePoint1 );
1058 sl->setReferencePoint1IsCentroid( mReferencePoint1IsCentroid );
1059 sl->setReferencePoint2( mReferencePoint2 );
1060 sl->setReferencePoint2IsCentroid( mReferencePoint2IsCentroid );
1061 sl->setAngle( mAngle );
1062 sl->setOffset( mOffset );
1063 sl->setOffsetUnit( mOffsetUnit );
1064 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1065 copyDataDefinedProperties( sl.get() );
1066 copyPaintEffect( sl.get() );
1067 return sl.release();
1068}
1069
1071{
1072 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1073 return offsetBleed;
1074}
1075
1077{
1078 return true;
1079}
1080
1082{
1083 mOffsetUnit = unit;
1084}
1085
1087{
1088 return mOffsetUnit;
1089}
1090
1092{
1094}
1095
1097{
1098 mOffsetMapUnitScale = scale;
1099}
1100
1102{
1103 return mOffsetMapUnitScale;
1104}
1105
1106//QgsShapeburstFillSymbolLayer
1107
1109 int blurRadius, bool useWholeShape, double maxDistance )
1110 : mBlurRadius( blurRadius )
1111 , mUseWholeShape( useWholeShape )
1112 , mMaxDistance( maxDistance )
1113 , mColorType( colorType )
1114 , mColor2( color2 )
1115{
1116 mColor = color;
1117}
1118
1120
1122{
1123 //default to a two-color gradient
1125 QColor color = DEFAULT_SIMPLEFILL_COLOR, color2 = Qt::white;
1126 int blurRadius = 0;
1127 bool useWholeShape = true;
1128 double maxDistance = 5;
1129 QPointF offset;
1130
1131 //update fill properties from props
1132 if ( props.contains( QStringLiteral( "color_type" ) ) )
1133 {
1134 colorType = static_cast< Qgis::GradientColorSource >( props[QStringLiteral( "color_type" )].toInt() );
1135 }
1136 if ( props.contains( QStringLiteral( "shapeburst_color" ) ) )
1137 {
1138 //pre 2.5 projects used "shapeburst_color"
1139 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color" )].toString() );
1140 }
1141 else if ( props.contains( QStringLiteral( "color" ) ) )
1142 {
1143 color = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "color" )].toString() );
1144 }
1145
1146 if ( props.contains( QStringLiteral( "shapeburst_color2" ) ) )
1147 {
1148 //pre 2.5 projects used "shapeburst_color2"
1149 color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "shapeburst_color2" )].toString() );
1150 }
1151 else if ( props.contains( QStringLiteral( "gradient_color2" ) ) )
1152 {
1153 color2 = QgsSymbolLayerUtils::decodeColor( props[QStringLiteral( "gradient_color2" )].toString() );
1154 }
1155 if ( props.contains( QStringLiteral( "blur_radius" ) ) )
1156 {
1157 blurRadius = props[QStringLiteral( "blur_radius" )].toInt();
1158 }
1159 if ( props.contains( QStringLiteral( "use_whole_shape" ) ) )
1160 {
1161 useWholeShape = props[QStringLiteral( "use_whole_shape" )].toInt();
1162 }
1163 if ( props.contains( QStringLiteral( "max_distance" ) ) )
1164 {
1165 maxDistance = props[QStringLiteral( "max_distance" )].toDouble();
1166 }
1167 if ( props.contains( QStringLiteral( "offset" ) ) )
1168 {
1169 offset = QgsSymbolLayerUtils::decodePoint( props[QStringLiteral( "offset" )].toString() );
1170 }
1171
1172 //attempt to create color ramp from props
1173 QgsColorRamp *gradientRamp = nullptr;
1174 if ( props.contains( QStringLiteral( "rampType" ) ) && props[QStringLiteral( "rampType" )] == QgsCptCityColorRamp::typeString() )
1175 {
1176 gradientRamp = QgsCptCityColorRamp::create( props );
1177 }
1178 else
1179 {
1180 gradientRamp = QgsGradientColorRamp::create( props );
1181 }
1182
1183 //create a new shapeburst fill layer with desired properties
1184 std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( color, color2, colorType, blurRadius, useWholeShape, maxDistance );
1185 sl->setOffset( offset );
1186 if ( props.contains( QStringLiteral( "offset_unit" ) ) )
1187 {
1188 sl->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "offset_unit" )].toString() ) );
1189 }
1190 if ( props.contains( QStringLiteral( "distance_unit" ) ) )
1191 {
1192 sl->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( props[QStringLiteral( "distance_unit" )].toString() ) );
1193 }
1194 if ( props.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
1195 {
1196 sl->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
1197 }
1198 if ( props.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
1199 {
1200 sl->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( props[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
1201 }
1202 if ( props.contains( QStringLiteral( "ignore_rings" ) ) )
1203 {
1204 sl->setIgnoreRings( props[QStringLiteral( "ignore_rings" )].toInt() );
1205 }
1206 if ( gradientRamp )
1207 {
1208 sl->setColorRamp( gradientRamp );
1209 }
1210
1211 sl->restoreOldDataDefinedProperties( props );
1212
1213 return sl.release();
1214}
1215
1217{
1218 return QStringLiteral( "ShapeburstFill" );
1219}
1220
1222{
1223 if ( mGradientRamp.get() == ramp )
1224 return;
1225
1226 mGradientRamp.reset( ramp );
1227}
1228
1229void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
1230 double &maxDistance, bool &ignoreRings )
1231{
1232 //first gradient color
1233 color = mColor;
1235 {
1238 }
1239
1240 //second gradient color
1241 color2 = mColor2;
1243 {
1246 }
1247
1248 //blur radius
1249 blurRadius = mBlurRadius;
1251 {
1252 context.setOriginalValueVariable( mBlurRadius );
1254 }
1255
1256 //use whole shape
1257 useWholeShape = mUseWholeShape;
1259 {
1260 context.setOriginalValueVariable( mUseWholeShape );
1262 }
1263
1264 //max distance
1265 maxDistance = mMaxDistance;
1267 {
1268 context.setOriginalValueVariable( mMaxDistance );
1270 }
1271
1272 //ignore rings
1273 ignoreRings = mIgnoreRings;
1275 {
1276 context.setOriginalValueVariable( mIgnoreRings );
1278 }
1279
1280}
1281
1283{
1284 //TODO - check this
1285 QColor selColor = context.renderContext().selectionColor();
1286 if ( ! SELECTION_IS_OPAQUE )
1287 selColor.setAlphaF( context.opacity() );
1288 mSelBrush = QBrush( selColor );
1289}
1290
1292{
1293 Q_UNUSED( context )
1294}
1295
1296void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1297{
1298 QPainter *p = context.renderContext().painter();
1299 if ( !p )
1300 {
1301 return;
1302 }
1303
1304 if ( context.selected() )
1305 {
1306 //feature is selected, draw using selection style
1307 p->setBrush( mSelBrush );
1308 QPointF offset = mOffset;
1309
1311 {
1313 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1314 bool ok = false;
1315 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1316 if ( ok )
1317 offset = res;
1318 }
1319
1320 if ( !offset.isNull() )
1321 {
1322 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1323 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1324 p->translate( offset );
1325 }
1326 _renderPolygon( p, points, rings, context );
1327 if ( !offset.isNull() )
1328 {
1329 p->translate( -offset );
1330 }
1331 return;
1332 }
1333
1334 QColor color1, color2;
1335 int blurRadius;
1336 bool useWholeShape;
1337 double maxDistance;
1338 bool ignoreRings;
1339 //calculate data defined symbology
1340 applyDataDefinedSymbology( context, color1, color2, blurRadius, useWholeShape, maxDistance, ignoreRings );
1341
1342 //calculate max distance for shapeburst fill to extend from polygon boundary, in pixels
1343 int outputPixelMaxDist = 0;
1344 if ( !useWholeShape && !qgsDoubleNear( maxDistance, 0.0 ) )
1345 {
1346 //convert max distance to pixels
1347 outputPixelMaxDist = static_cast< int >( std::round( context.renderContext().convertToPainterUnits( maxDistance, mDistanceUnit, mDistanceMapUnitScale ) ) );
1348 }
1349
1350 //if we are using the two color mode, create a gradient ramp
1351 std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
1353 {
1354 twoColorGradientRamp = std::make_unique< QgsGradientColorRamp >( color1, color2 );
1355 }
1356
1357 //no stroke for shapeburst fills
1358 p->setPen( QPen( Qt::NoPen ) );
1359
1360 //calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
1361 int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
1362 //create a QImage to draw shapeburst in
1363 int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1364 int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1365 int imWidth = pointsWidth + ( sideBuffer * 2 );
1366 int imHeight = pointsHeight + ( sideBuffer * 2 );
1367
1368 // these are all potentially very expensive operations, so check regularly if the job is canceled and abort responsively
1369 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1370 return;
1371
1372 std::unique_ptr< QImage > fillImage = std::make_unique< QImage >( imWidth,
1373 imHeight, QImage::Format_ARGB32_Premultiplied );
1374 if ( fillImage->isNull() )
1375 {
1376 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1377 return;
1378 }
1379
1380 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1381 return;
1382
1383 //also create an image to store the alpha channel
1384 std::unique_ptr< QImage > alphaImage = std::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1385 if ( alphaImage->isNull() )
1386 {
1387 QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1388 return;
1389 }
1390
1391 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1392 return;
1393
1394 //Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
1395 //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
1396 //polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
1397 fillImage->fill( Qt::black );
1398
1399 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1400 return;
1401
1402 //initially fill the alpha channel image with a transparent color
1403 alphaImage->fill( Qt::transparent );
1404
1405 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1406 return;
1407
1408 //now, draw the polygon in the alpha channel image
1409 QPainter imgPainter;
1410 imgPainter.begin( alphaImage.get() );
1411 imgPainter.setRenderHint( QPainter::Antialiasing, true );
1412 imgPainter.setBrush( QBrush( Qt::white ) );
1413 imgPainter.setPen( QPen( Qt::black ) );
1414 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1415 _renderPolygon( &imgPainter, points, rings, context );
1416 imgPainter.end();
1417
1418 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1419 return;
1420
1421 //now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
1422 //(this avoids calling _renderPolygon twice, since that can be slow)
1423 imgPainter.begin( fillImage.get() );
1424 if ( !ignoreRings )
1425 {
1426 imgPainter.drawImage( 0, 0, *alphaImage );
1427 }
1428 else
1429 {
1430 //using ignore rings mode, so the alpha image can't be used
1431 //directly as the alpha channel contains polygon rings and we need
1432 //to draw now without any rings
1433 imgPainter.setBrush( QBrush( Qt::white ) );
1434 imgPainter.setPen( QPen( Qt::black ) );
1435 imgPainter.translate( -points.boundingRect().left() + sideBuffer, - points.boundingRect().top() + sideBuffer );
1436 _renderPolygon( &imgPainter, points, nullptr, context );
1437 }
1438 imgPainter.end();
1439
1440 if ( context.renderContext().feedback() && context.renderContext().feedback()->isCanceled() )
1441 return;
1442
1443 //apply distance transform to image, uses the current color ramp to calculate final pixel colors
1444 double *dtArray = distanceTransform( fillImage.get(), context.renderContext() );
1445
1446 //copy distance transform values back to QImage, shading by appropriate color ramp
1447 dtArrayToQImage( dtArray, fillImage.get(), mColorType == Qgis::GradientColorSource::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
1448 context.renderContext(), useWholeShape, outputPixelMaxDist );
1449 if ( context.opacity() < 1 )
1450 {
1451 QgsImageOperation::multiplyOpacity( *fillImage, context.opacity(), context.renderContext().feedback() );
1452 }
1453
1454 //clean up some variables
1455 delete [] dtArray;
1456
1457 //apply blur if desired
1458 if ( blurRadius > 0 )
1459 {
1460 QgsImageOperation::stackBlur( *fillImage, blurRadius, false, context.renderContext().feedback() );
1461 }
1462
1463 //apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1464 imgPainter.begin( fillImage.get() );
1465 imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
1466 imgPainter.drawImage( 0, 0, *alphaImage );
1467 imgPainter.end();
1468 //we're finished with the alpha channel image now
1469 alphaImage.reset();
1470
1471 //draw shapeburst image in correct place in the destination painter
1472
1473 QgsScopedQPainterState painterState( p );
1474 QPointF offset = mOffset;
1476 {
1478 const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
1479 bool ok = false;
1480 const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
1481 if ( ok )
1482 offset = res;
1483 }
1484 if ( !offset.isNull() )
1485 {
1486 offset.setX( context.renderContext().convertToPainterUnits( offset.x(), mOffsetUnit, mOffsetMapUnitScale ) );
1487 offset.setY( context.renderContext().convertToPainterUnits( offset.y(), mOffsetUnit, mOffsetMapUnitScale ) );
1488 p->translate( offset );
1489 }
1490
1491 p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
1492
1493 if ( !offset.isNull() )
1494 {
1495 p->translate( -offset );
1496 }
1497}
1498
1499//fast distance transform code, adapted from http://cs.brown.edu/~pff/dt/
1500
1501/* distance transform of a 1d function using squared distance */
1502void QgsShapeburstFillSymbolLayer::distanceTransform1d( double *f, int n, int *v, double *z, double *d )
1503{
1504 int k = 0;
1505 v[0] = 0;
1506 z[0] = -INF;
1507 z[1] = + INF;
1508 for ( int q = 1; q <= n - 1; q++ )
1509 {
1510 double s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1511 while ( s <= z[k] )
1512 {
1513 k--;
1514 s = ( ( f[q] + q * q ) - ( f[v[k]] + ( v[k] * v[k] ) ) ) / ( 2 * q - 2 * v[k] );
1515 }
1516 k++;
1517 v[k] = q;
1518 z[k] = s;
1519 z[k + 1] = + INF;
1520 }
1521
1522 k = 0;
1523 for ( int q = 0; q <= n - 1; q++ )
1524 {
1525 while ( z[k + 1] < q )
1526 k++;
1527 d[q] = ( q - v[k] ) * ( q - v[k] ) + f[v[k]];
1528 }
1529}
1530
1531/* distance transform of 2d function using squared distance */
1532void QgsShapeburstFillSymbolLayer::distanceTransform2d( double *im, int width, int height, QgsRenderContext &context )
1533{
1534 int maxDimension = std::max( width, height );
1535 double *f = new double[ maxDimension ];
1536 int *v = new int[ maxDimension ];
1537 double *z = new double[ maxDimension + 1 ];
1538 double *d = new double[ maxDimension ];
1539
1540 // transform along columns
1541 for ( int x = 0; x < width; x++ )
1542 {
1543 if ( context.renderingStopped() )
1544 break;
1545
1546 for ( int y = 0; y < height; y++ )
1547 {
1548 f[y] = im[ x + y * width ];
1549 }
1550 distanceTransform1d( f, height, v, z, d );
1551 for ( int y = 0; y < height; y++ )
1552 {
1553 im[ x + y * width ] = d[y];
1554 }
1555 }
1556
1557 // transform along rows
1558 for ( int y = 0; y < height; y++ )
1559 {
1560 if ( context.renderingStopped() )
1561 break;
1562
1563 for ( int x = 0; x < width; x++ )
1564 {
1565 f[x] = im[ x + y * width ];
1566 }
1567 distanceTransform1d( f, width, v, z, d );
1568 for ( int x = 0; x < width; x++ )
1569 {
1570 im[ x + y * width ] = d[x];
1571 }
1572 }
1573
1574 delete [] d;
1575 delete [] f;
1576 delete [] v;
1577 delete [] z;
1578}
1579
1580/* distance transform of a binary QImage */
1581double *QgsShapeburstFillSymbolLayer::distanceTransform( QImage *im, QgsRenderContext &context )
1582{
1583 int width = im->width();
1584 int height = im->height();
1585
1586 double *dtArray = new double[width * height];
1587
1588 //load qImage to array
1589 QRgb tmpRgb;
1590 int idx = 0;
1591 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1592 {
1593 if ( context.renderingStopped() )
1594 break;
1595
1596 const QRgb *scanLine = reinterpret_cast< const QRgb * >( im->constScanLine( heightIndex ) );
1597 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1598 {
1599 tmpRgb = scanLine[widthIndex];
1600 if ( qRed( tmpRgb ) == 0 )
1601 {
1602 //black pixel, so zero distance
1603 dtArray[ idx ] = 0;
1604 }
1605 else
1606 {
1607 //white pixel, so initially set distance as infinite
1608 dtArray[ idx ] = INF;
1609 }
1610 idx++;
1611 }
1612 }
1613
1614 //calculate squared distance transform
1615 distanceTransform2d( dtArray, width, height, context );
1616
1617 return dtArray;
1618}
1619
1620void QgsShapeburstFillSymbolLayer::dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, QgsRenderContext &context, bool useWholeShape, int maxPixelDistance )
1621{
1622 int width = im->width();
1623 int height = im->height();
1624
1625 //find maximum distance value
1626 double maxDistanceValue;
1627
1628 if ( useWholeShape )
1629 {
1630 //no max distance specified in symbol properties, so calculate from maximum value in distance transform results
1631 double dtMaxValue = array[0];
1632 for ( int i = 1; i < ( width * height ); ++i )
1633 {
1634 if ( array[i] > dtMaxValue )
1635 {
1636 dtMaxValue = array[i];
1637 }
1638 }
1639
1640 //values in distance transform are squared
1641 maxDistanceValue = std::sqrt( dtMaxValue );
1642 }
1643 else
1644 {
1645 //use max distance set in symbol properties
1646 maxDistanceValue = maxPixelDistance;
1647 }
1648
1649 //update the pixels in the provided QImage
1650 int idx = 0;
1651 double squaredVal = 0;
1652 double pixVal = 0;
1653
1654 for ( int heightIndex = 0; heightIndex < height; ++heightIndex )
1655 {
1656 if ( context.renderingStopped() )
1657 break;
1658
1659 QRgb *scanLine = reinterpret_cast< QRgb * >( im->scanLine( heightIndex ) );
1660 for ( int widthIndex = 0; widthIndex < width; ++widthIndex )
1661 {
1662 //result of distance transform
1663 squaredVal = array[idx];
1664
1665 //scale result to fit in the range [0, 1]
1666 if ( maxDistanceValue > 0 )
1667 {
1668 pixVal = squaredVal > 0 ? std::min( ( std::sqrt( squaredVal ) / maxDistanceValue ), 1.0 ) : 0;
1669 }
1670 else
1671 {
1672 pixVal = 1.0;
1673 }
1674
1675 //convert value to color from ramp
1676 //premultiply ramp color since we are storing this in a ARGB32_Premultiplied QImage
1677 scanLine[widthIndex] = qPremultiply( ramp->color( pixVal ).rgba() );
1678 idx++;
1679 }
1680 }
1681}
1682
1684{
1685 QVariantMap map;
1686 map[QStringLiteral( "color" )] = QgsSymbolLayerUtils::encodeColor( mColor );
1687 map[QStringLiteral( "gradient_color2" )] = QgsSymbolLayerUtils::encodeColor( mColor2 );
1688 map[QStringLiteral( "color_type" )] = QString::number( static_cast< int >( mColorType ) );
1689 map[QStringLiteral( "blur_radius" )] = QString::number( mBlurRadius );
1690 map[QStringLiteral( "use_whole_shape" )] = QString::number( mUseWholeShape );
1691 map[QStringLiteral( "max_distance" )] = QString::number( mMaxDistance );
1692 map[QStringLiteral( "distance_unit" )] = QgsUnitTypes::encodeUnit( mDistanceUnit );
1693 map[QStringLiteral( "distance_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale );
1694 map[QStringLiteral( "ignore_rings" )] = QString::number( mIgnoreRings );
1695 map[QStringLiteral( "offset" )] = QgsSymbolLayerUtils::encodePoint( mOffset );
1696 map[QStringLiteral( "offset_unit" )] = QgsUnitTypes::encodeUnit( mOffsetUnit );
1697 map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
1698 if ( mGradientRamp )
1699 {
1700#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1701 map.unite( mGradientRamp->properties() );
1702#else
1703 map.insert( mGradientRamp->properties() );
1704#endif
1705 }
1706
1707 return map;
1708}
1709
1711{
1712 std::unique_ptr< QgsShapeburstFillSymbolLayer > sl = std::make_unique< QgsShapeburstFillSymbolLayer >( mColor, mColor2, mColorType, mBlurRadius, mUseWholeShape, mMaxDistance );
1713 if ( mGradientRamp )
1714 {
1715 sl->setColorRamp( mGradientRamp->clone() );
1716 }
1717 sl->setDistanceUnit( mDistanceUnit );
1718 sl->setDistanceMapUnitScale( mDistanceMapUnitScale );
1719 sl->setIgnoreRings( mIgnoreRings );
1720 sl->setOffset( mOffset );
1721 sl->setOffsetUnit( mOffsetUnit );
1722 sl->setOffsetMapUnitScale( mOffsetMapUnitScale );
1723 copyDataDefinedProperties( sl.get() );
1724 copyPaintEffect( sl.get() );
1725 return sl.release();
1726}
1727
1729{
1730 double offsetBleed = context.convertToPainterUnits( std::max( std::fabs( mOffset.x() ), std::fabs( mOffset.y() ) ), mOffsetUnit, mOffsetMapUnitScale );
1731 return offsetBleed;
1732}
1733
1735{
1736 return true;
1737}
1738
1740{
1741 mDistanceUnit = unit;
1742 mOffsetUnit = unit;
1743}
1744
1746{
1747 if ( mDistanceUnit == mOffsetUnit )
1748 {
1749 return mDistanceUnit;
1750 }
1752}
1753
1755{
1756 return mDistanceUnit == QgsUnitTypes::RenderMapUnits || mDistanceUnit == QgsUnitTypes::RenderMetersInMapUnits
1757 || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
1758}
1759
1761{
1762 mDistanceMapUnitScale = scale;
1763 mOffsetMapUnitScale = scale;
1764}
1765
1767{
1768 if ( mDistanceMapUnitScale == mOffsetMapUnitScale )
1769 {
1770 return mDistanceMapUnitScale;
1771 }
1772 return QgsMapUnitScale();
1773}
1774
1775
1776//QgsImageFillSymbolLayer
1777
1779{
1780}
1781
1783
1784void QgsImageFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
1785{
1786 QPainter *p = context.renderContext().painter();
1787 if ( !p )
1788 {
1789 return;
1790 }
1791
1793 applyDataDefinedSettings( context );
1794
1795 p->setPen( QPen( Qt::NoPen ) );
1796
1797 QTransform bkTransform = mBrush.transform();
1798 if ( applyBrushTransformFromContext( &context ) && !context.renderContext().textureOrigin().isNull() )
1799 {
1800 QPointF leftCorner = context.renderContext().textureOrigin();
1801 QTransform t = mBrush.transform();
1802 t.translate( leftCorner.x(), leftCorner.y() );
1803 mBrush.setTransform( t );
1804 }
1805 else
1806 {
1807 QTransform t = mBrush.transform();
1808 t.translate( 0, 0 );
1809 mBrush.setTransform( t );
1810 }
1811
1812 if ( context.selected() )
1813 {
1814 QColor selColor = context.renderContext().selectionColor();
1815 p->setBrush( QBrush( selColor ) );
1816 _renderPolygon( p, points, rings, context );
1817 }
1818
1819 if ( !qgsDoubleNear( mNextAngle, 0.0 ) )
1820 {
1821 QTransform t = mBrush.transform();
1822 t.rotate( mNextAngle );
1823 mBrush.setTransform( t );
1824 }
1825 p->setBrush( mBrush );
1826 _renderPolygon( p, points, rings, context );
1827
1828 mBrush.setTransform( bkTransform );
1829}
1830
1832{
1833 mStrokeWidthUnit = unit;
1834}
1835
1837{
1838 return mStrokeWidthUnit;
1839}
1840
1842{
1844}
1845
1847{
1849}
1850
1852{
1853 double width = mStrokeWidth;
1855 {
1858 }
1860}
1861
1863{
1864 return Qt::SolidLine;
1865#if 0
1866 if ( !mStroke )
1867 {
1868 return Qt::SolidLine;
1869 }
1870 else
1871 {
1872 return mStroke->dxfPenStyle();
1873 }
1874#endif //0
1875}
1876
1878{
1879 QVariantMap map;
1880 map.insert( QStringLiteral( "coordinate_reference" ), QgsSymbolLayerUtils::encodeCoordinateReference( mCoordinateReference ) );
1881 return map;
1882}
1883
1885{
1886 //coordinate reference
1889 {
1890 bool ok = false;
1892 if ( ok )
1893 {
1895 if ( !ok )
1897 }
1898 }
1899
1901}
1902
1903
1904//QgsSVGFillSymbolLayer
1905
1906QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString &svgFilePath, double width, double angle )
1908 , mPatternWidth( width )
1909{
1910 mStrokeWidth = 0.3;
1911 mAngle = angle;
1912 mColor = QColor( 255, 255, 255 );
1914}
1915
1916QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray &svgData, double width, double angle )
1918 , mPatternWidth( width )
1919 , mSvgData( svgData )
1920{
1921 storeViewBox();
1922 mStrokeWidth = 0.3;
1923 mAngle = angle;
1924 mColor = QColor( 255, 255, 255 );
1925 setDefaultSvgParams();
1926}
1927
1929
1931{
1933 mPatternWidthUnit = unit;
1934 mSvgStrokeWidthUnit = unit;
1935 mStrokeWidthUnit = unit;
1936 if ( mStroke )
1937 mStroke->setOutputUnit( unit );
1938}
1939
1941{
1943 if ( mPatternWidthUnit != unit || mSvgStrokeWidthUnit != unit || mStrokeWidthUnit != unit )
1944 {
1946 }
1947 return unit;
1948}
1949
1951{
1953 mPatternWidthMapUnitScale = scale;
1954 mSvgStrokeWidthMapUnitScale = scale;
1955}
1956
1958{
1959 if ( QgsImageFillSymbolLayer::mapUnitScale() == mPatternWidthMapUnitScale &&
1960 mPatternWidthMapUnitScale == mSvgStrokeWidthMapUnitScale &&
1961 mSvgStrokeWidthMapUnitScale == mStrokeWidthMapUnitScale )
1962 {
1963 return mPatternWidthMapUnitScale;
1964 }
1965 return QgsMapUnitScale();
1966}
1967
1968void QgsSVGFillSymbolLayer::setSvgFilePath( const QString &svgPath )
1969{
1970 mSvgData = QgsApplication::svgCache()->getImageData( svgPath );
1971 storeViewBox();
1972
1973 mSvgFilePath = svgPath;
1974 setDefaultSvgParams();
1975}
1976
1977QgsSymbolLayer *QgsSVGFillSymbolLayer::create( const QVariantMap &properties )
1978{
1979 QByteArray data;
1980 double width = 20;
1981 QString svgFilePath;
1982 double angle = 0.0;
1983
1984 if ( properties.contains( QStringLiteral( "width" ) ) )
1985 {
1986 width = properties[QStringLiteral( "width" )].toDouble();
1987 }
1988 if ( properties.contains( QStringLiteral( "svgFile" ) ) )
1989 {
1990 svgFilePath = properties[QStringLiteral( "svgFile" )].toString();
1991 }
1992 if ( properties.contains( QStringLiteral( "angle" ) ) )
1993 {
1994 angle = properties[QStringLiteral( "angle" )].toDouble();
1995 }
1996
1997 std::unique_ptr< QgsSVGFillSymbolLayer > symbolLayer;
1998 if ( !svgFilePath.isEmpty() )
1999 {
2000 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( svgFilePath, width, angle );
2001 }
2002 else
2003 {
2004 if ( properties.contains( QStringLiteral( "data" ) ) )
2005 {
2006 data = QByteArray::fromHex( properties[QStringLiteral( "data" )].toString().toLocal8Bit() );
2007 }
2008 symbolLayer = std::make_unique< QgsSVGFillSymbolLayer >( data, width, angle );
2009 }
2010
2011 //svg parameters
2012 if ( properties.contains( QStringLiteral( "svgFillColor" ) ) )
2013 {
2014 //pre 2.5 projects used "svgFillColor"
2015 symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgFillColor" )].toString() ) );
2016 }
2017 else if ( properties.contains( QStringLiteral( "color" ) ) )
2018 {
2019 symbolLayer->setSvgFillColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() ) );
2020 }
2021 if ( properties.contains( QStringLiteral( "svgOutlineColor" ) ) )
2022 {
2023 //pre 2.5 projects used "svgOutlineColor"
2024 symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "svgOutlineColor" )].toString() ) );
2025 }
2026 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2027 {
2028 symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() ) );
2029 }
2030 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2031 {
2032 symbolLayer->setSvgStrokeColor( QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() ) );
2033 }
2034 if ( properties.contains( QStringLiteral( "svgOutlineWidth" ) ) )
2035 {
2036 //pre 2.5 projects used "svgOutlineWidth"
2037 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "svgOutlineWidth" )].toDouble() );
2038 }
2039 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2040 {
2041 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "outline_width" )].toDouble() );
2042 }
2043 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2044 {
2045 symbolLayer->setSvgStrokeWidth( properties[QStringLiteral( "line_width" )].toDouble() );
2046 }
2047
2048 //units
2049 if ( properties.contains( QStringLiteral( "pattern_width_unit" ) ) )
2050 {
2051 symbolLayer->setPatternWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "pattern_width_unit" )].toString() ) );
2052 }
2053 if ( properties.contains( QStringLiteral( "pattern_width_map_unit_scale" ) ) )
2054 {
2055 symbolLayer->setPatternWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "pattern_width_map_unit_scale" )].toString() ) );
2056 }
2057 if ( properties.contains( QStringLiteral( "svg_outline_width_unit" ) ) )
2058 {
2059 symbolLayer->setSvgStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "svg_outline_width_unit" )].toString() ) );
2060 }
2061 if ( properties.contains( QStringLiteral( "svg_outline_width_map_unit_scale" ) ) )
2062 {
2063 symbolLayer->setSvgStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "svg_outline_width_map_unit_scale" )].toString() ) );
2064 }
2065 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2066 {
2067 symbolLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2068 }
2069 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2070 {
2071 symbolLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2072 }
2073
2074 if ( properties.contains( QStringLiteral( "parameters" ) ) )
2075 {
2076 const QVariantMap parameters = properties[QStringLiteral( "parameters" )].toMap();
2077 symbolLayer->setParameters( QgsProperty::variantMapToPropertyMap( parameters ) );
2078 }
2079
2080 symbolLayer->restoreOldDataDefinedProperties( properties );
2081
2082 return symbolLayer.release();
2083}
2084
2085void QgsSVGFillSymbolLayer::resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving )
2086{
2087 QVariantMap::iterator it = properties.find( QStringLiteral( "svgFile" ) );
2088 if ( it != properties.end() )
2089 {
2090 if ( saving )
2091 it.value() = QgsSymbolLayerUtils::svgSymbolPathToName( it.value().toString(), pathResolver );
2092 else
2093 it.value() = QgsSymbolLayerUtils::svgSymbolNameToPath( it.value().toString(), pathResolver );
2094 }
2095}
2096
2098{
2099 return QStringLiteral( "SVGFill" );
2100}
2101
2102void QgsSVGFillSymbolLayer::applyPattern( QBrush &brush, const QString &svgFilePath, double patternWidth, QgsUnitTypes::RenderUnit patternWidthUnit,
2103 const QColor &svgFillColor, const QColor &svgStrokeColor, double svgStrokeWidth,
2104 QgsUnitTypes::RenderUnit svgStrokeWidthUnit, const QgsSymbolRenderContext &context,
2105 const QgsMapUnitScale &patternWidthMapUnitScale, const QgsMapUnitScale &svgStrokeWidthMapUnitScale, const QgsStringMap svgParameters )
2106{
2107 if ( mSvgViewBox.isNull() )
2108 {
2109 return;
2110 }
2111
2113
2114 if ( static_cast< int >( size ) < 1.0 || 10000.0 < size )
2115 {
2116 brush.setTextureImage( QImage() );
2117 }
2118 else
2119 {
2120 bool fitsInCache = true;
2122 QImage patternImage = QgsApplication::svgCache()->svgAsImage( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2123 context.renderContext().scaleFactor(), fitsInCache, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ), svgParameters );
2124 if ( !fitsInCache )
2125 {
2126 QPicture patternPict = QgsApplication::svgCache()->svgAsPicture( svgFilePath, size, svgFillColor, svgStrokeColor, strokeWidth,
2127 context.renderContext().scaleFactor(), false, 0, ( context.renderContext().flags() & Qgis::RenderContextFlag::RenderBlocking ) );
2128 double hwRatio = 1.0;
2129 if ( patternPict.width() > 0 )
2130 {
2131 hwRatio = static_cast< double >( patternPict.height() ) / static_cast< double >( patternPict.width() );
2132 }
2133 patternImage = QImage( static_cast< int >( size ), static_cast< int >( size * hwRatio ), QImage::Format_ARGB32_Premultiplied );
2134 patternImage.fill( 0 ); // transparent background
2135
2136 QPainter p( &patternImage );
2137 p.drawPicture( QPointF( size / 2, size * hwRatio / 2 ), patternPict );
2138 }
2139
2140 QTransform brushTransform;
2141 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
2142 {
2143 QImage transparentImage = patternImage.copy();
2144 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
2145 brush.setTextureImage( transparentImage );
2146 }
2147 else
2148 {
2149 brush.setTextureImage( patternImage );
2150 }
2151 brush.setTransform( brushTransform );
2152 }
2153}
2154
2156{
2157 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2158
2159 applyPattern( mBrush, mSvgFilePath, mPatternWidth, mPatternWidthUnit, mColor, mSvgStrokeColor, mSvgStrokeWidth, mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2160
2161 if ( mStroke )
2162 {
2163 mStroke->startRender( context.renderContext(), context.fields() );
2164 }
2165}
2166
2168{
2169 if ( mStroke )
2170 {
2171 mStroke->stopRender( context.renderContext() );
2172 }
2173}
2174
2175void QgsSVGFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
2176{
2177 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
2178
2179 if ( mStroke )
2180 {
2181 mStroke->renderPolyline( points, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
2182 if ( rings )
2183 {
2184 for ( auto ringIt = rings->constBegin(); ringIt != rings->constEnd(); ++ringIt )
2185 {
2186 mStroke->renderPolyline( *ringIt, context.feature(), context.renderContext(), -1, SELECT_FILL_BORDER && context.selected() );
2187 }
2188 }
2189 }
2190}
2191
2193{
2194 QVariantMap map;
2195 if ( !mSvgFilePath.isEmpty() )
2196 {
2197 map.insert( QStringLiteral( "svgFile" ), mSvgFilePath );
2198 }
2199 else
2200 {
2201 map.insert( QStringLiteral( "data" ), QString( mSvgData.toHex() ) );
2202 }
2203
2204 map.insert( QStringLiteral( "width" ), QString::number( mPatternWidth ) );
2205 map.insert( QStringLiteral( "angle" ), QString::number( mAngle ) );
2206
2207 //svg parameters
2208 map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
2209 map.insert( QStringLiteral( "outline_color" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2210 map.insert( QStringLiteral( "outline_width" ), QString::number( mSvgStrokeWidth ) );
2211
2212 //units
2213 map.insert( QStringLiteral( "pattern_width_unit" ), QgsUnitTypes::encodeUnit( mPatternWidthUnit ) );
2214 map.insert( QStringLiteral( "pattern_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mPatternWidthMapUnitScale ) );
2215 map.insert( QStringLiteral( "svg_outline_width_unit" ), QgsUnitTypes::encodeUnit( mSvgStrokeWidthUnit ) );
2216 map.insert( QStringLiteral( "svg_outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mSvgStrokeWidthMapUnitScale ) );
2217 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
2218 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
2219
2220 map[QStringLiteral( "parameters" )] = QgsProperty::propertyMapToVariantMap( mParameters );
2221
2222 return map;
2223}
2224
2226{
2227 std::unique_ptr< QgsSVGFillSymbolLayer > clonedLayer;
2228 if ( !mSvgFilePath.isEmpty() )
2229 {
2230 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgFilePath, mPatternWidth, mAngle );
2231 clonedLayer->setSvgFillColor( mColor );
2232 clonedLayer->setSvgStrokeColor( mSvgStrokeColor );
2233 clonedLayer->setSvgStrokeWidth( mSvgStrokeWidth );
2234 }
2235 else
2236 {
2237 clonedLayer = std::make_unique< QgsSVGFillSymbolLayer >( mSvgData, mPatternWidth, mAngle );
2238 }
2239
2240 clonedLayer->setPatternWidthUnit( mPatternWidthUnit );
2241 clonedLayer->setPatternWidthMapUnitScale( mPatternWidthMapUnitScale );
2242 clonedLayer->setSvgStrokeWidthUnit( mSvgStrokeWidthUnit );
2243 clonedLayer->setSvgStrokeWidthMapUnitScale( mSvgStrokeWidthMapUnitScale );
2244 clonedLayer->setStrokeWidthUnit( mStrokeWidthUnit );
2245 clonedLayer->setStrokeWidthMapUnitScale( mStrokeWidthMapUnitScale );
2246
2247 clonedLayer->setParameters( mParameters );
2248
2249 if ( mStroke )
2250 {
2251 clonedLayer->setSubSymbol( mStroke->clone() );
2252 }
2253 copyDataDefinedProperties( clonedLayer.get() );
2254 copyPaintEffect( clonedLayer.get() );
2255 return clonedLayer.release();
2256}
2257
2258void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
2259{
2260 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
2261 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
2262 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
2263 element.appendChild( symbolizerElem );
2264
2265 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
2266
2267 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
2268 symbolizerElem.appendChild( fillElem );
2269
2270 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
2271 fillElem.appendChild( graphicFillElem );
2272
2273 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
2274 graphicFillElem.appendChild( graphicElem );
2275
2276 if ( !mSvgFilePath.isEmpty() )
2277 {
2278 // encode a parametric SVG reference
2279 double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
2280 double strokeWidth = QgsSymbolLayerUtils::rescaleUom( mSvgStrokeWidth, mSvgStrokeWidthUnit, props );
2281 QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgStrokeColor, strokeWidth );
2282 }
2283 else
2284 {
2285 // TODO: create svg from data
2286 // <se:InlineContent>
2287 symbolizerElem.appendChild( doc.createComment( QStringLiteral( "SVG from data not implemented yet" ) ) );
2288 }
2289
2290 // <Rotation>
2291 QString angleFunc;
2292 bool ok;
2293 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
2294 if ( !ok )
2295 {
2296 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mAngle );
2297 }
2298 else if ( !qgsDoubleNear( angle + mAngle, 0.0 ) )
2299 {
2300 angleFunc = QString::number( angle + mAngle );
2301 }
2302 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
2303
2304 if ( mStroke )
2305 {
2306 // the stroke sub symbol should be stored within the Stroke element,
2307 // but it will be stored in a separated LineSymbolizer because it could
2308 // have more than one layer
2309 mStroke->toSld( doc, element, props );
2310 }
2311}
2312
2314{
2315 return mPatternWidthUnit == QgsUnitTypes::RenderMapUnits || mPatternWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
2316 || mSvgStrokeWidthUnit == QgsUnitTypes::RenderMapUnits || mSvgStrokeWidthUnit == QgsUnitTypes::RenderMetersInMapUnits;
2317}
2318
2320{
2321 return mStroke.get();
2322}
2323
2325{
2326 if ( !symbol ) //unset current stroke
2327 {
2328 mStroke.reset( nullptr );
2329 return true;
2330 }
2331
2332 if ( symbol->type() != Qgis::SymbolType::Line )
2333 {
2334 delete symbol;
2335 return false;
2336 }
2337
2338 QgsLineSymbol *lineSymbol = dynamic_cast<QgsLineSymbol *>( symbol );
2339 if ( lineSymbol )
2340 {
2341 mStroke.reset( lineSymbol );
2342 return true;
2343 }
2344
2345 delete symbol;
2346 return false;
2347}
2348
2350{
2351 if ( mStroke && mStroke->symbolLayer( 0 ) )
2352 {
2353 double subLayerBleed = mStroke->symbolLayer( 0 )->estimateMaxBleed( context );
2354 return subLayerBleed;
2355 }
2356 return 0;
2357}
2358
2360{
2361 Q_UNUSED( context )
2362 if ( !mStroke )
2363 {
2364 return QColor( Qt::black );
2365 }
2366 return mStroke->color();
2367}
2368
2369QSet<QString> QgsSVGFillSymbolLayer::usedAttributes( const QgsRenderContext &context ) const
2370{
2371 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2372 if ( mStroke )
2373 attr.unite( mStroke->usedAttributes( context ) );
2374 return attr;
2375}
2376
2378{
2380 return true;
2381 if ( mStroke && mStroke->hasDataDefinedProperties() )
2382 return true;
2383 return false;
2384}
2385
2387{
2388 QString path, mimeType;
2389 QColor fillColor, strokeColor;
2390 Qt::PenStyle penStyle;
2391 double size, strokeWidth;
2392
2393 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
2394 if ( fillElem.isNull() )
2395 return nullptr;
2396
2397 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
2398 if ( graphicFillElem.isNull() )
2399 return nullptr;
2400
2401 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
2402 if ( graphicElem.isNull() )
2403 return nullptr;
2404
2405 if ( !QgsSymbolLayerUtils::externalGraphicFromSld( graphicElem, path, mimeType, fillColor, size ) )
2406 return nullptr;
2407
2408 if ( mimeType != QLatin1String( "image/svg+xml" ) )
2409 return nullptr;
2410
2411 QgsSymbolLayerUtils::lineFromSld( graphicElem, penStyle, strokeColor, strokeWidth );
2412
2413 double scaleFactor = 1.0;
2414 const QString uom = element.attribute( QStringLiteral( "uom" ) );
2415 QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
2416 size = size * scaleFactor;
2417 strokeWidth = strokeWidth * scaleFactor;
2418
2419 double angle = 0.0;
2420 QString angleFunc;
2421 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
2422 {
2423 bool ok;
2424 double d = angleFunc.toDouble( &ok );
2425 if ( ok )
2426 angle = d;
2427 }
2428
2429 std::unique_ptr< QgsSVGFillSymbolLayer > sl = std::make_unique< QgsSVGFillSymbolLayer >( path, size, angle );
2430 sl->setOutputUnit( sldUnitSize );
2431 sl->setSvgFillColor( fillColor );
2432 sl->setSvgStrokeColor( strokeColor );
2433 sl->setSvgStrokeWidth( strokeWidth );
2434
2435 // try to get the stroke
2436 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
2437 if ( !strokeElem.isNull() )
2438 {
2440 if ( l )
2441 {
2442 QgsSymbolLayerList layers;
2443 layers.append( l );
2444 sl->setSubSymbol( new QgsLineSymbol( layers ) );
2445 }
2446 }
2447
2448 return sl.release();
2449}
2450
2452{
2456 {
2457 return; //no data defined settings
2458 }
2459
2461 {
2464 }
2465
2466 double width = mPatternWidth;
2468 {
2469 context.setOriginalValueVariable( mPatternWidth );
2471 }
2472 QString svgFile = mSvgFilePath;
2474 {
2475 context.setOriginalValueVariable( mSvgFilePath );
2477 context.renderContext().pathResolver() );
2478 }
2479 QColor svgFillColor = mColor;
2481 {
2484 }
2485 QColor svgStrokeColor = mSvgStrokeColor;
2487 {
2488 context.setOriginalValueVariable( QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
2490 }
2491 double strokeWidth = mSvgStrokeWidth;
2493 {
2494 context.setOriginalValueVariable( mSvgStrokeWidth );
2496 }
2497 QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( mParameters, context.renderContext().expressionContext() );
2498
2499 applyPattern( mBrush, svgFile, width, mPatternWidthUnit, svgFillColor, svgStrokeColor, strokeWidth,
2500 mSvgStrokeWidthUnit, context, mPatternWidthMapUnitScale, mSvgStrokeWidthMapUnitScale, evaluatedParameters );
2501
2502}
2503
2504void QgsSVGFillSymbolLayer::storeViewBox()
2505{
2506 if ( !mSvgData.isEmpty() )
2507 {
2508 QSvgRenderer r( mSvgData );
2509 if ( r.isValid() )
2510 {
2511 mSvgViewBox = r.viewBoxF();
2512 return;
2513 }
2514 }
2515
2516 mSvgViewBox = QRectF();
2517}
2518
2519void QgsSVGFillSymbolLayer::setDefaultSvgParams()
2520{
2521 if ( mSvgFilePath.isEmpty() )
2522 {
2523 return;
2524 }
2525
2526 bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
2527 bool hasDefaultFillColor, hasDefaultFillOpacity, hasDefaultStrokeColor, hasDefaultStrokeWidth, hasDefaultStrokeOpacity;
2528 QColor defaultFillColor, defaultStrokeColor;
2529 double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
2530 QgsApplication::svgCache()->containsParams( mSvgFilePath, hasFillParam, hasDefaultFillColor, defaultFillColor,
2531 hasFillOpacityParam, hasDefaultFillOpacity, defaultFillOpacity,
2532 hasStrokeParam, hasDefaultStrokeColor, defaultStrokeColor,
2533 hasStrokeWidthParam, hasDefaultStrokeWidth, defaultStrokeWidth,
2534 hasStrokeOpacityParam, hasDefaultStrokeOpacity, defaultStrokeOpacity );
2535
2536 double newFillOpacity = hasFillOpacityParam ? mColor.alphaF() : 1.0;
2537 double newStrokeOpacity = hasStrokeOpacityParam ? mSvgStrokeColor.alphaF() : 1.0;
2538
2539 if ( hasDefaultFillColor )
2540 {
2541 mColor = defaultFillColor;
2542 mColor.setAlphaF( newFillOpacity );
2543 }
2544 if ( hasDefaultFillOpacity )
2545 {
2546 mColor.setAlphaF( defaultFillOpacity );
2547 }
2548 if ( hasDefaultStrokeColor )
2549 {
2550 mSvgStrokeColor = defaultStrokeColor;
2551 mSvgStrokeColor.setAlphaF( newStrokeOpacity );
2552 }
2553 if ( hasDefaultStrokeOpacity )
2554 {
2555 mSvgStrokeColor.setAlphaF( defaultStrokeOpacity );
2556 }
2557 if ( hasDefaultStrokeWidth )
2558 {
2559 mSvgStrokeWidth = defaultStrokeWidth;
2560 }
2561}
2562
2563void QgsSVGFillSymbolLayer::setParameters( const QMap<QString, QgsProperty> &parameters )
2564{
2565 mParameters = parameters;
2566}
2567
2568
2571{
2572 mFillLineSymbol = std::make_unique<QgsLineSymbol>( );
2573 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
2574}
2575
2577
2579{
2580 mFillLineSymbol->setWidth( w );
2581 mLineWidth = w;
2582}
2583
2585{
2586 mFillLineSymbol->setColor( c );
2587 mColor = c;
2588}
2589
2591{
2592 return mFillLineSymbol ? mFillLineSymbol->color() : mColor;
2593}
2594
2596{
2597 if ( !symbol )
2598 {
2599 return false;
2600 }
2601
2602 if ( symbol->type() == Qgis::SymbolType::Line )
2603 {
2604 mFillLineSymbol.reset( qgis::down_cast<QgsLineSymbol *>( symbol ) );
2605 return true;
2606 }
2607 delete symbol;
2608 return false;
2609}
2610
2612{
2613 return mFillLineSymbol.get();
2614}
2615
2617{
2618 QSet<QString> attr = QgsImageFillSymbolLayer::usedAttributes( context );
2619 if ( mFillLineSymbol )
2620 attr.unite( mFillLineSymbol->usedAttributes( context ) );
2621 return attr;
2622}
2623
2625{
2627 return true;
2628 if ( mFillLineSymbol && mFillLineSymbol->hasDataDefinedProperties() )
2629 return true;
2630 return false;
2631}
2632
2634{
2635 // deliberately don't pass this on to subsymbol here
2636}
2637
2639{
2640 // deliberately don't pass this on to subsymbol here
2641}
2642
2644{
2645
2646 double lineAngleRads { qDegreesToRadians( mLineAngle ) };
2647 double distancePx { QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, {} ) };
2648
2649 QSize size { static_cast<int>( distancePx ), static_cast<int>( distancePx ) };
2650
2651 if ( static_cast<int>( mLineAngle ) % 90 != 0 )
2652 {
2653 size = QSize( static_cast<int>( distancePx / std::sin( lineAngleRads ) ), static_cast<int>( distancePx / std::cos( lineAngleRads ) ) );
2654 }
2655
2656 QPixmap pixmap( size );
2657 pixmap.fill( Qt::transparent );
2658 QPainter painter;
2659 painter.begin( &pixmap );
2660 painter.setRenderHint( QPainter::Antialiasing );
2661 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
2665 renderContext.setForceVectorOutput( true );
2666 QgsSymbolRenderContext symbolContext( renderContext, QgsUnitTypes::RenderUnit::RenderPixels, 1.0, false, Qgis::SymbolRenderHints() );
2667
2668 std::unique_ptr< QgsLinePatternFillSymbolLayer > layerClone( clone() );
2669 layerClone->setOffset( 0 );
2670 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
2671 painter.end();
2672 return pixmap.toImage();
2673 return QImage();
2674}
2675
2677{
2678 return 0;
2679}
2680
2682{
2684 mDistanceUnit = unit;
2685 mLineWidthUnit = unit;
2686 mOffsetUnit = unit;
2687
2688 if ( mFillLineSymbol )
2689 mFillLineSymbol->setOutputUnit( unit );
2690}
2691
2693{
2695 if ( mDistanceUnit != unit || mLineWidthUnit != unit || ( mOffsetUnit != unit && mOffsetUnit != QgsUnitTypes::RenderPercentage ) )
2696 {
2698 }
2699 return unit;
2700}
2701
2703{
2704 return mDistanceUnit == QgsUnitTypes::RenderMapUnits || mDistanceUnit == QgsUnitTypes::RenderMetersInMapUnits
2705 || mLineWidthUnit == QgsUnitTypes::RenderMapUnits || mLineWidthUnit == QgsUnitTypes::RenderMetersInMapUnits
2706 || mOffsetUnit == QgsUnitTypes::RenderMapUnits || mOffsetUnit == QgsUnitTypes::RenderMetersInMapUnits;
2707}
2708
2710{
2712 mDistanceMapUnitScale = scale;
2713 mLineWidthMapUnitScale = scale;
2714 mOffsetMapUnitScale = scale;
2715}
2716
2718{
2719 if ( QgsImageFillSymbolLayer::mapUnitScale() == mDistanceMapUnitScale &&
2720 mDistanceMapUnitScale == mLineWidthMapUnitScale &&
2721 mLineWidthMapUnitScale == mOffsetMapUnitScale )
2722 {
2723 return mDistanceMapUnitScale;
2724 }
2725 return QgsMapUnitScale();
2726}
2727
2729{
2730 std::unique_ptr< QgsLinePatternFillSymbolLayer > patternLayer = std::make_unique< QgsLinePatternFillSymbolLayer >();
2731
2732 //default values
2733 double lineAngle = 45;
2734 double distance = 5;
2735 double lineWidth = 0.5;
2736 QColor color( Qt::black );
2737 double offset = 0.0;
2738
2739 if ( properties.contains( QStringLiteral( "lineangle" ) ) )
2740 {
2741 //pre 2.5 projects used "lineangle"
2742 lineAngle = properties[QStringLiteral( "lineangle" )].toDouble();
2743 }
2744 else if ( properties.contains( QStringLiteral( "angle" ) ) )
2745 {
2746 lineAngle = properties[QStringLiteral( "angle" )].toDouble();
2747 }
2748 patternLayer->setLineAngle( lineAngle );
2749
2750 if ( properties.contains( QStringLiteral( "distance" ) ) )
2751 {
2752 distance = properties[QStringLiteral( "distance" )].toDouble();
2753 }
2754 patternLayer->setDistance( distance );
2755
2756 if ( properties.contains( QStringLiteral( "linewidth" ) ) )
2757 {
2758 //pre 2.5 projects used "linewidth"
2759 lineWidth = properties[QStringLiteral( "linewidth" )].toDouble();
2760 }
2761 else if ( properties.contains( QStringLiteral( "outline_width" ) ) )
2762 {
2763 lineWidth = properties[QStringLiteral( "outline_width" )].toDouble();
2764 }
2765 else if ( properties.contains( QStringLiteral( "line_width" ) ) )
2766 {
2767 lineWidth = properties[QStringLiteral( "line_width" )].toDouble();
2768 }
2769 patternLayer->setLineWidth( lineWidth );
2770
2771 if ( properties.contains( QStringLiteral( "color" ) ) )
2772 {
2773 color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "color" )].toString() );
2774 }
2775 else if ( properties.contains( QStringLiteral( "outline_color" ) ) )
2776 {
2777 color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "outline_color" )].toString() );
2778 }
2779 else if ( properties.contains( QStringLiteral( "line_color" ) ) )
2780 {
2781 color = QgsSymbolLayerUtils::decodeColor( properties[QStringLiteral( "line_color" )].toString() );
2782 }
2783 patternLayer->setColor( color );
2784
2785 if ( properties.contains( QStringLiteral( "offset" ) ) )
2786 {
2787 offset = properties[QStringLiteral( "offset" )].toDouble();
2788 }
2789 patternLayer->setOffset( offset );
2790
2791
2792 if ( properties.contains( QStringLiteral( "distance_unit" ) ) )
2793 {
2794 patternLayer->setDistanceUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_unit" )].toString() ) );
2795 }
2796 if ( properties.contains( QStringLiteral( "distance_map_unit_scale" ) ) )
2797 {
2798 patternLayer->setDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_map_unit_scale" )].toString() ) );
2799 }
2800 if ( properties.contains( QStringLiteral( "line_width_unit" ) ) )
2801 {
2802 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "line_width_unit" )].toString() ) );
2803 }
2804 else if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2805 {
2806 patternLayer->setLineWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2807 }
2808 if ( properties.contains( QStringLiteral( "line_width_map_unit_scale" ) ) )
2809 {
2810 patternLayer->setLineWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "line_width_map_unit_scale" )].toString() ) );
2811 }
2812 if ( properties.contains( QStringLiteral( "offset_unit" ) ) )
2813 {
2814 patternLayer->setOffsetUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_unit" )].toString() ) );
2815 }
2816 if ( properties.contains( QStringLiteral( "offset_map_unit_scale" ) ) )
2817 {
2818 patternLayer->setOffsetMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_map_unit_scale" )].toString() ) );
2819 }
2820 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
2821 {
2822 patternLayer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
2823 }
2824 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
2825 {
2826 patternLayer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
2827 }
2828 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
2829 {
2830 patternLayer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
2831 }
2832 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
2833 {
2834 patternLayer->setClipMode( QgsSymbolLayerUtils::decodeLineClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
2835 }
2836
2837 patternLayer->restoreOldDataDefinedProperties( properties );
2838
2839 return patternLayer.release();
2840}
2841
2843{
2844 return QStringLiteral( "LinePatternFill" );
2845}
2846
2847void QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double lineAngle, double distance )
2848{
2849 mBrush.setTextureImage( QImage() ); // set empty in case we have to return
2850
2851 if ( !mFillLineSymbol )
2852 {
2853 return;
2854 }
2855 // We have to make a copy because marker intervals will have to be adjusted
2856 std::unique_ptr< QgsLineSymbol > fillLineSymbol( mFillLineSymbol->clone() );
2857 if ( !fillLineSymbol )
2858 {
2859 return;
2860 }
2861
2862 const QgsRenderContext &ctx = context.renderContext();
2863 //double strokePixelWidth = lineWidth * QgsSymbolLayerUtils::pixelSizeScaleFactor( ctx, mLineWidthUnit, mLineWidthMapUnitScale );
2864 double outputPixelDist = ctx.convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
2865 double outputPixelOffset = mOffsetUnit == QgsUnitTypes::RenderPercentage ? outputPixelDist * mOffset / 100
2866 : ctx.convertToPainterUnits( mOffset, mOffsetUnit, mOffsetMapUnitScale );
2867
2868 // NOTE: this may need to be modified if we ever change from a forced rasterized/brush approach,
2869 // because potentially we may want to allow vector based line pattern fills where the first line
2870 // is offset by a large distance
2871
2872 // fix truncated pattern with larger offsets
2873 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDist );
2874 if ( outputPixelOffset > outputPixelDist / 2.0 )
2875 outputPixelOffset -= outputPixelDist;
2876
2877 // To get all patterns into image, we have to consider symbols size (estimateMaxBleed()).
2878 // For marker lines we have to get markers interval.
2879 double outputPixelBleed = 0;
2880 double outputPixelInterval = 0; // maximum interval
2881 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2882 {
2883 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2884 double outputPixelLayerBleed = layer->estimateMaxBleed( context.renderContext() );
2885 outputPixelBleed = std::max( outputPixelBleed, outputPixelLayerBleed );
2886
2887 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2888 if ( markerLineLayer )
2889 {
2890 double outputPixelLayerInterval = ctx.convertToPainterUnits( markerLineLayer->interval(), markerLineLayer->intervalUnit(), markerLineLayer->intervalMapUnitScale() );
2891
2892 // There may be multiple marker lines with different intervals.
2893 // In theory we should find the least common multiple, but that could be too
2894 // big (multiplication of intervals in the worst case).
2895 // Because patterns without small common interval would look strange, we
2896 // believe that the longest interval should usually be sufficient.
2897 outputPixelInterval = std::max( outputPixelInterval, outputPixelLayerInterval );
2898 }
2899 }
2900
2901 if ( outputPixelInterval > 0 )
2902 {
2903 // We have to adjust marker intervals to integer pixel size to get
2904 // repeatable pattern.
2905 double intervalScale = std::round( outputPixelInterval ) / outputPixelInterval;
2906 outputPixelInterval = std::round( outputPixelInterval );
2907
2908 for ( int i = 0; i < fillLineSymbol->symbolLayerCount(); i++ )
2909 {
2910 QgsSymbolLayer *layer = fillLineSymbol->symbolLayer( i );
2911
2912 QgsMarkerLineSymbolLayer *markerLineLayer = dynamic_cast<QgsMarkerLineSymbolLayer *>( layer );
2913 if ( markerLineLayer )
2914 {
2915 markerLineLayer->setInterval( intervalScale * markerLineLayer->interval() );
2916 }
2917 }
2918 }
2919
2920 //create image
2921 int height, width;
2922 lineAngle = std::fmod( lineAngle, 360 );
2923 if ( lineAngle < 0 )
2924 lineAngle += 360;
2925 if ( qgsDoubleNear( lineAngle, 0 ) || qgsDoubleNear( lineAngle, 360 ) || qgsDoubleNear( lineAngle, 180 ) )
2926 {
2927 height = outputPixelDist;
2928 width = outputPixelInterval > 0 ? outputPixelInterval : height;
2929 }
2930 else if ( qgsDoubleNear( lineAngle, 90 ) || qgsDoubleNear( lineAngle, 270 ) )
2931 {
2932 width = outputPixelDist;
2933 height = outputPixelInterval > 0 ? outputPixelInterval : width;
2934 }
2935 else
2936 {
2937 height = outputPixelDist / std::cos( lineAngle * M_PI / 180 ); //keep perpendicular distance between lines constant
2938 width = outputPixelDist / std::sin( lineAngle * M_PI / 180 );
2939
2940 // recalculate real angle and distance after rounding to pixels
2941 lineAngle = 180 * std::atan2( static_cast< double >( height ), static_cast< double >( width ) ) / M_PI;
2942 if ( lineAngle < 0 )
2943 {
2944 lineAngle += 360.;
2945 }
2946
2947 height = std::abs( height );
2948 width = std::abs( width );
2949
2950 outputPixelDist = std::abs( height * std::cos( lineAngle * M_PI / 180 ) );
2951
2952 // Round offset to correspond to one pixel height, otherwise lines may
2953 // be shifted on tile border if offset falls close to pixel center
2954 int offsetHeight = static_cast< int >( std::round( outputPixelOffset / std::cos( lineAngle * M_PI / 180 ) ) );
2955 outputPixelOffset = offsetHeight * std::cos( lineAngle * M_PI / 180 );
2956 }
2957
2958 //depending on the angle, we might need to render into a larger image and use a subset of it
2959 double dx = 0;
2960 double dy = 0;
2961
2962 // Add buffer based on bleed but keep precisely the height/width ratio (angle)
2963 // thus we add integer multiplications of width and height covering the bleed
2964 int bufferMulti = static_cast< int >( std::max( std::ceil( outputPixelBleed / width ), std::ceil( outputPixelBleed / width ) ) );
2965
2966 // Always buffer at least once so that center of line marker in upper right corner
2967 // does not fall outside due to representation error
2968 bufferMulti = std::max( bufferMulti, 1 );
2969
2970 int xBuffer = width * bufferMulti;
2971 int yBuffer = height * bufferMulti;
2972 int innerWidth = width;
2973 int innerHeight = height;
2974 width += 2 * xBuffer;
2975 height += 2 * yBuffer;
2976
2977 //protect from zero width/height image and symbol layer from eating too much memory
2978 if ( width > 10000 || height > 10000 || width == 0 || height == 0 )
2979 {
2980 return;
2981 }
2982
2983 QImage patternImage( width, height, QImage::Format_ARGB32 );
2984 patternImage.fill( 0 );
2985
2986 QPointF p1, p2, p3, p4, p5, p6;
2987 if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
2988 {
2989 p1 = QPointF( 0, yBuffer );
2990 p2 = QPointF( width, yBuffer );
2991 p3 = QPointF( 0, yBuffer + innerHeight );
2992 p4 = QPointF( width, yBuffer + innerHeight );
2993 }
2994 else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
2995 {
2996 p1 = QPointF( xBuffer, height );
2997 p2 = QPointF( xBuffer, 0 );
2998 p3 = QPointF( xBuffer + innerWidth, height );
2999 p4 = QPointF( xBuffer + innerWidth, 0 );
3000 }
3001 else if ( lineAngle > 0 && lineAngle < 90 )
3002 {
3003 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3004 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3005 p1 = QPointF( 0, height );
3006 p2 = QPointF( width, 0 );
3007 p3 = QPointF( -dx, height - dy );
3008 p4 = QPointF( width - dx, -dy );
3009 p5 = QPointF( dx, height + dy );
3010 p6 = QPointF( width + dx, dy );
3011 }
3012 else if ( lineAngle > 180 && lineAngle < 270 )
3013 {
3014 dx = outputPixelDist * std::cos( ( 90 - lineAngle ) * M_PI / 180.0 );
3015 dy = outputPixelDist * std::sin( ( 90 - lineAngle ) * M_PI / 180.0 );
3016 p1 = QPointF( width, 0 );
3017 p2 = QPointF( 0, height );
3018 p3 = QPointF( width - dx, -dy );
3019 p4 = QPointF( -dx, height - dy );
3020 p5 = QPointF( width + dx, dy );
3021 p6 = QPointF( dx, height + dy );
3022 }
3023 else if ( lineAngle > 90 && lineAngle < 180 )
3024 {
3025 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3026 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3027 p1 = QPointF( 0, 0 );
3028 p2 = QPointF( width, height );
3029 p5 = QPointF( dx, -dy );
3030 p6 = QPointF( width + dx, height - dy );
3031 p3 = QPointF( -dx, dy );
3032 p4 = QPointF( width - dx, height + dy );
3033 }
3034 else if ( lineAngle > 270 && lineAngle < 360 )
3035 {
3036 dy = outputPixelDist * std::cos( ( 180 - lineAngle ) * M_PI / 180 );
3037 dx = outputPixelDist * std::sin( ( 180 - lineAngle ) * M_PI / 180 );
3038 p1 = QPointF( width, height );
3039 p2 = QPointF( 0, 0 );
3040 p5 = QPointF( width + dx, height - dy );
3041 p6 = QPointF( dx, -dy );
3042 p3 = QPointF( width - dx, height + dy );
3043 p4 = QPointF( -dx, dy );
3044 }
3045
3046 if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
3047 {
3048 QPointF tempPt;
3049 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
3050 p3 = QPointF( tempPt.x(), tempPt.y() );
3051 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
3052 p4 = QPointF( tempPt.x(), tempPt.y() );
3053 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
3054 p5 = QPointF( tempPt.x(), tempPt.y() );
3055 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
3056 p6 = QPointF( tempPt.x(), tempPt.y() );
3057
3058 //update p1, p2 last
3059 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
3060 p1 = QPointF( tempPt.x(), tempPt.y() );
3061 tempPt = QgsSymbolLayerUtils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
3062 p2 = QPointF( tempPt.x(), tempPt.y() );
3063 }
3064
3065 QPainter p( &patternImage );
3066
3067#if 0
3068 // DEBUG: Draw rectangle
3069 p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
3070 QPen pen( QColor( Qt::black ) );
3071 pen.setWidthF( 0.1 );
3072 pen.setCapStyle( Qt::FlatCap );
3073 p.setPen( pen );
3074
3075 // To see this rectangle, comment buffer cut below.
3076 // Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
3077 QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 );
3078 p.drawPolygon( polygon );
3079
3080 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 );
3081 p.drawPolygon( polygon );
3082#endif
3083
3084 // Use antialiasing because without antialiasing lines are rendered to the
3085 // right and below the mathematically defined points (not symmetrical)
3086 // and such tiles become useless for are filling
3087 p.setRenderHint( QPainter::Antialiasing, true );
3088
3089 // line rendering needs context for drawing on patternImage
3090 QgsRenderContext lineRenderContext;
3091 lineRenderContext.setPainter( &p );
3092 lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3094 lineRenderContext.setMapToPixel( mtp );
3095 lineRenderContext.setForceVectorOutput( false );
3096 lineRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3098
3099 fillLineSymbol->startRender( lineRenderContext, context.fields() );
3100
3101 QVector<QPolygonF> polygons;
3102 polygons.append( QPolygonF() << p1 << p2 );
3103 polygons.append( QPolygonF() << p3 << p4 );
3104 if ( !qgsDoubleNear( lineAngle, 0 ) && !qgsDoubleNear( lineAngle, 360 ) && !qgsDoubleNear( lineAngle, 90 ) && !qgsDoubleNear( lineAngle, 180 ) && !qgsDoubleNear( lineAngle, 270 ) )
3105 {
3106 polygons.append( QPolygonF() << p5 << p6 );
3107 }
3108
3109 for ( const QPolygonF &polygon : std::as_const( polygons ) )
3110 {
3111 fillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, context.selected() );
3112 }
3113
3114 fillLineSymbol->stopRender( lineRenderContext );
3115 p.end();
3116
3117 // Cut off the buffer
3118 patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
3119
3120 //set image to mBrush
3121 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3122 {
3123 QImage transparentImage = patternImage.copy();
3124 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3125 brush.setTextureImage( transparentImage );
3126 }
3127 else
3128 {
3129 brush.setTextureImage( patternImage );
3130 }
3131
3132 QTransform brushTransform;
3133 brush.setTransform( brushTransform );
3134}
3135
3137{
3138 // if we are using a vector based output, we need to render points as vectors
3139 // (OR if the line has data defined symbology, in which case we need to evaluate this line-by-line)
3140 mRenderUsingLines = context.renderContext().forceVectorOutput()
3141 || mFillLineSymbol->hasDataDefinedProperties()
3144
3145 if ( mRenderUsingLines )
3146 {
3147 if ( mFillLineSymbol )
3148 mFillLineSymbol->startRender( context.renderContext(), context.fields() );
3149 }
3150 else
3151 {
3152 // optimised render for screen only, use image based brush
3153 applyPattern( context, mBrush, mLineAngle, mDistance );
3154 }
3155}
3156
3158{
3159 if ( mRenderUsingLines && mFillLineSymbol )
3160 {
3161 mFillLineSymbol->stopRender( context.renderContext() );
3162 }
3163}
3164
3165void QgsLinePatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3166{
3167 if ( !mRenderUsingLines )
3168 {
3169 // use image based brush for speed
3170 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3171 return;
3172 }
3173
3174 // vector based output - so draw line by line!
3175 QPainter *p = context.renderContext().painter();
3176 if ( !p )
3177 {
3178 return;
3179 }
3180
3181 double lineAngle = mLineAngle;
3183 {
3184 context.setOriginalValueVariable( mLineAngle );
3186 }
3187
3188 double distance = mDistance;
3190 {
3191 context.setOriginalValueVariable( mDistance );
3193 }
3194 const double outputPixelDistance = context.renderContext().convertToPainterUnits( distance, mDistanceUnit, mDistanceMapUnitScale );
3195
3196 double offset = mOffset;
3197 double outputPixelOffset = mOffsetUnit == QgsUnitTypes::RenderPercentage ? outputPixelDistance * offset / 100
3198 : context.renderContext().convertToPainterUnits( offset, mOffsetUnit, mOffsetMapUnitScale );
3199
3200 // fix truncated pattern with larger offsets
3201 outputPixelOffset = std::fmod( outputPixelOffset, outputPixelDistance );
3202 if ( outputPixelOffset > outputPixelDistance / 2.0 )
3203 outputPixelOffset -= outputPixelDistance;
3204
3205 p->setPen( QPen( Qt::NoPen ) );
3206
3207 if ( context.selected() )
3208 {
3209 QColor selColor = context.renderContext().selectionColor();
3210 p->setBrush( QBrush( selColor ) );
3211 _renderPolygon( p, points, rings, context );
3212 }
3213
3214 // if invalid parameters, skip out
3215 if ( qgsDoubleNear( distance, 0 ) )
3216 return;
3217
3218 p->save();
3219
3220 Qgis::LineClipMode clipMode = mClipMode;
3222 {
3224 bool ok = false;
3225 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyLineClipping, context.renderContext().expressionContext(), QString(), &ok );
3226 if ( ok )
3227 {
3228 Qgis::LineClipMode decodedMode = QgsSymbolLayerUtils::decodeLineClipMode( valueString, &ok );
3229 if ( ok )
3230 clipMode = decodedMode;
3231 }
3232 }
3233
3234 std::unique_ptr< QgsPolygon > shapePolygon;
3235 std::unique_ptr< QgsGeometryEngine > shapeEngine;
3236 switch ( clipMode )
3237 {
3239 break;
3240
3242 {
3243 shapePolygon = std::make_unique< QgsPolygon >();
3244 shapePolygon->setExteriorRing( QgsLineString::fromQPolygonF( points ) );
3245 if ( rings )
3246 {
3247 for ( const QPolygonF &ring : *rings )
3248 {
3249 shapePolygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
3250 }
3251 }
3252 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
3253 shapeEngine->prepareGeometry();
3254 break;
3255 }
3256
3258 {
3259 QPainterPath path;
3260 path.addPolygon( points );
3261 if ( rings )
3262 {
3263 for ( const QPolygonF &ring : *rings )
3264 {
3265 path.addPolygon( ring );
3266 }
3267 }
3268 p->setClipPath( path, Qt::IntersectClip );
3269 break;
3270 }
3271 }
3272
3273 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
3274 const QRectF boundingRect = points.boundingRect();
3275
3276 QTransform invertedRotateTransform;
3277 double left;
3278 double top;
3279 double right;
3280 double bottom;
3281
3282 QTransform transform;
3283 if ( applyBrushTransform )
3284 {
3285 // rotation applies around center of feature
3286 transform.translate( -boundingRect.center().x(),
3287 -boundingRect.center().y() );
3288 transform.rotate( lineAngle );
3289 transform.translate( boundingRect.center().x(),
3290 boundingRect.center().y() );
3291 }
3292 else
3293 {
3294 // rotation applies around top of viewport
3295 transform.rotate( lineAngle );
3296 }
3297
3298 const QRectF transformedBounds = transform.map( points ).boundingRect();
3299
3300 // bounds are expanded out a bit to account for maximum line width
3301 const double buffer = QgsSymbolLayerUtils::estimateMaxSymbolBleed( mFillLineSymbol.get(), context.renderContext() );
3302 left = transformedBounds.left() - buffer * 2;
3303 top = transformedBounds.top() - buffer * 2;
3304 right = transformedBounds.right() + buffer * 2;
3305 bottom = transformedBounds.bottom() + buffer * 2;
3306 invertedRotateTransform = transform.inverted();
3307
3308 if ( !applyBrushTransform )
3309 {
3310 top -= transformedBounds.top() - ( outputPixelDistance * std::floor( transformedBounds.top() / outputPixelDistance ) );
3311 }
3312
3314 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
3315 const bool needsExpressionContext = mFillLineSymbol->hasDataDefinedProperties();
3316
3317 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
3319
3320 int currentLine = 0;
3321 for ( double currentY = top; currentY <= bottom; currentY += outputPixelDistance )
3322 {
3323 if ( context.renderContext().renderingStopped() )
3324 break;
3325
3326 if ( needsExpressionContext )
3327 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_line_number" ), ++currentLine, true ) );
3328
3329 double x1 = left;
3330 double y1 = currentY;
3331 double x2 = left;
3332 double y2 = currentY;
3333 invertedRotateTransform.map( left, currentY - outputPixelOffset, &x1, &y1 );
3334 invertedRotateTransform.map( right, currentY - outputPixelOffset, &x2, &y2 );
3335
3336 if ( shapeEngine )
3337 {
3338 QgsLineString ls( QgsPoint( x1, y1 ), QgsPoint( x2, y2 ) );
3339 std::unique_ptr< QgsAbstractGeometry > intersection( shapeEngine->intersection( &ls ) );
3340 for ( auto it = intersection->const_parts_begin(); it != intersection->const_parts_end(); ++it )
3341 {
3342 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( *it ) )
3343 {
3344 mFillLineSymbol->renderPolyline( ls->asQPolygonF(), context.feature(), context.renderContext() );
3345 }
3346 }
3347 }
3348 else
3349 {
3350 mFillLineSymbol->renderPolyline( QPolygonF() << QPointF( x1, y1 ) << QPointF( x2, y2 ), context.feature(), context.renderContext() );
3351 }
3352 }
3353
3354 p->restore();
3355
3357}
3358
3360{
3361 QVariantMap map = QgsImageFillSymbolLayer::properties();
3362 map.insert( QStringLiteral( "angle" ), QString::number( mLineAngle ) );
3363 map.insert( QStringLiteral( "distance" ), QString::number( mDistance ) );
3364 map.insert( QStringLiteral( "line_width" ), QString::number( mLineWidth ) );
3365 map.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
3366 map.insert( QStringLiteral( "offset" ), QString::number( mOffset ) );
3367 map.insert( QStringLiteral( "distance_unit" ), QgsUnitTypes::encodeUnit( mDistanceUnit ) );
3368 map.insert( QStringLiteral( "line_width_unit" ), QgsUnitTypes::encodeUnit( mLineWidthUnit ) );
3369 map.insert( QStringLiteral( "offset_unit" ), QgsUnitTypes::encodeUnit( mOffsetUnit ) );
3370 map.insert( QStringLiteral( "distance_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceMapUnitScale ) );
3371 map.insert( QStringLiteral( "line_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineWidthMapUnitScale ) );
3372 map.insert( QStringLiteral( "offset_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale ) );
3373 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
3374 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
3375 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeLineClipMode( mClipMode ) );
3376 return map;
3377}
3378
3380{
3382 if ( mFillLineSymbol )
3383 {
3384 clonedLayer->setSubSymbol( mFillLineSymbol->clone() );
3385 }
3386 copyPaintEffect( clonedLayer );
3387 copyDataDefinedProperties( clonedLayer );
3388 return clonedLayer;
3389}
3390
3391void QgsLinePatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
3392{
3393 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
3394 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
3395 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
3396 element.appendChild( symbolizerElem );
3397
3398 // <Geometry>
3399 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
3400
3401 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
3402 symbolizerElem.appendChild( fillElem );
3403
3404 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
3405 fillElem.appendChild( graphicFillElem );
3406
3407 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
3408 graphicFillElem.appendChild( graphicElem );
3409
3410 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
3411
3412 // Export to PNG (TODO: SVG)
3413 bool exportOk { false };
3414 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
3415 {
3416 const QImage image { toTiledPatternImage() };
3417 if ( ! image.isNull() )
3418 {
3419 const QFileInfo info { context.exportFilePath() };
3420 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
3421 pngPath = QgsFileUtils::uniquePath( pngPath );
3422 image.save( pngPath );
3423 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
3424 exportOk = true;
3425 }
3426 }
3427
3428 if ( ! exportOk )
3429 {
3430 //line properties must be inside the graphic definition
3431 QColor lineColor = mFillLineSymbol ? mFillLineSymbol->color() : QColor();
3432 double lineWidth = mFillLineSymbol ? mFillLineSymbol->width() : 0.0;
3433 lineWidth = QgsSymbolLayerUtils::rescaleUom( lineWidth, mLineWidthUnit, props );
3434 double distance = QgsSymbolLayerUtils::rescaleUom( mDistance, mDistanceUnit, props );
3435 QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "horline" ), QColor(), lineColor, Qt::SolidLine, lineWidth, distance );
3436
3437 // <Rotation>
3438 QString angleFunc;
3439 bool ok;
3440 double angle = props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toDouble( &ok );
3441 if ( !ok )
3442 {
3443 angleFunc = QStringLiteral( "%1 + %2" ).arg( props.value( QStringLiteral( "angle" ), QStringLiteral( "0" ) ).toString() ).arg( mLineAngle );
3444 }
3445 else if ( !qgsDoubleNear( angle + mLineAngle, 0.0 ) )
3446 {
3447 angleFunc = QString::number( angle + mLineAngle );
3448 }
3449 QgsSymbolLayerUtils::createRotationElement( doc, graphicElem, angleFunc );
3450
3451 // <se:Displacement>
3452 QPointF lineOffset( std::sin( mLineAngle ) * mOffset, std::cos( mLineAngle ) * mOffset );
3453 lineOffset = QgsSymbolLayerUtils::rescaleUom( lineOffset, mOffsetUnit, props );
3454 QgsSymbolLayerUtils::createDisplacementElement( doc, graphicElem, lineOffset );
3455 }
3456}
3457
3458QString QgsLinePatternFillSymbolLayer::ogrFeatureStyleWidth( double widthScaleFactor ) const
3459{
3460 QString featureStyle;
3461 featureStyle.append( "Brush(" );
3462 featureStyle.append( QStringLiteral( "fc:%1" ).arg( mColor.name() ) );
3463 featureStyle.append( QStringLiteral( ",bc:%1" ).arg( QLatin1String( "#00000000" ) ) ); //transparent background
3464 featureStyle.append( ",id:\"ogr-brush-2\"" );
3465 featureStyle.append( QStringLiteral( ",a:%1" ).arg( mLineAngle ) );
3466 featureStyle.append( QStringLiteral( ",s:%1" ).arg( mLineWidth * widthScaleFactor ) );
3467 featureStyle.append( ",dx:0mm" );
3468 featureStyle.append( QStringLiteral( ",dy:%1mm" ).arg( mDistance * widthScaleFactor ) );
3469 featureStyle.append( ')' );
3470 return featureStyle;
3471}
3472
3474{
3476 && ( !mFillLineSymbol || !mFillLineSymbol->hasDataDefinedProperties() ) )
3477 {
3478 return; //no data defined settings
3479 }
3480
3481 double lineAngle = mLineAngle;
3483 {
3484 context.setOriginalValueVariable( mLineAngle );
3486 }
3487 double distance = mDistance;
3489 {
3490 context.setOriginalValueVariable( mDistance );
3492 }
3493 applyPattern( context, mBrush, lineAngle, distance );
3494}
3495
3497{
3498 QString name;
3499 QColor fillColor, lineColor;
3500 double size, lineWidth;
3501 Qt::PenStyle lineStyle;
3502
3503 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
3504 if ( fillElem.isNull() )
3505 return nullptr;
3506
3507 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
3508 if ( graphicFillElem.isNull() )
3509 return nullptr;
3510
3511 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
3512 if ( graphicElem.isNull() )
3513 return nullptr;
3514
3515 if ( !QgsSymbolLayerUtils::wellKnownMarkerFromSld( graphicElem, name, fillColor, lineColor, lineStyle, lineWidth, size ) )
3516 return nullptr;
3517
3518 if ( name != QLatin1String( "horline" ) )
3519 return nullptr;
3520
3521 double angle = 0.0;
3522 QString angleFunc;
3523 if ( QgsSymbolLayerUtils::rotationFromSldElement( graphicElem, angleFunc ) )
3524 {
3525 bool ok;
3526 double d = angleFunc.toDouble( &ok );
3527 if ( ok )
3528 angle = d;
3529 }
3530
3531 double offset = 0.0;
3532 QPointF vectOffset;
3533 if ( QgsSymbolLayerUtils::displacementFromSldElement( graphicElem, vectOffset ) )
3534 {
3535 offset = std::sqrt( std::pow( vectOffset.x(), 2 ) + std::pow( vectOffset.y(), 2 ) );
3536 }
3537
3538 double scaleFactor = 1.0;
3539 const QString uom = element.attribute( QStringLiteral( "uom" ) );
3540 QgsUnitTypes::RenderUnit sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( uom, &scaleFactor );
3541 size = size * scaleFactor;
3542 lineWidth = lineWidth * scaleFactor;
3543
3544 std::unique_ptr< QgsLinePatternFillSymbolLayer > sl = std::make_unique< QgsLinePatternFillSymbolLayer >();
3545 sl->setOutputUnit( sldUnitSize );
3546 sl->setColor( lineColor );
3547 sl->setLineWidth( lineWidth );
3548 sl->setLineAngle( angle );
3549 sl->setOffset( offset );
3550 sl->setDistance( size );
3551
3552 // try to get the stroke
3553 QDomElement strokeElem = element.firstChildElement( QStringLiteral( "Stroke" ) );
3554 if ( !strokeElem.isNull() )
3555 {
3557 if ( l )
3558 {
3559 QgsSymbolLayerList layers;
3560 layers.append( l );
3561 sl->setSubSymbol( new QgsLineSymbol( layers ) );
3562 }
3563 }
3564
3565 return sl.release();
3566}
3567
3568
3570
3573{
3574 mMarkerSymbol = std::make_unique<QgsMarkerSymbol>();
3575 QgsImageFillSymbolLayer::setSubSymbol( nullptr ); //no stroke
3576}
3577
3579
3581{
3583 mDistanceXUnit = unit;
3584 mDistanceYUnit = unit;
3585 // don't change "percentage" units -- since they adapt directly to whatever other unit is set
3587 mDisplacementXUnit = unit;
3589 mDisplacementYUnit = unit;
3591 mOffsetXUnit = unit;
3593 mOffsetYUnit = unit;
3595 mRandomDeviationXUnit = unit;
3597 mRandomDeviationYUnit = unit;
3598
3599 if ( mMarkerSymbol )
3600 {
3601 mMarkerSymbol->setOutputUnit( unit );
3602 }
3603}
3604
3606{
3608 if ( mDistanceXUnit != unit ||
3609 mDistanceYUnit != unit ||
3616 {
3618 }
3619 return unit;
3620}
3621
3623{
3632}
3633
3635{
3637 mDistanceXMapUnitScale = scale;
3638 mDistanceYMapUnitScale = scale;
3641 mOffsetXMapUnitScale = scale;
3642 mOffsetYMapUnitScale = scale;
3645}
3646
3648{
3657 {
3659 }
3660 return QgsMapUnitScale();
3661}
3662
3664{
3665 std::unique_ptr< QgsPointPatternFillSymbolLayer > layer = std::make_unique< QgsPointPatternFillSymbolLayer >();
3666 if ( properties.contains( QStringLiteral( "distance_x" ) ) )
3667 {
3668 layer->setDistanceX( properties[QStringLiteral( "distance_x" )].toDouble() );
3669 }
3670 if ( properties.contains( QStringLiteral( "distance_y" ) ) )
3671 {
3672 layer->setDistanceY( properties[QStringLiteral( "distance_y" )].toDouble() );
3673 }
3674 if ( properties.contains( QStringLiteral( "displacement_x" ) ) )
3675 {
3676 layer->setDisplacementX( properties[QStringLiteral( "displacement_x" )].toDouble() );
3677 }
3678 if ( properties.contains( QStringLiteral( "displacement_y" ) ) )
3679 {
3680 layer->setDisplacementY( properties[QStringLiteral( "displacement_y" )].toDouble() );
3681 }
3682 if ( properties.contains( QStringLiteral( "offset_x" ) ) )
3683 {
3684 layer->setOffsetX( properties[QStringLiteral( "offset_x" )].toDouble() );
3685 }
3686 if ( properties.contains( QStringLiteral( "offset_y" ) ) )
3687 {
3688 layer->setOffsetY( properties[QStringLiteral( "offset_y" )].toDouble() );
3689 }
3690
3691 if ( properties.contains( QStringLiteral( "distance_x_unit" ) ) )
3692 {
3693 layer->setDistanceXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_x_unit" )].toString() ) );
3694 }
3695 if ( properties.contains( QStringLiteral( "distance_x_map_unit_scale" ) ) )
3696 {
3697 layer->setDistanceXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_x_map_unit_scale" )].toString() ) );
3698 }
3699 if ( properties.contains( QStringLiteral( "distance_y_unit" ) ) )
3700 {
3701 layer->setDistanceYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "distance_y_unit" )].toString() ) );
3702 }
3703 if ( properties.contains( QStringLiteral( "distance_y_map_unit_scale" ) ) )
3704 {
3705 layer->setDistanceYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "distance_y_map_unit_scale" )].toString() ) );
3706 }
3707 if ( properties.contains( QStringLiteral( "displacement_x_unit" ) ) )
3708 {
3709 layer->setDisplacementXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_x_unit" )].toString() ) );
3710 }
3711 if ( properties.contains( QStringLiteral( "displacement_x_map_unit_scale" ) ) )
3712 {
3713 layer->setDisplacementXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_x_map_unit_scale" )].toString() ) );
3714 }
3715 if ( properties.contains( QStringLiteral( "displacement_y_unit" ) ) )
3716 {
3717 layer->setDisplacementYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "displacement_y_unit" )].toString() ) );
3718 }
3719 if ( properties.contains( QStringLiteral( "displacement_y_map_unit_scale" ) ) )
3720 {
3721 layer->setDisplacementYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "displacement_y_map_unit_scale" )].toString() ) );
3722 }
3723 if ( properties.contains( QStringLiteral( "offset_x_unit" ) ) )
3724 {
3725 layer->setOffsetXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_x_unit" )].toString() ) );
3726 }
3727 if ( properties.contains( QStringLiteral( "offset_x_map_unit_scale" ) ) )
3728 {
3729 layer->setOffsetXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_x_map_unit_scale" )].toString() ) );
3730 }
3731 if ( properties.contains( QStringLiteral( "offset_y_unit" ) ) )
3732 {
3733 layer->setOffsetYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "offset_y_unit" )].toString() ) );
3734 }
3735 if ( properties.contains( QStringLiteral( "offset_y_map_unit_scale" ) ) )
3736 {
3737 layer->setOffsetYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "offset_y_map_unit_scale" )].toString() ) );
3738 }
3739
3740 if ( properties.contains( QStringLiteral( "random_deviation_x" ) ) )
3741 {
3742 layer->setMaximumRandomDeviationX( properties[QStringLiteral( "random_deviation_x" )].toDouble() );
3743 }
3744 if ( properties.contains( QStringLiteral( "random_deviation_y" ) ) )
3745 {
3746 layer->setMaximumRandomDeviationY( properties[QStringLiteral( "random_deviation_y" )].toDouble() );
3747 }
3748 if ( properties.contains( QStringLiteral( "random_deviation_x_unit" ) ) )
3749 {
3750 layer->setRandomDeviationXUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_x_unit" )].toString() ) );
3751 }
3752 if ( properties.contains( QStringLiteral( "random_deviation_x_map_unit_scale" ) ) )
3753 {
3754 layer->setRandomDeviationXMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_x_map_unit_scale" )].toString() ) );
3755 }
3756 if ( properties.contains( QStringLiteral( "random_deviation_y_unit" ) ) )
3757 {
3758 layer->setRandomDeviationYUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "random_deviation_y_unit" )].toString() ) );
3759 }
3760 if ( properties.contains( QStringLiteral( "random_deviation_y_map_unit_scale" ) ) )
3761 {
3762 layer->setRandomDeviationYMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "random_deviation_y_map_unit_scale" )].toString() ) );
3763 }
3764 unsigned long seed = 0;
3765 if ( properties.contains( QStringLiteral( "seed" ) ) )
3766 seed = properties.value( QStringLiteral( "seed" ) ).toUInt();
3767 else
3768 {
3769 // if we a creating a new point pattern fill from scratch, we default to a random seed
3770 // because seed based fills are just nicer for users vs seeing points jump around with every map refresh
3771 std::random_device rd;
3772 std::mt19937 mt( seed == 0 ? rd() : seed );
3773 std::uniform_int_distribution<> uniformDist( 1, 999999999 );
3774 seed = uniformDist( mt );
3775 }
3776 layer->setSeed( seed );
3777
3778 if ( properties.contains( QStringLiteral( "outline_width_unit" ) ) )
3779 {
3780 layer->setStrokeWidthUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "outline_width_unit" )].toString() ) );
3781 }
3782 if ( properties.contains( QStringLiteral( "outline_width_map_unit_scale" ) ) )
3783 {
3784 layer->setStrokeWidthMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "outline_width_map_unit_scale" )].toString() ) );
3785 }
3786 if ( properties.contains( QStringLiteral( "clip_mode" ) ) )
3787 {
3788 layer->setClipMode( QgsSymbolLayerUtils::decodeMarkerClipMode( properties.value( QStringLiteral( "clip_mode" ) ).toString() ) );
3789 }
3790 if ( properties.contains( QStringLiteral( "coordinate_reference" ) ) )
3791 {
3792 layer->setCoordinateReference( QgsSymbolLayerUtils::decodeCoordinateReference( properties[QStringLiteral( "coordinate_reference" )].toString() ) );
3793 }
3794
3795 if ( properties.contains( QStringLiteral( "angle" ) ) )
3796 {
3797 layer->setAngle( properties[QStringLiteral( "angle" )].toDouble() );
3798 }
3799
3800 layer->restoreOldDataDefinedProperties( properties );
3801
3802 return layer.release();
3803}
3804
3806{
3807 return QStringLiteral( "PointPatternFill" );
3808}
3809
3810void QgsPointPatternFillSymbolLayer::applyPattern( const QgsSymbolRenderContext &context, QBrush &brush, double distanceX, double distanceY,
3811 double displacementX, double displacementY, double offsetX, double offsetY )
3812{
3813 //render 3 rows and columns in one go to easily incorporate displacement
3814 const QgsRenderContext &ctx = context.renderContext();
3817
3818 double widthOffset = std::fmod(
3820 width );
3821 double heightOffset = std::fmod(
3823 height );
3824
3825 if ( width > 10000 || height > 10000 ) //protect symbol layer from eating too much memory
3826 {
3827 QImage img;
3828 brush.setTextureImage( img );
3829 return;
3830 }
3831
3832 QImage patternImage( width, height, QImage::Format_ARGB32 );
3833 patternImage.fill( 0 );
3834 if ( patternImage.isNull() )
3835 {
3836 brush.setTextureImage( QImage() );
3837 return;
3838 }
3839 if ( mMarkerSymbol )
3840 {
3841 QPainter p( &patternImage );
3842
3843 //marker rendering needs context for drawing on patternImage
3844 QgsRenderContext pointRenderContext;
3845 pointRenderContext.setRendererScale( context.renderContext().rendererScale() );
3846 pointRenderContext.setPainter( &p );
3847 pointRenderContext.setScaleFactor( context.renderContext().scaleFactor() );
3848
3851 pointRenderContext.setMapToPixel( mtp );
3852 pointRenderContext.setForceVectorOutput( false );
3853 pointRenderContext.setExpressionContext( context.renderContext().expressionContext() );
3855
3856 mMarkerSymbol->startRender( pointRenderContext, context.fields() );
3857
3858 //render points on distance grid
3859 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3860 {
3861 for ( double currentY = -height; currentY <= height * 2.0; currentY += height )
3862 {
3863 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset, currentY + heightOffset ), context.feature(), pointRenderContext );
3864 }
3865 }
3866
3867 //render displaced points
3868 double displacementPixelX = mDisplacementXUnit == QgsUnitTypes::RenderPercentage
3869 ? ( width * displacementX / 200 )
3871 double displacementPixelY = mDisplacementYUnit == QgsUnitTypes::RenderPercentage
3872 ? ( height * displacementY / 200 )
3874 for ( double currentX = -width; currentX <= width * 2.0; currentX += width )
3875 {
3876 for ( double currentY = -height / 2.0; currentY <= height * 2.0; currentY += height )
3877 {
3878 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + displacementPixelX, currentY + heightOffset ), context.feature(), pointRenderContext );
3879 }
3880 }
3881
3882 for ( double currentX = -width / 2.0; currentX <= width * 2.0; currentX += width )
3883 {
3884 for ( double currentY = -height; currentY <= height * 2.0; currentY += height / 2.0 )
3885 {
3886 mMarkerSymbol->renderPoint( QPointF( currentX + widthOffset + ( std::fmod( currentY, height ) != 0 ? displacementPixelX : 0 ), currentY + heightOffset - displacementPixelY ), context.feature(), pointRenderContext );
3887 }
3888 }
3889
3890 mMarkerSymbol->stopRender( pointRenderContext );
3891 }
3892
3893 if ( !qgsDoubleNear( context.opacity(), 1.0 ) )
3894 {
3895 QImage transparentImage = patternImage.copy();
3896 QgsSymbolLayerUtils::multiplyImageOpacity( &transparentImage, context.opacity() );
3897 brush.setTextureImage( transparentImage );
3898 }
3899 else
3900 {
3901 brush.setTextureImage( patternImage );
3902 }
3903 QTransform brushTransform;
3904 brush.setTransform( brushTransform );
3905}
3906
3908{
3909 // if we are using a vector based output, we need to render points as vectors
3910 // (OR if the marker has data defined symbology, in which case we need to evaluate this point-by-point)
3911 mRenderUsingMarkers = context.renderContext().forceVectorOutput()
3912 || mMarkerSymbol->hasDataDefinedProperties()
3916 || mClipMode != Qgis::MarkerClipMode::Shape
3919 || !qgsDoubleNear( mAngle, 0 )
3921
3922 if ( mRenderUsingMarkers )
3923 {
3924 mMarkerSymbol->startRender( context.renderContext() );
3925 }
3926 else
3927 {
3928 // optimised render for screen only, use image based brush
3930 }
3931}
3932
3934{
3935 if ( mRenderUsingMarkers )
3936 {
3937 mMarkerSymbol->stopRender( context.renderContext() );
3938 }
3939}
3940
3942{
3943 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3944 // Otherwise generators used in the subsymbol will only render a single point per feature (they
3945 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
3946}
3947
3949{
3950 // The base class version passes this on to the subsymbol, but we deliberately don't do that here.
3951 // Otherwise generators used in the subsymbol will only render a single point per feature (they
3952 // have logic to only render once per paired call to startFeatureRender/stopFeatureRender).
3953}
3954
3955void QgsPointPatternFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
3956{
3957 if ( !mRenderUsingMarkers )
3958 {
3959 // use image based brush for speed
3960 QgsImageFillSymbolLayer::renderPolygon( points, rings, context );
3961 return;
3962 }
3963
3964 // vector based output - so draw dot by dot!
3965 QPainter *p = context.renderContext().painter();
3966 if ( !p )
3967 {
3968 return;
3969 }
3970
3971 double angle = mAngle;
3973 {
3976 }
3977
3978 double distanceX = mDistanceX;
3980 {
3983 }
3985
3986 double distanceY = mDistanceY;
3988 {
3991 }
3993
3994 double offsetX = mOffsetX;
3996 {
3999 }
4000 const double widthOffset = std::fmod(
4002 ? ( offsetX * width / 100 )
4004 width );
4005
4006 double offsetY = mOffsetY;
4008 {
4011 }
4012 const double heightOffset = std::fmod(
4014 ? ( offsetY * height / 100 )
4016 height );
4017
4020 {
4023 }
4024 const double displacementPixelX = mDisplacementXUnit == QgsUnitTypes::RenderPercentage
4025 ? ( displacementX * width / 100 )
4027
4030 {
4033 }
4034 const double displacementPixelY = mDisplacementYUnit == QgsUnitTypes::RenderPercentage
4035 ? ( displacementY * height / 100 )
4037
4038 p->setPen( QPen( Qt::NoPen ) );
4039
4040 if ( context.selected() )
4041 {
4042 QColor selColor = context.renderContext().selectionColor();
4043 p->setBrush( QBrush( selColor ) );
4044 _renderPolygon( p, points, rings, context );
4045 }
4046
4047 // if invalid parameters, skip out
4048 if ( qgsDoubleNear( width, 0 ) || qgsDoubleNear( height, 0 ) || width < 0 || height < 0 )
4049 return;
4050
4051 p->save();
4052
4053 Qgis::MarkerClipMode clipMode = mClipMode;
4055 {
4057 bool ok = false;
4058 const QString valueString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyMarkerClipping, context.renderContext().expressionContext(), QString(), &ok );
4059 if ( ok )
4060 {
4061 Qgis::MarkerClipMode decodedMode = QgsSymbolLayerUtils::decodeMarkerClipMode( valueString, &ok );
4062 if ( ok )
4063 clipMode = decodedMode;
4064 }
4065 }
4066
4067 std::unique_ptr< QgsPolygon > shapePolygon;
4068 std::unique_ptr< QgsGeometryEngine > shapeEngine;
4069 switch ( clipMode )
4070 {
4074 {
4075 shapePolygon = std::make_unique< QgsPolygon >();
4076 shapePolygon->setExteriorRing( QgsLineString::fromQPolygonF( points ) );
4077 if ( rings )
4078 {
4079 for ( const QPolygonF &ring : *rings )
4080 {
4081 shapePolygon->addInteriorRing( QgsLineString::fromQPolygonF( ring ) );
4082 }
4083 }
4084 shapeEngine.reset( QgsGeometry::createGeometryEngine( shapePolygon.get() ) );
4085 shapeEngine->prepareGeometry();
4086 break;
4087 }
4088
4090 {
4091 QPainterPath path;
4092 path.addPolygon( points );
4093 if ( rings )
4094 {
4095 for ( const QPolygonF &ring : *rings )
4096 {
4097 path.addPolygon( ring );
4098 }
4099 }
4100 p->setClipPath( path, Qt::IntersectClip );
4101 break;
4102 }
4103 }
4104
4105 const bool applyBrushTransform = applyBrushTransformFromContext( &context );
4106 const QRectF boundingRect = points.boundingRect();
4107
4108 QTransform invertedRotateTransform;
4109 double left;
4110 double top;
4111 double right;
4112 double bottom;
4113
4114 if ( !qgsDoubleNear( angle, 0 ) )
4115 {
4116 QTransform transform;
4117 if ( applyBrushTransform )
4118 {
4119 // rotation applies around center of feature
4120 transform.translate( -boundingRect.center().x(),
4121 -boundingRect.center().y() );
4122 transform.rotate( -angle );
4123 transform.translate( boundingRect.center().x(),
4124 boundingRect.center().y() );
4125 }
4126 else
4127 {
4128 // rotation applies around top of viewport
4129 transform.rotate( -angle );
4130 }
4131
4132 const QRectF transformedBounds = transform.map( points ).boundingRect();
4133 left = transformedBounds.left() - 2 * width;
4134 top = transformedBounds.top() - 2 * height;
4135 right = transformedBounds.right() + 2 * width;
4136 bottom = transformedBounds.bottom() + 2 * height;
4137 invertedRotateTransform = transform.inverted();
4138
4139 if ( !applyBrushTransform )
4140 {
4141 left -= transformedBounds.left() - ( width * std::floor( transformedBounds.left() / width ) );
4142 top -= transformedBounds.top() - ( height * std::floor( transformedBounds.top() / height ) );
4143 }
4144 }
4145 else
4146 {
4147 left = boundingRect.left() - 2 * width;
4148 top = boundingRect.top() - 2 * height;
4149 right = boundingRect.right() + 2 * width;
4150 bottom = boundingRect.bottom() + 2 * height;
4151
4152 if ( !applyBrushTransform )
4153 {
4154 left -= boundingRect.left() - ( width * std::floor( boundingRect.left() / width ) );
4155 top -= boundingRect.top() - ( height * std::floor( boundingRect.top() / height ) );
4156 }
4157 }
4158
4159 unsigned long seed = mSeed;
4161 {
4162 context.renderContext().expressionContext().setOriginalValueVariable( static_cast< unsigned long long >( seed ) );
4164 }
4165
4166 double maxRandomDeviationX = mRandomDeviationX;
4168 {
4169 context.setOriginalValueVariable( maxRandomDeviationX );
4170 maxRandomDeviationX = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetX, context.renderContext().expressionContext(), maxRandomDeviationX );
4171 }
4172 const double maxRandomDeviationPixelX = mRandomDeviationXUnit == QgsUnitTypes::RenderPercentage ? ( maxRandomDeviationX * width / 100 )
4174
4175 double maxRandomDeviationY = mRandomDeviationY;
4177 {
4178 context.setOriginalValueVariable( maxRandomDeviationY );
4179 maxRandomDeviationY = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyRandomOffsetY, context.renderContext().expressionContext(), maxRandomDeviationY );
4180 }
4181 const double maxRandomDeviationPixelY = mRandomDeviationYUnit == QgsUnitTypes::RenderPercentage ? ( maxRandomDeviationY * height / 100 )
4183
4184 std::random_device rd;
4185 std::mt19937 mt( seed == 0 ? rd() : seed );
4186 std::uniform_real_distribution<> uniformDist( 0, 1 );
4187 const bool useRandomShift = !qgsDoubleNear( maxRandomDeviationPixelX, 0 ) || !qgsDoubleNear( maxRandomDeviationPixelY, 0 );
4188
4190 QgsExpressionContextScopePopper scopePopper( context.renderContext().expressionContext(), scope );
4191 int pointNum = 0;
4192 const bool needsExpressionContext = mMarkerSymbol->hasDataDefinedProperties();
4193
4194 const bool prevIsSubsymbol = context.renderContext().flags() & Qgis::RenderContextFlag::RenderingSubSymbol;
4196
4197 const double prevOpacity = mMarkerSymbol->opacity();
4198 mMarkerSymbol->setOpacity( mMarkerSymbol->opacity() * context.opacity() );
4199
4200 bool alternateColumn = false;
4201 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
4202 for ( double currentX = left; currentX <= right; currentX += width, alternateColumn = !alternateColumn )
4203 {
4204 if ( context.renderContext().renderingStopped() )
4205 break;
4206
4207 if ( needsExpressionContext )
4208 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_column" ), ++currentCol, true ) );
4209
4210 bool alternateRow = false;
4211 const double columnX = currentX + widthOffset;
4212 int currentRow = -3;
4213 for ( double currentY = top; currentY <= bottom; currentY += height, alternateRow = !alternateRow )
4214 {
4215 if ( context.renderContext().renderingStopped() )
4216 break;
4217
4218 double y = currentY + heightOffset;
4219 double x = columnX;
4220 if ( alternateRow )
4221 x += displacementPixelX;
4222
4223 if ( !alternateColumn )
4224 y -= displacementPixelY;
4225
4226 if ( !qgsDoubleNear( angle, 0 ) )
4227 {
4228 double xx = x;
4229 double yy = y;
4230 invertedRotateTransform.map( xx, yy, &x, &y );
4231 }
4232
4233 if ( useRandomShift )
4234 {
4235 x += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelX;
4236 y += ( 2 * uniformDist( mt ) - 1 ) * maxRandomDeviationPixelY;
4237 }
4238
4239 if ( needsExpressionContext )
4240 {
4242 scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_row" ), ++currentRow, true ) );
4243 }
4244
4245 if ( shapeEngine )
4246 {
4247 bool renderPoint = true;
4248 switch ( clipMode )
4249 {
4251 {
4252 // 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
4253 const QgsRectangle markerRect = QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) );
4254 QgsPoint p( markerRect.center() );
4255 renderPoint = shapeEngine->intersects( &p );
4256 break;
4257 }
4258
4261 {
4262 const QgsGeometry markerBounds = QgsGeometry::fromRect( QgsRectangle( mMarkerSymbol->bounds( QPointF( x, y ), context.renderContext(), context.feature() ? *context.feature() : QgsFeature() ) ) );
4263
4265 renderPoint = shapeEngine->contains( markerBounds.constGet() );
4266 else
4267 renderPoint = shapeEngine->intersects( markerBounds.constGet() );
4268 break;
4269 }
4270
4272 break;
4273 }
4274
4275 if ( !renderPoint )
4276 continue;
4277 }
4278
4279 mMarkerSymbol->renderPoint( QPointF( x, y ), context.feature(), context.renderContext() );
4280 }
4281 }
4282
4283 mMarkerSymbol->setOpacity( prevOpacity );
4284
4285 p->restore();
4286
4288}
4289
4291{
4292 QVariantMap map = QgsImageFillSymbolLayer::properties();
4293 map.insert( QStringLiteral( "distance_x" ), QString::number( mDistanceX ) );
4294 map.insert( QStringLiteral( "distance_y" ), QString::number( mDistanceY ) );
4295 map.insert( QStringLiteral( "displacement_x" ), QString::number( mDisplacementX ) );
4296 map.insert( QStringLiteral( "displacement_y" ), QString::number( mDisplacementY ) );
4297 map.insert( QStringLiteral( "offset_x" ), QString::number( mOffsetX ) );
4298 map.insert( QStringLiteral( "offset_y" ), QString::number( mOffsetY ) );
4299 map.insert( QStringLiteral( "distance_x_unit" ), QgsUnitTypes::encodeUnit( mDistanceXUnit ) );
4300 map.insert( QStringLiteral( "distance_y_unit" ), QgsUnitTypes::encodeUnit( mDistanceYUnit ) );
4301 map.insert( QStringLiteral( "displacement_x_unit" ), QgsUnitTypes::encodeUnit( mDisplacementXUnit ) );
4302 map.insert( QStringLiteral( "displacement_y_unit" ), QgsUnitTypes::encodeUnit( mDisplacementYUnit ) );
4303 map.insert( QStringLiteral( "offset_x_unit" ), QgsUnitTypes::encodeUnit( mOffsetXUnit ) );
4304 map.insert( QStringLiteral( "offset_y_unit" ), QgsUnitTypes::encodeUnit( mOffsetYUnit ) );
4305 map.insert( QStringLiteral( "distance_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceXMapUnitScale ) );
4306 map.insert( QStringLiteral( "distance_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDistanceYMapUnitScale ) );
4307 map.insert( QStringLiteral( "displacement_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementXMapUnitScale ) );
4308 map.insert( QStringLiteral( "displacement_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDisplacementYMapUnitScale ) );
4309 map.insert( QStringLiteral( "offset_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetXMapUnitScale ) );
4310 map.insert( QStringLiteral( "offset_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetYMapUnitScale ) );
4311 map.insert( QStringLiteral( "outline_width_unit" ), QgsUnitTypes::encodeUnit( mStrokeWidthUnit ) );
4312 map.insert( QStringLiteral( "outline_width_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mStrokeWidthMapUnitScale ) );
4313 map.insert( QStringLiteral( "clip_mode" ), QgsSymbolLayerUtils::encodeMarkerClipMode( mClipMode ) );
4314 map.insert( QStringLiteral( "random_deviation_x" ), QString::number( mRandomDeviationX ) );
4315 map.insert( QStringLiteral( "random_deviation_y" ), QString::number( mRandomDeviationY ) );
4316 map.insert( QStringLiteral( "random_deviation_x_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationXUnit ) );
4317 map.insert( QStringLiteral( "random_deviation_y_unit" ), QgsUnitTypes::encodeUnit( mRandomDeviationYUnit ) );
4318 map.insert( QStringLiteral( "random_deviation_x_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationXMapUnitScale ) );
4319 map.insert( QStringLiteral( "random_deviation_y_map_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mRandomDeviationYMapUnitScale ) );
4320 map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
4321 map.insert( QStringLiteral( "angle" ), mAngle );
4322 return map;
4323}
4324
4326{
4328 if ( mMarkerSymbol )
4329 {
4330 clonedLayer->setSubSymbol( mMarkerSymbol->clone() );
4331 }
4332 clonedLayer->setClipMode( mClipMode );
4333 copyDataDefinedProperties( clonedLayer );
4334 copyPaintEffect( clonedLayer );
4335 return clonedLayer;
4336}
4337
4338void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
4339{
4340 for ( int symbolLayerIdx = 0; symbolLayerIdx < mMarkerSymbol->symbolLayerCount(); symbolLayerIdx++ )
4341 {
4342 QDomElement symbolizerElem = doc.createElement( QStringLiteral( "se:PolygonSymbolizer" ) );
4343 if ( !props.value( QStringLiteral( "uom" ), QString() ).toString().isEmpty() )
4344 symbolizerElem.setAttribute( QStringLiteral( "uom" ), props.value( QStringLiteral( "uom" ), QString() ).toString() );
4345 element.appendChild( symbolizerElem );
4346
4347 // <Geometry>
4348 QgsSymbolLayerUtils::createGeometryElement( doc, symbolizerElem, props.value( QStringLiteral( "geom" ), QString() ).toString() );
4349
4350 QDomElement fillElem = doc.createElement( QStringLiteral( "se:Fill" ) );
4351 symbolizerElem.appendChild( fillElem );
4352
4353 QDomElement graphicFillElem = doc.createElement( QStringLiteral( "se:GraphicFill" ) );
4354 fillElem.appendChild( graphicFillElem );
4355
4356 QgsSymbolLayer *layer = mMarkerSymbol->symbolLayer( symbolLayerIdx );
4357
4358 const QgsSldExportContext context { props.value( QStringLiteral( "SldExportContext" ), QVariant::fromValue( QgsSldExportContext() ) ).value< QgsSldExportContext >() };
4359
4360 // Export to PNG (TODO: SVG)
4361 bool exportOk { false };
4362 if ( ! context.exportFilePath().isEmpty() && context.exportOptions().testFlag( Qgis::SldExportOption::Png ) )
4363 {
4364 const QImage image { toTiledPatternImage( ) };
4365 if ( ! image.isNull() )
4366 {
4367 QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
4368 graphicFillElem.appendChild( graphicElem );
4369 const QFileInfo info { context.exportFilePath() };
4370 QString pngPath { info.completeSuffix().isEmpty() ? context.exportFilePath() : context.exportFilePath().chopped( info.completeSuffix().length() ).append( QStringLiteral( "png" ) ) };
4371 pngPath = QgsFileUtils::uniquePath( pngPath );
4372 image.save( pngPath );
4373 QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, QFileInfo( pngPath ).fileName(), QStringLiteral( "image/png" ), QColor(), image.height() );
4374 exportOk = true;
4375 }
4376 }
4377
4378 if ( ! exportOk )
4379 {
4380 // Converts to GeoServer "graphic-margin": symbol size must be subtracted from distance and then divided by 2
4381 const double markerSize { mMarkerSymbol->size() };
4382
4383 // store distanceX, distanceY, displacementX, displacementY in a <VendorOption>
4386 // From: https://docs.geoserver.org/stable/en/user/styling/sld/extensions/margins.html
4387 // top-bottom,right-left (two values, top and bottom sharing the same value)
4388 const QString marginSpec = QString( "%1 %2" ).arg( qgsDoubleToString( ( dy - markerSize ) / 2, 2 ), qgsDoubleToString( ( dx - markerSize ) / 2, 2 ) );
4389
4390 QDomElement graphicMarginElem = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), marginSpec );
4391 symbolizerElem.appendChild( graphicMarginElem );
4392
4393 if ( QgsMarkerSymbolLayer *markerLayer = dynamic_cast<QgsMarkerSymbolLayer *>( layer ) )
4394 {
4395 markerLayer->writeSldMarker( doc, graphicFillElem, props );
4396 }
4397 else if ( layer )
4398 {
4399 QString errorMsg = QStringLiteral( "QgsMarkerSymbolLayer expected, %1 found. Skip it." ).arg( layer->layerType() );
4400 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4401 }
4402 else
4403 {
4404 QString errorMsg = QStringLiteral( "Missing point pattern symbol layer. Skip it." );
4405 graphicFillElem.appendChild( doc.createComment( errorMsg ) );
4406 }
4407 }
4408 }
4409}
4410
4412{
4413
4414 double angleRads { qDegreesToRadians( mAngle ) };
4415
4416 int distanceXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, {} ) ) };
4417 int distanceYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, {} ) ) };
4418
4419 const QSize size { QgsSymbolLayerUtils::tileSize( distanceXPx, distanceYPx, angleRads ) };
4420
4421 QPixmap pixmap( size );
4422 pixmap.fill( Qt::transparent );
4423 QPainter painter;
4424 painter.begin( &pixmap );
4425 painter.setRenderHint( QPainter::Antialiasing );
4426 QgsRenderContext renderContext = QgsRenderContext::fromQPainter( &painter );
4430 renderContext.setForceVectorOutput( true );
4431 QgsSymbolRenderContext symbolContext( renderContext, QgsUnitTypes::RenderUnit::RenderPixels, 1.0, false, Qgis::SymbolRenderHints() );
4432
4433 std::unique_ptr< QgsPointPatternFillSymbolLayer > layerClone( clone() );
4434
4435 layerClone->setAngle( qRadiansToDegrees( angleRads ) );
4436
4437 layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
4438 painter.end();
4439 return pixmap.toImage();
4440}
4441
4443{
4444
4445 // input element is PolygonSymbolizer
4446
4447 QDomElement fillElem = element.firstChildElement( QStringLiteral( "Fill" ) );
4448 if ( fillElem.isNull() )
4449 return nullptr;
4450
4451 QDomElement graphicFillElem = fillElem.firstChildElement( QStringLiteral( "GraphicFill" ) );
4452 if ( graphicFillElem.isNull() )
4453 return nullptr;
4454
4455 QDomElement graphicElem = graphicFillElem.firstChildElement( QStringLiteral( "Graphic" ) );
4456 if ( graphicElem.isNull() )
4457 return nullptr;
4458
4459 QgsSymbolLayer *simpleMarkerSl = QgsSymbolLayerUtils::createMarkerLayerFromSld( graphicFillElem );
4460 if ( !simpleMarkerSl )
4461 return nullptr;
4462
4463
4464 QgsSymbolLayerList layers;
4465 layers.append( simpleMarkerSl );
4466
4467 std::unique_ptr< QgsMarkerSymbol > marker = std::make_unique< QgsMarkerSymbol >( layers );
4468
4469 // Converts from GeoServer "graphic-margin": symbol size must be added and margin doubled
4470 const double markerSize { marker->size() };
4471
4472 std::unique_ptr< QgsPointPatternFillSymbolLayer > pointPatternFillSl = std::make_unique< QgsPointPatternFillSymbolLayer >();
4473 pointPatternFillSl->setSubSymbol( marker.release() );
4474
4475 auto distanceParser = [ & ]( const QStringList & values )
4476 {
4477
4478 // This may not be correct in all cases, TODO: check "uom"
4479 pointPatternFillSl->setDistanceXUnit( QgsUnitTypes::RenderUnit::RenderPixels );
4480 pointPatternFillSl->setDistanceYUnit( QgsUnitTypes::RenderUnit::RenderPixels );
4481
4482 switch ( values.count( ) )
4483 {
4484 case 1: // top-right-bottom-left (single value for all four margins)
4485 {
4486 bool ok;
4487 const double v { values.at( 0 ).toDouble( &ok ) };
4488 if ( ok )
4489 {
4490 pointPatternFillSl->setDistanceX( v * 2 + markerSize );
4491 pointPatternFillSl->setDistanceY( v * 2 + markerSize );
4492 }
4493 break;
4494 }
4495 case 2: // top-bottom,right-left (two values, top and bottom sharing the same value)
4496 {
4497 bool ok;
4498 const double vX { values.at( 1 ).toDouble( &ok ) };
4499 if ( ok )
4500 {
4501 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4502 }
4503 const double vY { values.at( 0 ).toDouble( &ok ) };
4504 if ( ok )
4505 {
4506 pointPatternFillSl->setDistanceY( vY * 2 + markerSize );
4507 }
4508 break;
4509 }
4510 case 3: // top,right-left,bottom (three values, with right and left sharing the same value)
4511 {
4512 bool ok;
4513 const double vX { values.at( 1 ).toDouble( &ok ) };
4514 if ( ok )
4515 {
4516 pointPatternFillSl->setDistanceX( vX * 2 + markerSize );
4517 }
4518 const double vYt { values.at( 0 ).toDouble( &ok ) };
4519 if ( ok )
4520 {
4521 const double vYb { values.at( 2 ).toDouble( &ok ) };
4522 if ( ok )
4523 {
4524 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4525 }
4526 }
4527 break;
4528 }
4529 case 4: // top,right,bottom,left (one explicit value per margin)
4530 {
4531 bool ok;
4532 const double vYt { values.at( 0 ).toDouble( &ok ) };
4533 if ( ok )
4534 {
4535 const double vYb { values.at( 2 ).toDouble( &ok ) };
4536 if ( ok )
4537 {
4538 pointPatternFillSl->setDistanceY( ( vYt + vYb ) + markerSize );
4539 }
4540 }
4541 const double vXr { values.at( 1 ).toDouble( &ok ) };
4542 if ( ok )
4543 {
4544 const double vXl { values.at( 3 ).toDouble( &ok ) };
4545 if ( ok )
4546 {
4547 pointPatternFillSl->setDistanceX( ( vXr + vXl ) + markerSize );
4548 }
4549 }
4550 break;
4551 }
4552 default:
4553 break;
4554 }
4555 };
4556
4557 // Set distance X and Y from vendor options
4558 QgsStringMap vendorOptions = QgsSymbolLayerUtils::getVendorOptionList( element );
4559 for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
4560 {
4561 // Legacy
4562 if ( it.key() == QLatin1String( "distance" ) )
4563 {
4564 distanceParser( it.value().split( ',' ) );
4565 }
4566 // GeoServer
4567 else if ( it.key() == QLatin1String( "graphic-margin" ) )
4568 {
4569 distanceParser( it.value().split( ' ' ) );
4570
4571 }
4572 }
4573 return pointPatternFillSl.release();
4574}
4575
4577{
4578 if ( !symbol )
4579 {
4580 return false;
4581 }
4582
4583 if ( symbol->type() == Qgis::SymbolType::Marker )
4584 {
4585 QgsMarkerSymbol *markerSymbol = static_cast<QgsMarkerSymbol *>( symbol );
4586 mMarkerSymbol.reset( markerSymbol );
4587 }
4588 return true;
4589}
4590
4592{
4593 return mMarkerSymbol.get();
4594}
4595
4597{
4601 && ( !mMarkerSymbol || !mMarkerSymbol->hasDataDefinedProperties() ) )
4602 {
4603 return;
4604 }
4605
4606 double distanceX = mDistanceX;
4608 {
4611 }
4612 double distanceY = mDistanceY;
4614 {
4617 }
4620 {
4623 }
4626 {
4629 }
4630 double offsetX = mOffsetX;
4632 {
4635 }
4636 double offsetY = mOffsetY;
4638 {
4641 }
4642 applyPattern( context, mBrush, distanceX, distanceY, displacementX, displacementY, offsetX, offsetY );
4643}
4644
4646{
4647 return 0;
4648}
4649
4651{
4652 QSet<QString> attributes = QgsImageFillSymbolLayer::usedAttributes( context );
4653
4654 if ( mMarkerSymbol )
4655 attributes.unite( mMarkerSymbol->usedAttributes( context ) );
4656
4657 return attributes;
4658}
4659
4661{
4663 return true;
4664 if ( mMarkerSymbol && mMarkerSymbol->hasDataDefinedProperties() )
4665 return true;
4666 return false;
4667}
4668
4670{
4671 mColor = c;
4672 if ( mMarkerSymbol )
4673 mMarkerSymbol->setColor( c );
4674}
4675
4677{
4678 return mMarkerSymbol ? mMarkerSymbol->color() : mColor;
4679}
4680
4682
4683
4685{
4687}
4688
4690
4692{
4693 std::unique_ptr< QgsCentroidFillSymbolLayer > sl = std::make_unique< QgsCentroidFillSymbolLayer >();
4694
4695 if ( properties.contains( QStringLiteral( "point_on_surface" ) ) )
4696 sl->setPointOnSurface( properties[QStringLiteral( "point_on_surface" )].toInt() != 0 );
4697 if ( properties.contains( QStringLiteral( "point_on_all_parts" ) ) )
4698 sl->setPointOnAllParts( properties[QStringLiteral( "point_on_all_parts" )].toInt() != 0 );
4699 if ( properties.contains( QStringLiteral( "clip_points" ) ) )
4700 sl->setClipPoints( properties[QStringLiteral( "clip_points" )].toInt() != 0 );
4701 if ( properties.contains( QStringLiteral( "clip_on_current_part_only" ) ) )
4702 sl->setClipOnCurrentPartOnly( properties[QStringLiteral( "clip_on_current_part_only" )].toInt() != 0 );
4703
4704 sl->restoreOldDataDefinedProperties( properties );
4705
4706 return sl.release();
4707}
4708
4710{
4711 return QStringLiteral( "CentroidFill" );
4712}
4713
4714void QgsCentroidFillSymbolLayer::setColor( const QColor &color )
4715{
4716 mMarker->setColor( color );
4717 mColor = color;
4718}
4719
4721{
4722 return mMarker ? mMarker->color() : mColor;
4723}
4724
4726{
4727 mMarker->startRender( context.renderContext(), context.fields() );
4728}
4729
4731{
4732 mMarker->stopRender( context.renderContext() );
4733}
4734
4735void QgsCentroidFillSymbolLayer::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, QgsSymbolRenderContext &context )
4736{
4737 Part part;
4738 part.exterior = points;
4739 if ( rings )
4740 part.rings = *rings;
4741
4742 if ( mRenderingFeature )
4743 {
4744 // in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
4745 // until after we've received the final part
4746 mFeatureSymbolOpacity = context.opacity();
4747 mCurrentParts << part;
4748 }
4749 else
4750 {
4751 // not rendering a feature, so we can just render the polygon immediately
4752 const double prevOpacity = mMarker->opacity();
4753 mMarker->setOpacity( mMarker->opacity() * context.opacity() );
4754 render( context.renderContext(), QVector<Part>() << part, context.feature() ? *context.feature() : QgsFeature(), context.selected() );
4755 mMarker->setOpacity( prevOpacity );
4756 }
4757}
4758
4760{
4761 mRenderingFeature = true;