Skip to content

Commit

Permalink
[composer] Fix picture rotation, optimise scaling of rotated pictures…
Browse files Browse the repository at this point in the history
… to fit in item bounding box
  • Loading branch information
nyalldawson committed Dec 30, 2013
1 parent 86b972c commit ddcfd06
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 35 deletions.
83 changes: 79 additions & 4 deletions src/core/composer/qgscomposeritem.cpp
Expand Up @@ -42,6 +42,10 @@

#define FONT_WORKAROUND_SCALE 10 //scale factor for upscaling fontsize and downscaling painter

#ifndef M_DEG2RAD
#define M_DEG2RAD 0.0174532925
#endif

QgsComposerItem::QgsComposerItem( QgsComposition* composition, bool manageZValue )
: QObject( 0 )
, QGraphicsRectItem( 0 )
Expand Down Expand Up @@ -733,6 +737,77 @@ bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& heigh
return imageSizeConsideringRotation( width, height, mItemRotation );
}

QRectF QgsComposerItem::largestRotatedRectWithinBounds( QRectF originalRect, QRectF boundsRect, double rotation ) const
{
double originalWidth = originalRect.width();
double originalHeight = originalRect.height();
double boundsWidth = boundsRect.width();
double boundsHeight = boundsRect.height();
double ratioBoundsRect = boundsWidth / boundsHeight;

//shortcut for some rotation values
if ( rotation == 0 || rotation == 90 || rotation == 180 || rotation == 270 )
{
double originalRatio = originalWidth / originalHeight;
double rectScale = originalRatio > ratioBoundsRect ? boundsWidth / originalWidth : boundsHeight / originalHeight;
double rectScaledWidth = rectScale * originalWidth;
double rectScaledHeight = rectScale * originalHeight;

if ( rotation == 0 || rotation == 180 )
{
return QRectF(( boundsWidth - rectScaledWidth ) / 2.0, ( boundsHeight - rectScaledHeight ) / 2.0, rectScaledWidth, rectScaledHeight );
}
else if ( rotation == 0 || rotation == 180 )
{
return QRectF(( boundsWidth - rectScaledHeight ) / 2.0, ( boundsHeight - rectScaledWidth ) / 2.0, rectScaledHeight, rectScaledWidth );
}
}

//convert angle to radians and flip
double angleRad = -rotation * M_DEG2RAD;
double cosAngle = cos( angleRad );
double sinAngle = sin( angleRad );

//calculate size of bounds of rotated rectangle
double widthBoundsRotatedRect = originalWidth * fabs( cosAngle ) + originalHeight * fabs( sinAngle );
double heightBoundsRotatedRect = originalHeight * fabs( cosAngle ) + originalWidth * fabs( sinAngle );

//compare ratio of rotated rect with bounds rect and calculate scaling of rotated
//rect to fit within bounds
double ratioBoundsRotatedRect = widthBoundsRotatedRect / heightBoundsRotatedRect;
double rectScale = ratioBoundsRotatedRect > ratioBoundsRect ? boundsWidth / widthBoundsRotatedRect : boundsHeight / heightBoundsRotatedRect;
double rectScaledWidth = rectScale * originalWidth;
double rectScaledHeight = rectScale * originalHeight;

//now calculate offset so that rotated rectangle is centered within bounds
//first calculate min x and y coordinates
double currentCornerX = 0;
double minX = 0;
currentCornerX += rectScaledWidth * cosAngle;
minX = minX < currentCornerX ? minX : currentCornerX;
currentCornerX += rectScaledHeight * sinAngle;
minX = minX < currentCornerX ? minX : currentCornerX;
currentCornerX -= rectScaledWidth * cosAngle;
minX = minX < currentCornerX ? minX : currentCornerX;

double currentCornerY = 0;
double minY = 0;
currentCornerY -= rectScaledWidth * sinAngle;
minY = minY < currentCornerY ? minY : currentCornerY;
currentCornerY += rectScaledHeight * cosAngle;
minY = minY < currentCornerY ? minY : currentCornerY;
currentCornerY += rectScaledWidth * sinAngle;
minY = minY < currentCornerY ? minY : currentCornerY;

//now calculate offset position of rotated rectangle
double offsetX = ratioBoundsRotatedRect > ratioBoundsRect ? 0 : ( boundsWidth - rectScale * widthBoundsRotatedRect ) / 2.0;
offsetX += fabs( minX );
double offsetY = ratioBoundsRotatedRect > ratioBoundsRect ? ( boundsHeight - rectScale * heightBoundsRotatedRect ) / 2.0 : 0;
offsetY += fabs( minY );

return QRectF( offsetX, offsetY, rectScaledWidth, rectScaledHeight );
}

bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& height, double rotation ) const
{
if ( qAbs( rotation ) <= 0.0 ) //width and height stays the same if there is no rotation
Expand All @@ -759,19 +834,19 @@ bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& heigh
double midX = width / 2.0;
double midY = height / 2.0;

if ( !cornerPointOnRotatedAndScaledRect( x1, y1, width, height ) )
if ( !cornerPointOnRotatedAndScaledRect( x1, y1, width, height, rotation ) )
{
return false;
}
if ( !cornerPointOnRotatedAndScaledRect( x2, y2, width, height ) )
if ( !cornerPointOnRotatedAndScaledRect( x2, y2, width, height, rotation ) )
{
return false;
}
if ( !cornerPointOnRotatedAndScaledRect( x3, y3, width, height ) )
if ( !cornerPointOnRotatedAndScaledRect( x3, y3, width, height, rotation ) )
{
return false;
}
if ( !cornerPointOnRotatedAndScaledRect( x4, y4, width, height ) )
if ( !cornerPointOnRotatedAndScaledRect( x4, y4, width, height, rotation ) )
{
return false;
}
Expand Down
8 changes: 8 additions & 0 deletions src/core/composer/qgscomposeritem.h
Expand Up @@ -408,6 +408,14 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem
*/
bool imageSizeConsideringRotation( double& width, double& height ) const;

/**Calculates the largest scaled version of originalRect which fits within boundsRect, when it is rotated by
* a specified amount
@param originalRect QRectF to be rotated and scaled
@param boundsRect QRectF specifying the bounds which the rotated and scaled rectangle must fit within
@param rotation the rotation in degrees to be applied to the rectangle
*/
QRectF largestRotatedRectWithinBounds( QRectF originalRect, QRectF boundsRect, double rotation ) const;

/**Calculates corner point after rotation and scaling*/
bool cornerPointOnRotatedAndScaledRect( double& x, double& y, double width, double height, double rotation ) const;
/**Calculates corner point after rotation and scaling
Expand Down
59 changes: 29 additions & 30 deletions src/core/composer/qgscomposerpicture.cpp
Expand Up @@ -38,7 +38,6 @@ QgsComposerPicture::QgsComposerPicture(): QgsComposerItem( 0 ), mMode( Unknown )
mPictureHeight = rect().height();
}


QgsComposerPicture::~QgsComposerPicture()
{

Expand All @@ -59,20 +58,8 @@ void QgsComposerPicture::paint( QPainter* painter, const QStyleOptionGraphicsIte

if ( mMode != Unknown )
{
double rectPixelWidth = /*rect().width()*/mPictureWidth * newDpi / 25.4;
double rectPixelHeight = /*rect().height()*/ mPictureHeight * newDpi / 25.4;
QRectF boundRect;
if ( mMode == SVG )
{
boundRect = boundedSVGRect( rectPixelWidth, rectPixelHeight );
}
else if ( mMode == RASTER )
{
boundRect = boundedImageRect( rectPixelWidth, rectPixelHeight );
}

double boundRectWidthMM = boundRect.width() / newDpi * 25.4;
double boundRectHeightMM = boundRect.height() / newDpi * 25.4;
double boundRectWidthMM = mPictureWidth;
double boundRectHeightMM = mPictureHeight;

painter->save();
painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
Expand Down Expand Up @@ -180,6 +167,22 @@ QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeig
}
}

QSizeF QgsComposerPicture::pictureSize()
{
if ( mMode == SVG )
{
return mDefaultSvgSize;
}
else if ( mMode == RASTER )
{
return QSizeF( mImage.width(), mImage.height() );
}
else
{
return QSizeF( 0, 0 );
}
}

#if 0
QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
{
Expand All @@ -203,12 +206,11 @@ void QgsComposerPicture::setSceneRect( const QRectF& rectangle )
{
QgsComposerItem::setSceneRect( rectangle );

//consider to change size of the shape if the rectangle changes width and/or height
double newPictureWidth = rectangle.width();
double newPictureHeight = rectangle.height();
imageSizeConsideringRotation( newPictureWidth, newPictureHeight );
mPictureWidth = newPictureWidth;
mPictureHeight = newPictureHeight;
//find largest scaling of picture with this rotation which fits in item
QSizeF currentPictureSize = pictureSize();
QRectF rotatedImageRect = largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rectangle, mPictureRotation );
mPictureWidth = rotatedImageRect.width();
mPictureHeight = rotatedImageRect.height();

emit itemChanged();
}
Expand All @@ -221,17 +223,14 @@ void QgsComposerPicture::setRotation( double r )

void QgsComposerPicture::setPictureRotation( double r )
{
//adapt rectangle size
double width = mPictureWidth;
double height = mPictureHeight;
sizeChangedByRotation( width, height );
mPictureRotation = r;

//adapt scene rect to have the same center and the new width / height
double x = pos().x() + rect().width() / 2.0 - width / 2.0;
double y = pos().y() + rect().height() / 2.0 - height / 2.0;
QgsComposerItem::setSceneRect( QRectF( x, y, width, height ) );
//find largest scaling of picture with this rotation which fits in item
QSizeF currentPictureSize = pictureSize();
QRectF rotatedImageRect = largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
mPictureWidth = rotatedImageRect.width();
mPictureHeight = rotatedImageRect.height();

mPictureRotation = r;
update();
emit pictureRotationChanged( mPictureRotation );
}
Expand Down
2 changes: 2 additions & 0 deletions src/core/composer/qgscomposerpicture.h
Expand Up @@ -124,6 +124,8 @@ class CORE_EXPORT QgsComposerPicture: public QgsComposerItem
/**Calculates bounding rect for image such that aspect ratio is correct*/
QRectF boundedImageRect( double deviceWidth, double deviceHeight );

/**Returns size of current raster or svg picture */
QSizeF pictureSize();

QImage mImage;
QSvgRenderer mSVG;
Expand Down
2 changes: 1 addition & 1 deletion tests/src/core/testqgscomposerrotation.cpp
Expand Up @@ -221,7 +221,7 @@ void TestQgsComposerRotation::pictureRotation()
//test map rotation
mComposition->addComposerPicture( mComposerPicture );
mComposerPicture->setPictureRotation( 45 );
mComposerPicture->setSceneRect( QRectF( 70, 70, 100, 100 ) );
//mComposerPicture->setSceneRect( QRectF( 70, 70, 100, 100 ) );

QgsCompositionChecker checker( "composerrotation_maprotation", mComposition );
QVERIFY( checker.testComposition( mReport ) );
Expand Down

0 comments on commit ddcfd06

Please sign in to comment.