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