Skip to content

Commit ca37a1e

Browse files
committed
Fix drawing of map items (grids, overviews) when rendering map item as a raster item
1 parent 770ffdf commit ca37a1e

File tree

13 files changed

+157
-44
lines changed

13 files changed

+157
-44
lines changed

python/core/layout/qgslayoutitemmap.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ This is calculated using the width of the map item and the width of the
457457
current visible map extent.
458458
%End
459459

460-
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
460+
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, double dpi ) const;
461461
%Docstring
462462
Return map settings that will be used for drawing of the map.
463463
%End

src/app/layout/qgslayoutdesignerdialog.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1529,7 +1529,7 @@ void QgsLayoutDesignerDialog::exportToRaster()
15291529
switch ( exporter.exportToImage( fileNExt.first, settings ) )
15301530
{
15311531
case QgsLayoutExporter::Success:
1532-
mMessageBar->pushInfo( tr( "Export layout" ), tr( "Successfully exported layout to %1" ).arg( fileNExt.first ) );
1532+
mMessageBar->pushInfo( tr( "Export layout" ), tr( "Successfully exported layout to <a href=\"%1\">%2</a>" ).arg( QUrl::fromLocalFile( fileNExt.first ).toString(), fileNExt.first ) );
15331533
break;
15341534

15351535
case QgsLayoutExporter::PrintError:

src/core/layout/qgslayoutitemmap.cpp

+87-41
Original file line numberDiff line numberDiff line change
@@ -756,11 +756,12 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
756756
if ( thisPaintRect.width() == 0 || thisPaintRect.height() == 0 )
757757
return;
758758

759-
painter->save();
760-
painter->setClipRect( thisPaintRect );
759+
//TODO - try to reduce the amount of duplicate code here!
761760

762761
if ( mLayout->context().isPreviewRender() )
763762
{
763+
painter->save();
764+
painter->setClipRect( thisPaintRect );
764765
if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
765766
{
766767
// No initial render available - so draw some preview text alerting user
@@ -800,6 +801,23 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
800801
//restore rotation
801802
painter->restore();
802803
}
804+
805+
painter->setClipRect( thisPaintRect, Qt::NoClip );
806+
807+
if ( shouldDrawPart( OverviewMapExtent ) )
808+
{
809+
mOverviewStack->drawItems( painter );
810+
}
811+
if ( shouldDrawPart( Grid ) )
812+
{
813+
mGridStack->drawItems( painter );
814+
}
815+
drawAnnotations( painter );
816+
if ( shouldDrawPart( Frame ) )
817+
{
818+
drawMapFrame( painter );
819+
}
820+
painter->restore();
803821
}
804822
else
805823
{
@@ -811,42 +829,73 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
811829
if ( !paintDevice )
812830
return;
813831

814-
// Fill with background color
815-
if ( shouldDrawPart( Background ) )
816-
{
817-
drawMapBackground( painter );
818-
}
819-
820832
QgsRectangle cExtent = extent();
821833
QSizeF size( cExtent.width() * mapUnitsToLayoutUnits(), cExtent.height() * mapUnitsToLayoutUnits() );
822834

823835
if ( containsAdvancedEffects() && ( !mLayout || !( mLayout->context().flags() & QgsLayoutContext::FlagForceVectorOutput ) ) )
824836
{
825837
// rasterise
826-
double destinationDpi = mLayout ? mLayout->context().dpi() : style->matrix.m11() * 25.4;
827-
828-
double layoutUnitsToPixels = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutPixels ).length() : destinationDpi / 25.4;
829-
double widthInPixels = boundingRect().width() * layoutUnitsToPixels;
830-
double heightInPixels = boundingRect().height() * layoutUnitsToPixels;
838+
double destinationDpi = style->matrix.m11() * 25.4;
839+
double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutInches ).length() : 1;
840+
int widthInPixels = std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi );
841+
int heightInPixels = std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi );
831842
QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
832843

833844
image.fill( Qt::transparent );
834845
image.setDotsPerMeterX( 1000 * destinationDpi / 25.4 );
835846
image.setDotsPerMeterY( 1000 * destinationDpi / 25.4 );
847+
double dotsPerMM = destinationDpi / 25.4;
836848
QPainter p( &image );
837-
double dotsPerMM = image.logicalDpiX() / 25.4;
838-
drawMap( &p, cExtent, image.size(), destinationDpi );
839-
p.end();
840849

841-
dotsPerMM = paintDevice->logicalDpiX() / 25.4;
850+
QPointF tl = -boundingRect().topLeft();
851+
QRect imagePaintRect( std::round( tl.x() * dotsPerMM ),
852+
std::round( tl.y() * dotsPerMM ),
853+
std::round( thisPaintRect.width() * dotsPerMM ),
854+
std::round( thisPaintRect.height() * dotsPerMM ) );
855+
p.setClipRect( imagePaintRect );
856+
857+
p.translate( imagePaintRect.topLeft() );
858+
859+
// Fill with background color - must be drawn onto the flattened image
860+
// so that layers with opacity or blend modes can correctly interact with it
861+
if ( shouldDrawPart( Background ) )
862+
{
863+
p.scale( dotsPerMM, dotsPerMM );
864+
drawMapBackground( &p );
865+
p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
866+
}
867+
868+
drawMap( &p, cExtent, imagePaintRect.size(), image.logicalDpiX() );
869+
870+
// important - all other items, overviews, grids etc must be rendered to the
871+
// flattened image, in case these have blend modes must need to interact
872+
// with the map
873+
p.scale( dotsPerMM, dotsPerMM );
874+
875+
if ( shouldDrawPart( OverviewMapExtent ) )
876+
{
877+
mOverviewStack->drawItems( &p );
878+
}
879+
if ( shouldDrawPart( Grid ) )
880+
{
881+
mGridStack->drawItems( &p );
882+
}
883+
drawAnnotations( &p );
884+
842885
painter->save();
843886
painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
844-
painter->drawImage( 0, 0, image );
845-
painter->restore();
846-
887+
painter->drawImage( std::round( -tl.x()* dotsPerMM ), std::round( -tl.y() * dotsPerMM ), image );
888+
painter->scale( dotsPerMM, dotsPerMM );
847889
}
848890
else
849891
{
892+
// Fill with background color
893+
if ( shouldDrawPart( Background ) )
894+
{
895+
drawMapBackground( painter );
896+
}
897+
898+
painter->setClipRect( thisPaintRect );
850899
painter->save();
851900
painter->translate( mXOffset, mYOffset );
852901

@@ -856,31 +905,28 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
856905
drawMap( painter, cExtent, size, paintDevice->logicalDpiX() );
857906

858907
painter->restore();
859-
}
860908

861-
mDrawing = false;
862-
}
863-
864-
painter->setClipRect( thisPaintRect, Qt::NoClip );
909+
painter->setClipRect( thisPaintRect, Qt::NoClip );
865910

866-
if ( shouldDrawPart( OverviewMapExtent ) )
867-
{
868-
mOverviewStack->drawItems( painter );
869-
}
870-
if ( shouldDrawPart( Grid ) )
871-
{
872-
mGridStack->drawItems( painter );
873-
}
911+
if ( shouldDrawPart( OverviewMapExtent ) )
912+
{
913+
mOverviewStack->drawItems( painter );
914+
}
915+
if ( shouldDrawPart( Grid ) )
916+
{
917+
mGridStack->drawItems( painter );
918+
}
919+
drawAnnotations( painter );
874920

875-
//draw canvas items
876-
drawAnnotations( painter );
921+
}
877922

878-
if ( shouldDrawPart( Frame ) )
879-
{
880-
drawMapFrame( painter );
923+
if ( shouldDrawPart( Frame ) )
924+
{
925+
drawMapFrame( painter );
926+
}
927+
painter->restore();
928+
mDrawing = false;
881929
}
882-
883-
painter->restore();
884930
}
885931

886932
int QgsLayoutItemMap::numberExportLayers() const
@@ -994,7 +1040,7 @@ void QgsLayoutItemMap::recreateCachedImageInBackground( double viewScaleFactor )
9941040
mPainterJob->start();
9951041
}
9961042

997-
QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const
1043+
QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, double dpi ) const
9981044
{
9991045
QgsExpressionContext expressionContext = createExpressionContext();
10001046
QgsCoordinateReferenceSystem renderCrs = crs();

src/core/layout/qgslayoutitemmap.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
408408
/**
409409
* Return map settings that will be used for drawing of the map.
410410
*/
411-
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
411+
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, double dpi ) const;
412412

413413
void finalizeRestoreFromXml() override;
414414

src/core/layout/qgslayoutitemmapgrid.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -1219,7 +1219,11 @@ void QgsLayoutItemMapGrid::drawCoordinateAnnotation( QPainter *p, QPointF pos, c
12191219
ypos += ( mAnnotationFrameDistance + textHeight + gridFrameDistance );
12201220
xpos -= textWidth / 2.0;
12211221
if ( extension )
1222+
{
12221223
extension->bottom = std::max( extension->bottom, mAnnotationFrameDistance + gridFrameDistance + textHeight );
1224+
extension->left = std::max( extension->left, textWidth / 2.0 ); // annotation at bottom left/right may extend outside the bounds
1225+
extension->right = std::max( extension->right, textWidth / 2.0 );
1226+
}
12231227
}
12241228
else if ( mBottomGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending )
12251229
{

tests/src/core/testqgslayoutmap.cpp

+63
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "qgsmapthemecollection.h"
2929
#include "qgsproperty.h"
3030
#include "qgslayoutpagecollection.h"
31+
#include "qgslayoutitempolyline.h"
3132
#include <QObject>
3233
#include "qgstest.h"
3334

@@ -53,6 +54,7 @@ class TestQgsLayoutMap : public QObject
5354
void mapPolygonVertices(); // test mapPolygon function with no map rotation
5455
void dataDefinedLayers(); //test data defined layer string
5556
void dataDefinedStyles(); //test data defined styles
57+
void rasterized();
5658

5759
private:
5860
QgsRasterLayer *mRasterLayer = nullptr;
@@ -440,5 +442,66 @@ void TestQgsLayoutMap::dataDefinedStyles()
440442
QVERIFY( checker.testLayout( mReport, 0, 0 ) );
441443
}
442444

445+
void TestQgsLayoutMap::rasterized()
446+
{
447+
// test a map which must be rasterised
448+
QgsLayout l( QgsProject::instance() );
449+
l.initializeDefaults();
450+
451+
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
452+
map->attemptMove( QgsLayoutPoint( 20, 30 ) );
453+
map->attemptResize( QgsLayoutSize( 200, 100 ) );
454+
map->setFrameEnabled( true );
455+
map->setExtent( QgsRectangle( -110.0, 25.0, -90, 40.0 ) );
456+
QList<QgsMapLayer *> layers = QList<QgsMapLayer *>() << mLinesLayer;
457+
map->setLayers( layers );
458+
map->setBackgroundColor( Qt::yellow );
459+
l.addLayoutItem( map );
460+
461+
// add some guide lines, just for reference
462+
QPolygonF points;
463+
points << QPointF( 0, 30 ) << QPointF( 10, 30 );
464+
QgsLayoutItemPolyline *line1 = new QgsLayoutItemPolyline( points, &l );
465+
l.addLayoutItem( line1 );
466+
points.clear();
467+
points << QPointF( 0, 30 + map->rect().height() ) << QPointF( 10, 30 + map->rect().height() );
468+
QgsLayoutItemPolyline *line2 = new QgsLayoutItemPolyline( points, &l );
469+
l.addLayoutItem( line2 );
470+
points.clear();
471+
points << QPointF( 20, 0 ) << QPointF( 20, 20 );
472+
QgsLayoutItemPolyline *line3 = new QgsLayoutItemPolyline( points, &l );
473+
l.addLayoutItem( line3 );
474+
points.clear();
475+
points << QPointF( 220, 0 ) << QPointF( 220, 20 );
476+
QgsLayoutItemPolyline *line4 = new QgsLayoutItemPolyline( points, &l );
477+
l.addLayoutItem( line4 );
478+
479+
// force rasterization
480+
QgsLayoutItemMapGrid *grid = new QgsLayoutItemMapGrid( "test", map );
481+
grid->setIntervalX( 10 );
482+
grid->setIntervalY( 10 );
483+
grid->setBlendMode( QPainter::CompositionMode_Darken );
484+
grid->setAnnotationEnabled( true );
485+
grid->setAnnotationDisplay( QgsLayoutItemMapGrid::ShowAll, QgsLayoutItemMapGrid::Left );
486+
grid->setAnnotationDisplay( QgsLayoutItemMapGrid::ShowAll, QgsLayoutItemMapGrid::Top );
487+
grid->setAnnotationDisplay( QgsLayoutItemMapGrid::ShowAll, QgsLayoutItemMapGrid::Right );
488+
grid->setAnnotationDisplay( QgsLayoutItemMapGrid::ShowAll, QgsLayoutItemMapGrid::Bottom );
489+
map->grids()->addGrid( grid );
490+
map->updateBoundingRect();
491+
492+
QVERIFY( map->containsAdvancedEffects() );
493+
494+
QgsLayoutChecker checker( QStringLiteral( "layoutmap_rasterized" ), &l );
495+
checker.setControlPathPrefix( QStringLiteral( "composer_map" ) );
496+
QVERIFY( checker.testLayout( mReport, 0, 0 ) );
497+
498+
// try rendering again, without requiring rasterization, for comparison
499+
// (we can use the same test image, because CompositionMode_Darken doesn't actually have any noticable
500+
// rendering differences for the black grid!)
501+
grid->setBlendMode( QPainter::CompositionMode_SourceOver );
502+
QVERIFY( !map->containsAdvancedEffects() );
503+
QVERIFY( checker.testLayout( mReport, 0, 0 ) );
504+
}
505+
443506
QGSTEST_MAIN( TestQgsLayoutMap )
444507
#include "testqgslayoutmap.moc"

0 commit comments

Comments
 (0)