Skip to content

Commit

Permalink
[FEATURE]: SVG fill symbol layer for polygon textures
Browse files Browse the repository at this point in the history
git-svn-id: http://svn.osgeo.org/qgis/trunk@12777 c8812cc2-4d05-0410-92ff-de0c093fc19c
  • Loading branch information
mhugent committed Jan 16, 2010
1 parent 0283bdb commit 697ef55
Show file tree
Hide file tree
Showing 9 changed files with 534 additions and 26 deletions.
226 changes: 210 additions & 16 deletions src/core/symbology-ng/qgsfillsymbollayerv2.cpp
Expand Up @@ -64,22 +64,7 @@ void QgsSimpleFillSymbolLayerV2::renderPolygon( const QPolygonF& points, QList<Q
p->setBrush( mBrush );
p->setPen( mPen );

if ( rings == NULL )
{
// simple polygon without holes
p->drawPolygon( points );
}
else
{
// polygon with holes must be drawn using painter path
QPainterPath path;
path.addPolygon( points );
QList<QPolygonF>::iterator it;
for ( it = rings->begin(); it != rings->end(); ++it )
path.addPolygon( *it );

p->drawPath( path );
}
_renderPolygon( p, points, rings );
}

QgsStringMap QgsSimpleFillSymbolLayerV2::properties() const
Expand All @@ -97,3 +82,212 @@ QgsSymbolLayerV2* QgsSimpleFillSymbolLayerV2::clone() const
{
return new QgsSimpleFillSymbolLayerV2( mColor, mBrushStyle, mBorderColor, mBorderStyle, mBorderWidth );
}

//QgsSVGFillSymbolLayer
#include <QFile>
#include <QSvgRenderer>

QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QString& svgFilePath, double width ): mPatternWidth( width ), mOutline( 0 )
{
setSvgFilePath( svgFilePath );
mOutlineWidth = 0.3;
setSubSymbol( new QgsLineSymbolV2() );
}

QgsSVGFillSymbolLayer::QgsSVGFillSymbolLayer( const QByteArray& svgData, double width ): mSvgData( svgData ), mPatternWidth( width ), mOutline( 0 )
{
storeViewBox();
mOutlineWidth = 0.3;
setSubSymbol( new QgsLineSymbolV2() );
}

QgsSVGFillSymbolLayer::~QgsSVGFillSymbolLayer()
{
delete mOutline;
}

void QgsSVGFillSymbolLayer::setSvgFilePath( const QString& svgPath )
{
QFile svgFile( svgPath );
if ( svgFile.open( QFile::ReadOnly ) )
{
mSvgData = svgFile.readAll();
storeViewBox();
}
mSvgFilePath = svgPath;
}

QgsSymbolLayerV2* QgsSVGFillSymbolLayer::create( const QgsStringMap& properties )
{
QByteArray data;
double width = 20;
QString svgFilePath;


if ( properties.contains( "width" ) )
{
width = properties["width"].toDouble();
}
if ( properties.contains( "svgFile" ) )
{
svgFilePath = properties["svgFile"];
}

if ( !svgFilePath.isEmpty() )
{
return new QgsSVGFillSymbolLayer( svgFilePath, width );
}
else
{
if ( properties.contains( "data" ) )
{
data = QByteArray::fromHex( properties["data"].toLocal8Bit() );
}

return new QgsSVGFillSymbolLayer( data, width );
}
}

QString QgsSVGFillSymbolLayer::layerType() const
{
return "SVGFill";
}

void QgsSVGFillSymbolLayer::startRender( QgsSymbolV2RenderContext& context )
{
if ( mSvgViewBox.isNull() )
{
return;
}

//create QImage with appropriate dimensions
int pixelWidth = context.outputPixelSize( mPatternWidth );//mPatternWidth.value( context, QgsOutputUnit::Pixel );
int pixelHeight = pixelWidth / mSvgViewBox.width() * mSvgViewBox.height();

QImage textureImage( pixelWidth, pixelHeight, QImage::Format_ARGB32_Premultiplied );
textureImage.fill( QColor( 255, 255, 255, 0 ).rgba() );

//rasterise byte array to image
QPainter p( &textureImage );
QSvgRenderer r( mSvgData );
if ( !r.isValid() )
{
return;
}
r.render( &p );

QTransform brushTransform;
brushTransform.scale( 1.0 / context.renderContext().rasterScaleFactor(), 1.0 / context.renderContext().rasterScaleFactor() );
mBrush.setTextureImage( textureImage );
mBrush.setTransform( brushTransform );

if ( mOutline )
{
mOutline->startRender( context.renderContext() );
}
}

void QgsSVGFillSymbolLayer::stopRender( QgsSymbolV2RenderContext& context )
{
if ( mOutline )
{
mOutline->stopRender( context.renderContext() );
}
}

void QgsSVGFillSymbolLayer::renderPolygon( const QPolygonF& points, QList<QPolygonF>* rings, QgsSymbolV2RenderContext& context )
{
QPainter* p = context.renderContext().painter();
if ( !p )
{
return;
}
p->setBrush( mBrush );
p->setPen( QPen( Qt::NoPen ) );
_renderPolygon( p, points, rings );
if ( mOutline )
{
mOutline->renderPolyline( points, context.renderContext() );
if ( rings )
{
QList<QPolygonF>::const_iterator ringIt = rings->constBegin();
for ( ; ringIt != rings->constEnd(); ++ringIt )
{
mOutline->renderPolyline( *ringIt, context.renderContext() );
}
}
}
}

QgsStringMap QgsSVGFillSymbolLayer::properties() const
{
QgsStringMap map;
if ( !mSvgFilePath.isEmpty() )
{
map.insert( "svgFile", mSvgFilePath );
}
else
{
map.insert( "data", QString( mSvgData.toHex() ) );
}

map.insert( "width", QString::number( mPatternWidth ) );
return map;
}

QgsSymbolLayerV2* QgsSVGFillSymbolLayer::clone() const
{
QgsSymbolLayerV2* clonedLayer = 0;
if ( !mSvgFilePath.isEmpty() )
{
clonedLayer = new QgsSVGFillSymbolLayer( mSvgFilePath, mPatternWidth );
}
else
{
clonedLayer = new QgsSVGFillSymbolLayer( mSvgData, mPatternWidth );
}

if ( mOutline )
{
clonedLayer->setSubSymbol( mOutline->clone() );
}
return clonedLayer;
}

void QgsSVGFillSymbolLayer::storeViewBox()
{
if ( !mSvgData.isEmpty() )
{
QSvgRenderer r( mSvgData );
if ( r.isValid() )
{
mSvgViewBox = r.viewBoxF();
return;
}
}

mSvgViewBox = QRectF();
return;
}

bool QgsSVGFillSymbolLayer::setSubSymbol( QgsSymbolV2* symbol )
{

if ( !symbol || symbol->type() != QgsSymbolV2::Line )
{
delete symbol;
return false;
}


QgsLineSymbolV2* lineSymbol = dynamic_cast<QgsLineSymbolV2*>( symbol );
if ( lineSymbol )
{
delete mOutline;
mOutline = lineSymbol;
return true;
}

delete symbol;
return false;
}
54 changes: 54 additions & 0 deletions src/core/symbology-ng/qgsfillsymbollayerv2.h
Expand Up @@ -61,4 +61,58 @@ class CORE_EXPORT QgsSimpleFillSymbolLayerV2 : public QgsFillSymbolLayerV2
QPen mPen;
};

/**A class for svg fill patterns. The class automatically scales the pattern to
the appropriate pixel dimensions of the output device*/
class CORE_EXPORT QgsSVGFillSymbolLayer: public QgsFillSymbolLayerV2
{
public:
QgsSVGFillSymbolLayer( const QString& svgFilePath = "", double width = 20 );
QgsSVGFillSymbolLayer( const QByteArray& svgData, double width = 20 );
~QgsSVGFillSymbolLayer();

static QgsSymbolLayerV2* create( const QgsStringMap& properties = QgsStringMap() );

// implemented from base classes

QString layerType() const;

void startRender( QgsSymbolV2RenderContext& context );
void stopRender( QgsSymbolV2RenderContext& context );

void renderPolygon( const QPolygonF& points, QList<QPolygonF>* rings, QgsSymbolV2RenderContext& context );

QgsStringMap properties() const;

QgsSymbolLayerV2* clone() const;

//gettersn and setters
void setSvgFilePath( const QString& svgPath );
QString svgFilePath() const { return mSvgFilePath; }
void setPatternWidth( double width ) { mPatternWidth = width;}
double patternWidth() const { return mPatternWidth; }

QgsSymbolV2* subSymbol() { return mOutline; }
bool setSubSymbol( QgsSymbolV2* symbol );

protected:
/**Width of the pattern (in QgsSymbolV2 output units)*/
double mPatternWidth;
/**SVG data*/
QByteArray mSvgData;
/**Path to the svg file (or empty if constructed directly from data)*/
QString mSvgFilePath;
/**SVG view box (to keep the aspect ratio */
QRectF mSvgViewBox;
/**Brush that receives rendered pixel image in startRender() method*/
QBrush mBrush;
/**Outline width*/
double mOutlineWidth;
/**Custom outline*/
QgsLineSymbolV2* mOutline;

private:
/**Helper function that gets the view box from the byte array*/
void storeViewBox();
};

#endif
28 changes: 28 additions & 0 deletions src/core/symbology-ng/qgssymbollayerv2.cpp
Expand Up @@ -4,6 +4,7 @@
#include "qgsrendercontext.h"

#include <QSize>
#include <QPainter>
#include <QPointF>
#include <QPolygonF>

Expand Down Expand Up @@ -50,3 +51,30 @@ void QgsFillSymbolLayerV2::drawPreviewIcon( QgsSymbolV2RenderContext& context, Q
renderPolygon( poly, NULL, context );
stopRender( context );
}

void QgsFillSymbolLayerV2::_renderPolygon( QPainter* p, const QPolygonF& points, const QList<QPolygonF>* rings )
{
if ( !p )
{
return;
}

if ( rings == NULL )
{
// simple polygon without holes
p->drawPolygon( points );
}
else
{
// polygon with holes must be drawn using painter path
QPainterPath path;
path.addPolygon( points );
QList<QPolygonF>::const_iterator it = rings->constBegin();
for ( ; it != rings->constEnd(); ++it )
{
path.addPolygon( *it );
}

p->drawPath( path );
}
}
2 changes: 2 additions & 0 deletions src/core/symbology-ng/qgssymbollayerv2.h
Expand Up @@ -114,6 +114,8 @@ class CORE_EXPORT QgsFillSymbolLayerV2 : public QgsSymbolLayerV2

protected:
QgsFillSymbolLayerV2( bool locked = false );
/**Default method to render polygon*/
void _renderPolygon( QPainter* p, const QPolygonF& points, const QList<QPolygonF>* rings );
};

class QgsSymbolLayerV2Widget;
Expand Down
6 changes: 4 additions & 2 deletions src/core/symbology-ng/qgssymbollayerv2registry.cpp
Expand Up @@ -24,11 +24,13 @@ QgsSymbolLayerV2Registry::QgsSymbolLayerV2Registry()

addSymbolLayerType( new QgsSymbolLayerV2Metadata( "SimpleFill", QgsSymbolV2::Fill,
QgsSimpleFillSymbolLayerV2::create ) );

addSymbolLayerType( new QgsSymbolLayerV2Metadata( "SVGFill", QgsSymbolV2::Fill, QgsSVGFillSymbolLayer::create ) );
}

QgsSymbolLayerV2Registry::~QgsSymbolLayerV2Registry()
{
foreach (QString name, mMetadata.keys())
foreach( QString name, mMetadata.keys() )
{
delete mMetadata[name];
}
Expand Down Expand Up @@ -91,7 +93,7 @@ QStringList QgsSymbolLayerV2Registry::symbolLayersForType( QgsSymbolV2::SymbolTy
QMap<QString, QgsSymbolLayerV2AbstractMetadata*>::ConstIterator it = mMetadata.begin();
for ( ; it != mMetadata.end(); ++it )
{
if ( (*it)->type() == type )
if (( *it )->type() == type )
lst.append( it.key() );
}
return lst;
Expand Down

0 comments on commit 697ef55

Please sign in to comment.