Skip to content

Commit ddcfd06

Browse files
committed
[composer] Fix picture rotation, optimise scaling of rotated pictures to fit in item bounding box
1 parent 86b972c commit ddcfd06

File tree

5 files changed

+119
-35
lines changed

5 files changed

+119
-35
lines changed

src/core/composer/qgscomposeritem.cpp

+79-4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242

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

45+
#ifndef M_DEG2RAD
46+
#define M_DEG2RAD 0.0174532925
47+
#endif
48+
4549
QgsComposerItem::QgsComposerItem( QgsComposition* composition, bool manageZValue )
4650
: QObject( 0 )
4751
, QGraphicsRectItem( 0 )
@@ -733,6 +737,77 @@ bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& heigh
733737
return imageSizeConsideringRotation( width, height, mItemRotation );
734738
}
735739

740+
QRectF QgsComposerItem::largestRotatedRectWithinBounds( QRectF originalRect, QRectF boundsRect, double rotation ) const
741+
{
742+
double originalWidth = originalRect.width();
743+
double originalHeight = originalRect.height();
744+
double boundsWidth = boundsRect.width();
745+
double boundsHeight = boundsRect.height();
746+
double ratioBoundsRect = boundsWidth / boundsHeight;
747+
748+
//shortcut for some rotation values
749+
if ( rotation == 0 || rotation == 90 || rotation == 180 || rotation == 270 )
750+
{
751+
double originalRatio = originalWidth / originalHeight;
752+
double rectScale = originalRatio > ratioBoundsRect ? boundsWidth / originalWidth : boundsHeight / originalHeight;
753+
double rectScaledWidth = rectScale * originalWidth;
754+
double rectScaledHeight = rectScale * originalHeight;
755+
756+
if ( rotation == 0 || rotation == 180 )
757+
{
758+
return QRectF(( boundsWidth - rectScaledWidth ) / 2.0, ( boundsHeight - rectScaledHeight ) / 2.0, rectScaledWidth, rectScaledHeight );
759+
}
760+
else if ( rotation == 0 || rotation == 180 )
761+
{
762+
return QRectF(( boundsWidth - rectScaledHeight ) / 2.0, ( boundsHeight - rectScaledWidth ) / 2.0, rectScaledHeight, rectScaledWidth );
763+
}
764+
}
765+
766+
//convert angle to radians and flip
767+
double angleRad = -rotation * M_DEG2RAD;
768+
double cosAngle = cos( angleRad );
769+
double sinAngle = sin( angleRad );
770+
771+
//calculate size of bounds of rotated rectangle
772+
double widthBoundsRotatedRect = originalWidth * fabs( cosAngle ) + originalHeight * fabs( sinAngle );
773+
double heightBoundsRotatedRect = originalHeight * fabs( cosAngle ) + originalWidth * fabs( sinAngle );
774+
775+
//compare ratio of rotated rect with bounds rect and calculate scaling of rotated
776+
//rect to fit within bounds
777+
double ratioBoundsRotatedRect = widthBoundsRotatedRect / heightBoundsRotatedRect;
778+
double rectScale = ratioBoundsRotatedRect > ratioBoundsRect ? boundsWidth / widthBoundsRotatedRect : boundsHeight / heightBoundsRotatedRect;
779+
double rectScaledWidth = rectScale * originalWidth;
780+
double rectScaledHeight = rectScale * originalHeight;
781+
782+
//now calculate offset so that rotated rectangle is centered within bounds
783+
//first calculate min x and y coordinates
784+
double currentCornerX = 0;
785+
double minX = 0;
786+
currentCornerX += rectScaledWidth * cosAngle;
787+
minX = minX < currentCornerX ? minX : currentCornerX;
788+
currentCornerX += rectScaledHeight * sinAngle;
789+
minX = minX < currentCornerX ? minX : currentCornerX;
790+
currentCornerX -= rectScaledWidth * cosAngle;
791+
minX = minX < currentCornerX ? minX : currentCornerX;
792+
793+
double currentCornerY = 0;
794+
double minY = 0;
795+
currentCornerY -= rectScaledWidth * sinAngle;
796+
minY = minY < currentCornerY ? minY : currentCornerY;
797+
currentCornerY += rectScaledHeight * cosAngle;
798+
minY = minY < currentCornerY ? minY : currentCornerY;
799+
currentCornerY += rectScaledWidth * sinAngle;
800+
minY = minY < currentCornerY ? minY : currentCornerY;
801+
802+
//now calculate offset position of rotated rectangle
803+
double offsetX = ratioBoundsRotatedRect > ratioBoundsRect ? 0 : ( boundsWidth - rectScale * widthBoundsRotatedRect ) / 2.0;
804+
offsetX += fabs( minX );
805+
double offsetY = ratioBoundsRotatedRect > ratioBoundsRect ? ( boundsHeight - rectScale * heightBoundsRotatedRect ) / 2.0 : 0;
806+
offsetY += fabs( minY );
807+
808+
return QRectF( offsetX, offsetY, rectScaledWidth, rectScaledHeight );
809+
}
810+
736811
bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& height, double rotation ) const
737812
{
738813
if ( qAbs( rotation ) <= 0.0 ) //width and height stays the same if there is no rotation
@@ -759,19 +834,19 @@ bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& heigh
759834
double midX = width / 2.0;
760835
double midY = height / 2.0;
761836

762-
if ( !cornerPointOnRotatedAndScaledRect( x1, y1, width, height ) )
837+
if ( !cornerPointOnRotatedAndScaledRect( x1, y1, width, height, rotation ) )
763838
{
764839
return false;
765840
}
766-
if ( !cornerPointOnRotatedAndScaledRect( x2, y2, width, height ) )
841+
if ( !cornerPointOnRotatedAndScaledRect( x2, y2, width, height, rotation ) )
767842
{
768843
return false;
769844
}
770-
if ( !cornerPointOnRotatedAndScaledRect( x3, y3, width, height ) )
845+
if ( !cornerPointOnRotatedAndScaledRect( x3, y3, width, height, rotation ) )
771846
{
772847
return false;
773848
}
774-
if ( !cornerPointOnRotatedAndScaledRect( x4, y4, width, height ) )
849+
if ( !cornerPointOnRotatedAndScaledRect( x4, y4, width, height, rotation ) )
775850
{
776851
return false;
777852
}

src/core/composer/qgscomposeritem.h

+8
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,14 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem
408408
*/
409409
bool imageSizeConsideringRotation( double& width, double& height ) const;
410410

411+
/**Calculates the largest scaled version of originalRect which fits within boundsRect, when it is rotated by
412+
* a specified amount
413+
@param originalRect QRectF to be rotated and scaled
414+
@param boundsRect QRectF specifying the bounds which the rotated and scaled rectangle must fit within
415+
@param rotation the rotation in degrees to be applied to the rectangle
416+
*/
417+
QRectF largestRotatedRectWithinBounds( QRectF originalRect, QRectF boundsRect, double rotation ) const;
418+
411419
/**Calculates corner point after rotation and scaling*/
412420
bool cornerPointOnRotatedAndScaledRect( double& x, double& y, double width, double height, double rotation ) const;
413421
/**Calculates corner point after rotation and scaling

src/core/composer/qgscomposerpicture.cpp

+29-30
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ QgsComposerPicture::QgsComposerPicture(): QgsComposerItem( 0 ), mMode( Unknown )
3838
mPictureHeight = rect().height();
3939
}
4040

41-
4241
QgsComposerPicture::~QgsComposerPicture()
4342
{
4443

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

6059
if ( mMode != Unknown )
6160
{
62-
double rectPixelWidth = /*rect().width()*/mPictureWidth * newDpi / 25.4;
63-
double rectPixelHeight = /*rect().height()*/ mPictureHeight * newDpi / 25.4;
64-
QRectF boundRect;
65-
if ( mMode == SVG )
66-
{
67-
boundRect = boundedSVGRect( rectPixelWidth, rectPixelHeight );
68-
}
69-
else if ( mMode == RASTER )
70-
{
71-
boundRect = boundedImageRect( rectPixelWidth, rectPixelHeight );
72-
}
73-
74-
double boundRectWidthMM = boundRect.width() / newDpi * 25.4;
75-
double boundRectHeightMM = boundRect.height() / newDpi * 25.4;
61+
double boundRectWidthMM = mPictureWidth;
62+
double boundRectHeightMM = mPictureHeight;
7663

7764
painter->save();
7865
painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
@@ -180,6 +167,22 @@ QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeig
180167
}
181168
}
182169

170+
QSizeF QgsComposerPicture::pictureSize()
171+
{
172+
if ( mMode == SVG )
173+
{
174+
return mDefaultSvgSize;
175+
}
176+
else if ( mMode == RASTER )
177+
{
178+
return QSizeF( mImage.width(), mImage.height() );
179+
}
180+
else
181+
{
182+
return QSizeF( 0, 0 );
183+
}
184+
}
185+
183186
#if 0
184187
QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
185188
{
@@ -203,12 +206,11 @@ void QgsComposerPicture::setSceneRect( const QRectF& rectangle )
203206
{
204207
QgsComposerItem::setSceneRect( rectangle );
205208

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

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

222224
void QgsComposerPicture::setPictureRotation( double r )
223225
{
224-
//adapt rectangle size
225-
double width = mPictureWidth;
226-
double height = mPictureHeight;
227-
sizeChangedByRotation( width, height );
226+
mPictureRotation = r;
228227

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

234-
mPictureRotation = r;
235234
update();
236235
emit pictureRotationChanged( mPictureRotation );
237236
}

src/core/composer/qgscomposerpicture.h

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ class CORE_EXPORT QgsComposerPicture: public QgsComposerItem
124124
/**Calculates bounding rect for image such that aspect ratio is correct*/
125125
QRectF boundedImageRect( double deviceWidth, double deviceHeight );
126126

127+
/**Returns size of current raster or svg picture */
128+
QSizeF pictureSize();
127129

128130
QImage mImage;
129131
QSvgRenderer mSVG;

tests/src/core/testqgscomposerrotation.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ void TestQgsComposerRotation::pictureRotation()
221221
//test map rotation
222222
mComposition->addComposerPicture( mComposerPicture );
223223
mComposerPicture->setPictureRotation( 45 );
224-
mComposerPicture->setSceneRect( QRectF( 70, 70, 100, 100 ) );
224+
//mComposerPicture->setSceneRect( QRectF( 70, 70, 100, 100 ) );
225225

226226
QgsCompositionChecker checker( "composerrotation_maprotation", mComposition );
227227
QVERIFY( checker.testComposition( mReport ) );

0 commit comments

Comments
 (0)