Skip to content

Commit 8341b9b

Browse files
committed
Safer memory management for shapeburst fills
Fail gracefully if temporary image could not be allocated TODO: use a maximum size for the image, and downsample to match
1 parent c013c23 commit 8341b9b

File tree

3 files changed

+58
-41
lines changed

3 files changed

+58
-41
lines changed

python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in

+2-7
Original file line numberDiff line numberDiff line change
@@ -653,13 +653,8 @@ Returns the units used for the offset of the shapeburst fill.
653653
virtual QgsMapUnitScale mapUnitScale() const;
654654

655655

656-
protected:
657-
658-
659-
660-
661-
662-
656+
private:
657+
QgsShapeburstFillSymbolLayer( const QgsShapeburstFillSymbolLayer &other );
663658
};
664659

665660
class QgsImageFillSymbolLayer: QgsFillSymbolLayer

src/core/symbology/qgsfillsymbollayer.cpp

+33-25
Original file line numberDiff line numberDiff line change
@@ -947,10 +947,7 @@ QgsShapeburstFillSymbolLayer::QgsShapeburstFillSymbolLayer( const QColor &color,
947947
mColor = color;
948948
}
949949

950-
QgsShapeburstFillSymbolLayer::~QgsShapeburstFillSymbolLayer()
951-
{
952-
delete mGradientRamp;
953-
}
950+
QgsShapeburstFillSymbolLayer::~QgsShapeburstFillSymbolLayer() = default;
954951

955952
QgsSymbolLayer *QgsShapeburstFillSymbolLayer::create( const QgsStringMap &props )
956953
{
@@ -1054,8 +1051,10 @@ QString QgsShapeburstFillSymbolLayer::layerType() const
10541051

10551052
void QgsShapeburstFillSymbolLayer::setColorRamp( QgsColorRamp *ramp )
10561053
{
1057-
delete mGradientRamp;
1058-
mGradientRamp = ramp;
1054+
if ( mGradientRamp.get() == ramp )
1055+
return;
1056+
1057+
mGradientRamp.reset( ramp );
10591058
}
10601059

10611060
void QgsShapeburstFillSymbolLayer::applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
@@ -1169,9 +1168,10 @@ void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, QList
11691168
}
11701169

11711170
//if we are using the two color mode, create a gradient ramp
1171+
std::unique_ptr< QgsGradientColorRamp > twoColorGradientRamp;
11721172
if ( mColorType == QgsShapeburstFillSymbolLayer::SimpleTwoColor )
11731173
{
1174-
mTwoColorGradientRamp = new QgsGradientColorRamp( color1, color2 );
1174+
twoColorGradientRamp = qgis::make_unique< QgsGradientColorRamp >( color1, color2 );
11751175
}
11761176

11771177
//no stroke for shapeburst fills
@@ -1180,23 +1180,37 @@ void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, QList
11801180
//calculate margin size in pixels so that QImage of polygon has sufficient space to draw the full blur effect
11811181
int sideBuffer = 4 + ( blurRadius + 2 ) * 4;
11821182
//create a QImage to draw shapeburst in
1183-
double imWidth = points.boundingRect().width() + ( sideBuffer * 2 );
1184-
double imHeight = points.boundingRect().height() + ( sideBuffer * 2 );
1185-
QImage *fillImage = new QImage( imWidth,
1186-
imHeight, QImage::Format_ARGB32_Premultiplied );
1183+
int pointsWidth = static_cast< int >( std::round( points.boundingRect().width() ) );
1184+
int pointsHeight = static_cast< int >( std::round( points.boundingRect().height() ) );
1185+
int imWidth = pointsWidth + ( sideBuffer * 2 );
1186+
int imHeight = pointsHeight + ( sideBuffer * 2 );
1187+
std::unique_ptr< QImage > fillImage = qgis::make_unique< QImage >( imWidth,
1188+
imHeight, QImage::Format_ARGB32_Premultiplied );
1189+
if ( fillImage->isNull() )
1190+
{
1191+
QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1192+
return;
1193+
}
1194+
1195+
//also create an image to store the alpha channel
1196+
std::unique_ptr< QImage > alphaImage = qgis::make_unique< QImage >( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
1197+
if ( alphaImage->isNull() )
1198+
{
1199+
QgsMessageLog::logMessage( QObject::tr( "Could not allocate sufficient memory for shapeburst fill" ) );
1200+
return;
1201+
}
1202+
11871203
//Fill this image with black. Initially the distance transform is drawn in greyscale, where black pixels have zero distance from the
11881204
//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
11891205
//polygon in white. The distance transform function then fills in the correct distance values for the white pixels.
11901206
fillImage->fill( Qt::black );
11911207

1192-
//also create an image to store the alpha channel
1193-
QImage *alphaImage = new QImage( fillImage->width(), fillImage->height(), QImage::Format_ARGB32_Premultiplied );
11941208
//initially fill the alpha channel image with a transparent color
11951209
alphaImage->fill( Qt::transparent );
11961210

11971211
//now, draw the polygon in the alpha channel image
11981212
QPainter imgPainter;
1199-
imgPainter.begin( alphaImage );
1213+
imgPainter.begin( alphaImage.get() );
12001214
imgPainter.setRenderHint( QPainter::Antialiasing, true );
12011215
imgPainter.setBrush( QBrush( Qt::white ) );
12021216
imgPainter.setPen( QPen( Qt::black ) );
@@ -1206,7 +1220,7 @@ void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, QList
12061220

12071221
//now that we have a render of the polygon in white, draw this onto the shapeburst fill image too
12081222
//(this avoids calling _renderPolygon twice, since that can be slow)
1209-
imgPainter.begin( fillImage );
1223+
imgPainter.begin( fillImage.get() );
12101224
if ( !ignoreRings )
12111225
{
12121226
imgPainter.drawImage( 0, 0, *alphaImage );
@@ -1224,18 +1238,14 @@ void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, QList
12241238
imgPainter.end();
12251239

12261240
//apply distance transform to image, uses the current color ramp to calculate final pixel colors
1227-
double *dtArray = distanceTransform( fillImage );
1241+
double *dtArray = distanceTransform( fillImage.get() );
12281242

12291243
//copy distance transform values back to QImage, shading by appropriate color ramp
1230-
dtArrayToQImage( dtArray, fillImage, mColorType == QgsShapeburstFillSymbolLayer::SimpleTwoColor ? mTwoColorGradientRamp : mGradientRamp,
1244+
dtArrayToQImage( dtArray, fillImage.get(), mColorType == QgsShapeburstFillSymbolLayer::SimpleTwoColor ? twoColorGradientRamp.get() : mGradientRamp.get(),
12311245
context.opacity(), useWholeShape, outputPixelMaxDist );
12321246

12331247
//clean up some variables
12341248
delete [] dtArray;
1235-
if ( mColorType == QgsShapeburstFillSymbolLayer::SimpleTwoColor )
1236-
{
1237-
delete mTwoColorGradientRamp;
1238-
}
12391249

12401250
//apply blur if desired
12411251
if ( blurRadius > 0 )
@@ -1244,12 +1254,12 @@ void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, QList
12441254
}
12451255

12461256
//apply alpha channel to distance transform image, so that areas outside the polygon are transparent
1247-
imgPainter.begin( fillImage );
1257+
imgPainter.begin( fillImage.get() );
12481258
imgPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
12491259
imgPainter.drawImage( 0, 0, *alphaImage );
12501260
imgPainter.end();
12511261
//we're finished with the alpha channel image now
1252-
delete alphaImage;
1262+
alphaImage.reset();
12531263

12541264
//draw shapeburst image in correct place in the destination painter
12551265

@@ -1264,8 +1274,6 @@ void QgsShapeburstFillSymbolLayer::renderPolygon( const QPolygonF &points, QList
12641274

12651275
p->drawImage( points.boundingRect().left() - sideBuffer, points.boundingRect().top() - sideBuffer, *fillImage );
12661276

1267-
delete fillImage;
1268-
12691277
if ( !mOffset.isNull() )
12701278
{
12711279
p->translate( -offset );

src/core/symbology/qgsfillsymbollayer.h

+23-9
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,18 @@ class CORE_EXPORT QgsShapeburstFillSymbolLayer : public QgsFillSymbolLayer
352352

353353
~QgsShapeburstFillSymbolLayer() override;
354354

355+
/**
356+
* QgsShapeburstFillSymbolLayer cannot be copied.
357+
* \see clone()
358+
*/
359+
QgsShapeburstFillSymbolLayer( const QgsShapeburstFillSymbolLayer &other ) = delete;
360+
361+
/**
362+
* QgsShapeburstFillSymbolLayer cannot be copied.
363+
* \see clone()
364+
*/
365+
QgsShapeburstFillSymbolLayer &operator=( const QgsShapeburstFillSymbolLayer &other ) = delete;
366+
355367
// static stuff
356368

357369
static QgsSymbolLayer *create( const QgsStringMap &properties = QgsStringMap() ) SIP_FACTORY;
@@ -488,7 +500,7 @@ class CORE_EXPORT QgsShapeburstFillSymbolLayer : public QgsFillSymbolLayer
488500
* \see colorType
489501
* \since QGIS 2.3
490502
*/
491-
QgsColorRamp *colorRamp() { return mGradientRamp; }
503+
QgsColorRamp *colorRamp() { return mGradientRamp.get(); }
492504

493505
/**
494506
* Sets the color for the endpoint of the shapeburst fill. This color is only used if setColorType is set ShapeburstColorType::SimpleTwoColor.
@@ -570,29 +582,27 @@ class CORE_EXPORT QgsShapeburstFillSymbolLayer : public QgsFillSymbolLayer
570582
void setMapUnitScale( const QgsMapUnitScale &scale ) override;
571583
QgsMapUnitScale mapUnitScale() const override;
572584

573-
protected:
585+
private:
574586
QBrush mBrush;
575587
QBrush mSelBrush;
576588

577-
int mBlurRadius;
589+
int mBlurRadius = 0;
578590

579-
bool mUseWholeShape;
580-
double mMaxDistance;
591+
bool mUseWholeShape = true;
592+
double mMaxDistance = 5.0;
581593
QgsUnitTypes::RenderUnit mDistanceUnit = QgsUnitTypes::RenderMillimeters;
582594
QgsMapUnitScale mDistanceMapUnitScale;
583595

584-
ShapeburstColorType mColorType;
596+
ShapeburstColorType mColorType = SimpleTwoColor;
585597
QColor mColor2;
586-
QgsColorRamp *mGradientRamp = nullptr;
587-
QgsColorRamp *mTwoColorGradientRamp = nullptr;
588598

589599
bool mIgnoreRings = false;
590600

591601
QPointF mOffset;
592602
QgsUnitTypes::RenderUnit mOffsetUnit = QgsUnitTypes::RenderMillimeters;
593603
QgsMapUnitScale mOffsetMapUnitScale;
594604

595-
private:
605+
std::unique_ptr< QgsColorRamp > mGradientRamp;
596606

597607
//helper functions for data defined symbology
598608
void applyDataDefinedSymbology( QgsSymbolRenderContext &context, QColor &color, QColor &color2, int &blurRadius, bool &useWholeShape,
@@ -607,6 +617,10 @@ class CORE_EXPORT QgsShapeburstFillSymbolLayer : public QgsFillSymbolLayer
607617

608618
/* fills a QImage with values from an array of doubles containing squared distance transform values */
609619
void dtArrayToQImage( double *array, QImage *im, QgsColorRamp *ramp, double layerAlpha = 1, bool useWholeShape = true, int maxPixelDistance = 0 );
620+
621+
#ifdef SIP_RUN
622+
QgsShapeburstFillSymbolLayer( const QgsShapeburstFillSymbolLayer &other );
623+
#endif
610624
};
611625

612626
/**

0 commit comments

Comments
 (0)