Skip to content

Commit

Permalink
[FEATURE]: offset for line pattern symbols
Browse files Browse the repository at this point in the history
  • Loading branch information
mhugent committed Aug 30, 2011
1 parent 339d800 commit 48c007e
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 77 deletions.
3 changes: 1 addition & 2 deletions python/core/qgscomposeritem.sip
Expand Up @@ -326,8 +326,7 @@ class QgsComposerItem: QObject, QGraphicsRectItem
bool imageSizeConsideringRotation( double& width, double& height ) const;
/**Calculates corner point after rotation and scaling*/
bool cornerPointOnRotatedAndScaledRect( double& x, double& y, double width, double height ) const;
/**Returns a point on the line from startPoint to directionPoint that is a certain distance away from the starting point*/
QPointF pointOnLineWithDistance( const QPointF& startPoint, const QPointF& directionPoint, double distance ) const;

/**Calculates width / height of the bounding box of a rotated rectangle (mRotation)*/
void sizeChangedByRotation(double& width, double& height);
/**Rotates a point / vector
Expand Down
16 changes: 4 additions & 12 deletions src/core/composer/qgscomposeritem.cpp
Expand Up @@ -30,6 +30,7 @@
#include "qgsapplication.h"
#include "qgsrectangle.h" //just for debugging
#include "qgslogger.h"
#include "qgssymbollayerv2utils.h" //for pointOnLineWithDistance

#include <cmath>

Expand Down Expand Up @@ -954,7 +955,7 @@ bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& heigh

//assume points 1 and 3 are on the rectangle boundaries. Calculate 2 and 4.
double distM1 = sqrt(( x1 - midX ) * ( x1 - midX ) + ( y1 - midY ) * ( y1 - midY ) );
QPointF p2 = pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x2, y2 ), distM1 );
QPointF p2 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x2, y2 ), distM1 );

if ( p2.x() < width && p2.x() > 0 && p2.y() < height && p2.y() > 0 )
{
Expand All @@ -965,8 +966,8 @@ bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& heigh

//else assume that points 2 and 4 are on the rectangle boundaries. Calculate 1 and 3
double distM2 = sqrt(( x2 - midX ) * ( x2 - midX ) + ( y2 - midY ) * ( y2 - midY ) );
QPointF p1 = pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x1, y1 ), distM2 );
QPointF p3 = pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x3, y3 ), distM2 );
QPointF p1 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x1, y1 ), distM2 );
QPointF p3 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x3, y3 ), distM2 );
width = sqrt(( x2 - p1.x() ) * ( x2 - p1.x() ) + ( y2 - p1.y() ) * ( y2 - p1.y() ) );
height = sqrt(( p3.x() - x2 ) * ( p3.x() - x2 ) + ( p3.y() - y2 ) * ( p3.y() - y2 ) );
return true;
Expand Down Expand Up @@ -1036,15 +1037,6 @@ bool QgsComposerItem::cornerPointOnRotatedAndScaledRect( double& x, double& y, d
return false;
}

QPointF QgsComposerItem::pointOnLineWithDistance( const QPointF& startPoint, const QPointF& directionPoint, double distance ) const
{
double dx = directionPoint.x() - startPoint.x();
double dy = directionPoint.y() - startPoint.y();
double length = sqrt( dx * dx + dy * dy );
double scaleFactor = distance / length;
return QPointF( startPoint.x() + dx * scaleFactor, startPoint.y() + dy * scaleFactor );
}

void QgsComposerItem::sizeChangedByRotation( double& width, double& height )
{
if ( mRotation == 0.0 )
Expand Down
3 changes: 1 addition & 2 deletions src/core/composer/qgscomposeritem.h
Expand Up @@ -315,8 +315,7 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem
bool imageSizeConsideringRotation( double& width, double& height ) const;
/**Calculates corner point after rotation and scaling*/
bool cornerPointOnRotatedAndScaledRect( double& x, double& y, double width, double height ) const;
/**Returns a point on the line from startPoint to directionPoint that is a certain distance away from the starting point*/
QPointF pointOnLineWithDistance( const QPointF& startPoint, const QPointF& directionPoint, double distance ) const;

/**Calculates width / height of the bounding box of a rotated rectangle (mRotation)*/
void sizeChangedByRotation( double& width, double& height );
/**Rotates a point / vector
Expand Down
17 changes: 9 additions & 8 deletions src/core/composer/qgscomposermap.cpp
Expand Up @@ -31,6 +31,7 @@

#include "qgslabel.h"
#include "qgslabelattributes.h"
#include "qgssymbollayerv2utils.h" //for pointOnLineWithDistance

#include <QGraphicsScene>
#include <QGraphicsView>
Expand Down Expand Up @@ -881,7 +882,7 @@ void QgsComposerMap::drawGrid( QPainter* p )
for ( ; vIt != verticalLines.constEnd(); ++vIt )
{
//start mark
crossEnd1 = pointOnLineWithDistance( vIt->second.p1(), vIt->second.p2(), mCrossLength );
crossEnd1 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( vIt->second.p1(), vIt->second.p2(), mCrossLength );
p->drawLine( vIt->second.p1(), crossEnd1 );

//test for intersection with every horizontal line
Expand All @@ -890,35 +891,35 @@ void QgsComposerMap::drawGrid( QPainter* p )
{
if ( hIt->second.intersect( vIt->second, &intersectionPoint ) == QLineF::BoundedIntersection )
{
crossEnd1 = pointOnLineWithDistance( intersectionPoint, vIt->second.p1(), mCrossLength );
crossEnd2 = pointOnLineWithDistance( intersectionPoint, vIt->second.p2(), mCrossLength );
crossEnd1 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( intersectionPoint, vIt->second.p1(), mCrossLength );
crossEnd2 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( intersectionPoint, vIt->second.p2(), mCrossLength );
p->drawLine( crossEnd1, crossEnd2 );
}
}
//end mark
QPointF crossEnd2 = pointOnLineWithDistance( vIt->second.p2(), vIt->second.p1(), mCrossLength );
QPointF crossEnd2 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( vIt->second.p2(), vIt->second.p1(), mCrossLength );
p->drawLine( vIt->second.p2(), crossEnd2 );
}

hIt = horizontalLines.constBegin();
for ( ; hIt != horizontalLines.constEnd(); ++hIt )
{
//start mark
crossEnd1 = pointOnLineWithDistance( hIt->second.p1(), hIt->second.p2(), mCrossLength );
crossEnd1 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( hIt->second.p1(), hIt->second.p2(), mCrossLength );
p->drawLine( hIt->second.p1(), crossEnd1 );

vIt = verticalLines.constBegin();
for ( ; vIt != verticalLines.constEnd(); ++vIt )
{
if ( vIt->second.intersect( hIt->second, &intersectionPoint ) == QLineF::BoundedIntersection )
{
crossEnd1 = pointOnLineWithDistance( intersectionPoint, hIt->second.p1(), mCrossLength );
crossEnd2 = pointOnLineWithDistance( intersectionPoint, hIt->second.p2(), mCrossLength );
crossEnd1 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( intersectionPoint, hIt->second.p1(), mCrossLength );
crossEnd2 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( intersectionPoint, hIt->second.p2(), mCrossLength );
p->drawLine( crossEnd1, crossEnd2 );
}
}
//end mark
crossEnd1 = pointOnLineWithDistance( hIt->second.p2(), hIt->second.p1(), mCrossLength );
crossEnd1 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( hIt->second.p2(), hIt->second.p1(), mCrossLength );
p->drawLine( hIt->second.p2(), crossEnd1 );
}

Expand Down
104 changes: 63 additions & 41 deletions src/core/symbology-ng/qgsfillsymbollayerv2.cpp
Expand Up @@ -386,6 +386,7 @@ QgsSymbolLayerV2* QgsLinePatternFillSymbolLayer::create( const QgsStringMap& pro
double distance = 5;
double lineWidth = 0.5;
QColor color( Qt::black );
double offset = 0.0;

if ( properties.contains( "lineangle" ) )
{
Expand All @@ -410,6 +411,12 @@ QgsSymbolLayerV2* QgsLinePatternFillSymbolLayer::create( const QgsStringMap& pro
color = QgsSymbolLayerV2Utils::decodeColor( properties["color"] );
}
patternLayer->setColor( color );

if ( properties.contains( "offset" ) )
{
offset = properties["offset"].toDouble();
}
patternLayer->setOffset( offset );
return patternLayer;
}

Expand All @@ -434,75 +441,89 @@ void QgsLinePatternFillSymbolLayer::startRender( QgsSymbolV2RenderContext& conte
}

double outlinePixelWidth = context.outputPixelSize( mLineWidth );
double outputPixelDist = context.outputPixelSize( mDistance );
double outputPixelOffset = context.outputPixelSize( mOffset );

//find a suitable multiple of width and heigh
//depending on the angle, we might need to render into a larger image and use a subset of it
int dx = 0;
int dy = 0;

QImage patternImage( qAbs( width ), height, QImage::Format_ARGB32 );
QImage patternImage( width, height, QImage::Format_ARGB32 );
patternImage.fill( 0 );
QPainter p( &patternImage );

p.setRenderHint( QPainter::Antialiasing, true );
QPen pen( mColor );
pen.setWidthF( outlinePixelWidth );
pen.setCapStyle( Qt::FlatCap );
p.setPen( pen );

//draw line and dots in the border
QPoint p1, p2, p3, p4, p5, p6;
if ( doubleNear( mLineAngle, 0.0 ) || doubleNear( mLineAngle, 360.0 ) || doubleNear( mLineAngle, 180.0 ) )
{
p.drawLine( QPointF( 0, height / 2.0 ), QPointF( width, height / 2.0 ) );
p1 = QPoint( 0, height );
p2 = QPoint( width, height );
p3 = QPoint( 0, 0 );
p4 = QPoint( width, 0 );
p5 = QPoint( 0, 2 * height );
p6 = QPoint( width, 2 * height );
}
else if ( doubleNear( mLineAngle, 90.0 ) || doubleNear( mLineAngle, 270.0 ) )
{
p.drawLine( QPointF( width / 2.0, 0 ), QPointF( width / 2.0, height ) );
p1 = QPoint( 0, height );
p2 = QPoint( 0, 0 );
p3 = QPoint( width, height );
p4 = QPoint( width, 0 );
p5 = QPoint( -width, height );
p6 = QPoint( -width, 0 );
}
else if (( mLineAngle > 0 && mLineAngle < 90 ) || ( mLineAngle > 180 && mLineAngle < 270 ) )
{
p.drawLine( QPointF( 0, height ), QPointF( width, 0 ) );
dx = outputPixelDist * cos(( 90 - mLineAngle ) * M_PI / 180 );
dy = outputPixelDist * sin(( 90 - mLineAngle ) * M_PI / 180 );
p1 = QPoint( 0, height );
p2 = QPoint( width, 0 );
p3 = QPoint( -dx, height - dy );
p4 = QPoint( width - dx, -dy );
p5 = QPoint( dx, height + dy );
p6 = QPoint( width + dx, dy );
}
else if (( mLineAngle < 180 ) || ( mLineAngle > 270 && mLineAngle < 360 ) )
{
p.drawLine( QPointF( width, height ), QPointF( 0, 0 ) );
dy = outputPixelDist * cos(( 180 - mLineAngle ) * M_PI / 180 );
dx = outputPixelDist * sin(( 180 - mLineAngle ) * M_PI / 180 );
p1 = QPoint( width, height );
p2 = QPoint( 0, 0 );
p5 = QPoint( width + dx, height - dy );
p6 = QPoint( dx, -dy );
p3 = QPoint( width - dx, height + dy );
p4 = QPoint( -dx, dy );
}

//todo: calculate triangles more accurately
double d1 = 0;
double d2 = 0;
QPolygonF triangle1, triangle2;
if ( mLineAngle > 0 && mLineAngle < 90 )
{
d1 = ( outlinePixelWidth / 2.0 ) / cos( mLineAngle * M_PI / 180 );
d2 = ( outlinePixelWidth / 2.0 ) / cos(( 90 - mLineAngle ) * M_PI / 180 );
triangle1 << QPointF( 0, 0 ) << QPointF( 0, d1 ) << QPointF( d2, 0 ) << QPointF( 0, 0 );
triangle2 << QPointF( width, height ) << QPointF( width - d2, height ) << QPointF( width, height - d1 ) << QPointF( width, height );
}
else if ( mLineAngle > 90 && mLineAngle < 180 )
if ( !doubleNear( mOffset, 0.0 ) ) //shift everything
{
d1 = ( outlinePixelWidth / 2.0 ) / cos(( mLineAngle - 90 ) * M_PI / 180 );
d2 = ( outlinePixelWidth / 2.0 ) / cos(( 180 - mLineAngle ) * M_PI / 180 );
triangle1 << QPointF( width, 0 ) << QPointF( width - d1, 0 ) << QPointF( width, d2 ) << QPointF( width, 0 );
triangle2 << QPointF( 0, height ) << QPointF( 0, height - d2 ) << QPointF( d1, height ) << QPointF( 0, height );
}
else if ( mLineAngle > 180 && mLineAngle < 270 )
{
d1 = ( outlinePixelWidth / 2.0 ) / cos(( mLineAngle - 180 ) * M_PI / 180 );
d2 = ( outlinePixelWidth / 2.0 ) / cos(( 270 - mLineAngle ) * M_PI / 180 );
triangle1 << QPointF( 0, 0 ) << QPointF( 0, d1 ) << QPointF( d2, 0 ) << QPointF( 0, 0 );
triangle2 << QPointF( width, height ) << QPointF( width - d2, height ) << QPointF( width, height - d1 ) << QPointF( width, height );
}
else if ( mLineAngle > 270 && mLineAngle < 360 )
{
d1 = ( outlinePixelWidth / 2.0 ) / cos(( mLineAngle - 270 ) * M_PI / 180 );
d2 = ( outlinePixelWidth / 2.0 ) / cos(( 360 - mLineAngle ) * M_PI / 180 );
triangle1 << QPointF( width, 0 ) << QPointF( width - d1, 0 ) << QPointF( width, d2 ) << QPointF( width, 0 );
triangle2 << QPointF( 0, height ) << QPointF( 0, height - d2 ) << QPointF( d1, height ) << QPointF( 0, height );
QPointF p3Shift = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
p3 = QPoint( p3Shift.x(), p3Shift.y() );
QPointF p4Shift = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
p4 = QPoint( p4Shift.x(), p4Shift.y() );
QPointF p5Shift = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
p5 = QPoint( p5Shift.x(), p5Shift.y() );
QPointF p6Shift = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
p6 = QPoint( p6Shift.x(), p6Shift.y() );

//update p1, p2 last
p1 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p1, p3, outputPixelOffset ).toPoint();
p2 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p2, p4, outputPixelOffset ).toPoint();
}

p.setPen( QPen( Qt::NoPen ) );
p.setBrush( QBrush( mColor ) );
p.drawPolygon( triangle1 );
p.drawPolygon( triangle2 );
p.drawLine( p1, p2 );
p.drawLine( p3, p4 );
p.drawLine( p5, p6 );
p.end();

//debug
//patternImage.save( "/home/marco/tmp/patternImage.png", "png" );

//set image to mBrush
if ( !doubleNear( context.alpha(), 1.0 ) )
{
Expand Down Expand Up @@ -536,6 +557,7 @@ QgsStringMap QgsLinePatternFillSymbolLayer::properties() const
map.insert( "distance", QString::number( mDistance ) );
map.insert( "linewidth", QString::number( mLineWidth ) );
map.insert( "color", QgsSymbolLayerV2Utils::encodeColor( mColor ) );
map.insert( "offset", QString::number( mOffset ) );
return map;
}

Expand Down
4 changes: 4 additions & 0 deletions src/core/symbology-ng/qgsfillsymbollayerv2.h
Expand Up @@ -157,6 +157,8 @@ class CORE_EXPORT QgsLinePatternFillSymbolLayer: public QgsImageFillSymbolLayer
double lineWidth() const { return mLineWidth; }
void setColor( const QColor& c ) { mColor = c; }
QColor color() const { return mColor; }
void setOffset( double offset ) { mOffset = offset; }
double offset() const { return mOffset; }

protected:
/**Distance (in mm or map units) between lines*/
Expand All @@ -166,6 +168,8 @@ class CORE_EXPORT QgsLinePatternFillSymbolLayer: public QgsImageFillSymbolLayer
QColor mColor;
/**Vector line angle in degrees (0 = horizontal, counterclockwise)*/
double mLineAngle;
/**Offset perpendicular to line direction*/
double mOffset;
};

class CORE_EXPORT QgsPointPatternFillSymbolLayer: public QgsImageFillSymbolLayer
Expand Down
9 changes: 9 additions & 0 deletions src/core/symbology-ng/qgssymbollayerv2utils.cpp
Expand Up @@ -765,3 +765,12 @@ void QgsSymbolLayerV2Utils::multiplyImageOpacity( QImage* image, qreal alpha )
}
}
}

QPointF QgsSymbolLayerV2Utils::pointOnLineWithDistance( const QPointF& startPoint, const QPointF& directionPoint, double distance )
{
double dx = directionPoint.x() - startPoint.x();
double dy = directionPoint.y() - startPoint.y();
double length = sqrt( dx * dx + dy * dy );
double scaleFactor = distance / length;
return QPointF( startPoint.x() + dx * scaleFactor, startPoint.y() + dy * scaleFactor );
}
3 changes: 3 additions & 0 deletions src/core/symbology-ng/qgssymbollayerv2utils.h
Expand Up @@ -81,6 +81,9 @@ class CORE_EXPORT QgsSymbolLayerV2Utils

/**Multiplies opacity of image pixel values with a (global) transparency value*/
static void multiplyImageOpacity( QImage* image, qreal alpha );

/**Returns a point on the line from startPoint to directionPoint that is a certain distance away from the starting point*/
static QPointF pointOnLineWithDistance( const QPointF& startPoint, const QPointF& directionPoint, double distance );
};

class QPolygonF;
Expand Down
10 changes: 10 additions & 0 deletions src/gui/symbology-ng/qgssymbollayerv2widget.cpp
Expand Up @@ -930,6 +930,7 @@ void QgsLinePatternFillSymbolLayerWidget::setSymbolLayer( QgsSymbolLayerV2* laye
mAngleSpinBox->setValue( mLayer->lineAngle() );
mDistanceSpinBox->setValue( mLayer->distance() );
mLineWidthSpinBox->setValue( mLayer->lineWidth() );
mOffsetSpinBox->setValue( mLayer->offset() );
}
}

Expand Down Expand Up @@ -965,6 +966,15 @@ void QgsLinePatternFillSymbolLayerWidget::on_mLineWidthSpinBox_valueChanged( dou
}
}

void QgsLinePatternFillSymbolLayerWidget::on_mOffsetSpinBox_valueChanged( double d )
{
if ( mLayer )
{
mLayer->setOffset( d );
emit changed();
}
}

void QgsLinePatternFillSymbolLayerWidget::on_mColorPushButton_clicked()
{
if ( mLayer )
Expand Down

0 comments on commit 48c007e

Please sign in to comment.