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