Fix drawing of map items (grids, overviews) when rendering map item as a raster item

This commit is contained in:
Nyall Dawson 2017-12-13 08:41:46 +10:00
parent 770ffdf614
commit ca37a1ebd7
13 changed files with 158 additions and 45 deletions

View File

@ -457,7 +457,7 @@ This is calculated using the width of the map item and the width of the
current visible map extent.
%End
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, double dpi ) const;
%Docstring
Return map settings that will be used for drawing of the map.
%End

View File

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

View File

@ -756,11 +756,12 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
if ( thisPaintRect.width() == 0 || thisPaintRect.height() == 0 )
return;
painter->save();
painter->setClipRect( thisPaintRect );
//TODO - try to reduce the amount of duplicate code here!
if ( mLayout->context().isPreviewRender() )
{
painter->save();
painter->setClipRect( thisPaintRect );
if ( !mCacheFinalImage || mCacheFinalImage->isNull() )
{
// No initial render available - so draw some preview text alerting user
@ -800,6 +801,23 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
//restore rotation
painter->restore();
}
painter->setClipRect( thisPaintRect, Qt::NoClip );
if ( shouldDrawPart( OverviewMapExtent ) )
{
mOverviewStack->drawItems( painter );
}
if ( shouldDrawPart( Grid ) )
{
mGridStack->drawItems( painter );
}
drawAnnotations( painter );
if ( shouldDrawPart( Frame ) )
{
drawMapFrame( painter );
}
painter->restore();
}
else
{
@ -811,42 +829,73 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
if ( !paintDevice )
return;
// Fill with background color
if ( shouldDrawPart( Background ) )
{
drawMapBackground( painter );
}
QgsRectangle cExtent = extent();
QSizeF size( cExtent.width() * mapUnitsToLayoutUnits(), cExtent.height() * mapUnitsToLayoutUnits() );
if ( containsAdvancedEffects() && ( !mLayout || !( mLayout->context().flags() & QgsLayoutContext::FlagForceVectorOutput ) ) )
{
// rasterise
double destinationDpi = mLayout ? mLayout->context().dpi() : style->matrix.m11() * 25.4;
double layoutUnitsToPixels = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutPixels ).length() : destinationDpi / 25.4;
double widthInPixels = boundingRect().width() * layoutUnitsToPixels;
double heightInPixels = boundingRect().height() * layoutUnitsToPixels;
double destinationDpi = style->matrix.m11() * 25.4;
double layoutUnitsInInches = mLayout ? mLayout->convertFromLayoutUnits( 1, QgsUnitTypes::LayoutInches ).length() : 1;
int widthInPixels = std::round( boundingRect().width() * layoutUnitsInInches * destinationDpi );
int heightInPixels = std::round( boundingRect().height() * layoutUnitsInInches * destinationDpi );
QImage image = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
image.fill( Qt::transparent );
image.setDotsPerMeterX( 1000 * destinationDpi / 25.4 );
image.setDotsPerMeterY( 1000 * destinationDpi / 25.4 );
double dotsPerMM = destinationDpi / 25.4;
QPainter p( &image );
double dotsPerMM = image.logicalDpiX() / 25.4;
drawMap( &p, cExtent, image.size(), destinationDpi );
p.end();
dotsPerMM = paintDevice->logicalDpiX() / 25.4;
QPointF tl = -boundingRect().topLeft();
QRect imagePaintRect( std::round( tl.x() * dotsPerMM ),
std::round( tl.y() * dotsPerMM ),
std::round( thisPaintRect.width() * dotsPerMM ),
std::round( thisPaintRect.height() * dotsPerMM ) );
p.setClipRect( imagePaintRect );
p.translate( imagePaintRect.topLeft() );
// Fill with background color - must be drawn onto the flattened image
// so that layers with opacity or blend modes can correctly interact with it
if ( shouldDrawPart( Background ) )
{
p.scale( dotsPerMM, dotsPerMM );
drawMapBackground( &p );
p.scale( 1.0 / dotsPerMM, 1.0 / dotsPerMM );
}
drawMap( &p, cExtent, imagePaintRect.size(), image.logicalDpiX() );
// important - all other items, overviews, grids etc must be rendered to the
// flattened image, in case these have blend modes must need to interact
// with the map
p.scale( dotsPerMM, dotsPerMM );
if ( shouldDrawPart( OverviewMapExtent ) )
{
mOverviewStack->drawItems( &p );
}
if ( shouldDrawPart( Grid ) )
{
mGridStack->drawItems( &p );
}
drawAnnotations( &p );
painter->save();
painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
painter->drawImage( 0, 0, image );
painter->restore();
painter->drawImage( std::round( -tl.x()* dotsPerMM ), std::round( -tl.y() * dotsPerMM ), image );
painter->scale( dotsPerMM, dotsPerMM );
}
else
{
// Fill with background color
if ( shouldDrawPart( Background ) )
{
drawMapBackground( painter );
}
painter->setClipRect( thisPaintRect );
painter->save();
painter->translate( mXOffset, mYOffset );
@ -856,31 +905,28 @@ void QgsLayoutItemMap::paint( QPainter *painter, const QStyleOptionGraphicsItem
drawMap( painter, cExtent, size, paintDevice->logicalDpiX() );
painter->restore();
painter->setClipRect( thisPaintRect, Qt::NoClip );
if ( shouldDrawPart( OverviewMapExtent ) )
{
mOverviewStack->drawItems( painter );
}
if ( shouldDrawPart( Grid ) )
{
mGridStack->drawItems( painter );
}
drawAnnotations( painter );
}
if ( shouldDrawPart( Frame ) )
{
drawMapFrame( painter );
}
painter->restore();
mDrawing = false;
}
painter->setClipRect( thisPaintRect, Qt::NoClip );
if ( shouldDrawPart( OverviewMapExtent ) )
{
mOverviewStack->drawItems( painter );
}
if ( shouldDrawPart( Grid ) )
{
mGridStack->drawItems( painter );
}
//draw canvas items
drawAnnotations( painter );
if ( shouldDrawPart( Frame ) )
{
drawMapFrame( painter );
}
painter->restore();
}
int QgsLayoutItemMap::numberExportLayers() const
@ -994,7 +1040,7 @@ void QgsLayoutItemMap::recreateCachedImageInBackground( double viewScaleFactor )
mPainterJob->start();
}
QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const
QgsMapSettings QgsLayoutItemMap::mapSettings( const QgsRectangle &extent, QSizeF size, double dpi ) const
{
QgsExpressionContext expressionContext = createExpressionContext();
QgsCoordinateReferenceSystem renderCrs = crs();

View File

@ -408,7 +408,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
/**
* Return map settings that will be used for drawing of the map.
*/
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, double dpi ) const;
void finalizeRestoreFromXml() override;

View File

@ -1219,7 +1219,11 @@ void QgsLayoutItemMapGrid::drawCoordinateAnnotation( QPainter *p, QPointF pos, c
ypos += ( mAnnotationFrameDistance + textHeight + gridFrameDistance );
xpos -= textWidth / 2.0;
if ( extension )
{
extension->bottom = std::max( extension->bottom, mAnnotationFrameDistance + gridFrameDistance + textHeight );
extension->left = std::max( extension->left, textWidth / 2.0 ); // annotation at bottom left/right may extend outside the bounds
extension->right = std::max( extension->right, textWidth / 2.0 );
}
}
else if ( mBottomGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending )
{

View File

@ -28,6 +28,7 @@
#include "qgsmapthemecollection.h"
#include "qgsproperty.h"
#include "qgslayoutpagecollection.h"
#include "qgslayoutitempolyline.h"
#include <QObject>
#include "qgstest.h"
@ -53,6 +54,7 @@ class TestQgsLayoutMap : public QObject
void mapPolygonVertices(); // test mapPolygon function with no map rotation
void dataDefinedLayers(); //test data defined layer string
void dataDefinedStyles(); //test data defined styles
void rasterized();
private:
QgsRasterLayer *mRasterLayer = nullptr;
@ -440,5 +442,66 @@ void TestQgsLayoutMap::dataDefinedStyles()
QVERIFY( checker.testLayout( mReport, 0, 0 ) );
}
void TestQgsLayoutMap::rasterized()
{
// test a map which must be rasterised
QgsLayout l( QgsProject::instance() );
l.initializeDefaults();
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->attemptMove( QgsLayoutPoint( 20, 30 ) );
map->attemptResize( QgsLayoutSize( 200, 100 ) );
map->setFrameEnabled( true );
map->setExtent( QgsRectangle( -110.0, 25.0, -90, 40.0 ) );
QList<QgsMapLayer *> layers = QList<QgsMapLayer *>() << mLinesLayer;
map->setLayers( layers );
map->setBackgroundColor( Qt::yellow );
l.addLayoutItem( map );
// add some guide lines, just for reference
QPolygonF points;
points << QPointF( 0, 30 ) << QPointF( 10, 30 );
QgsLayoutItemPolyline *line1 = new QgsLayoutItemPolyline( points, &l );
l.addLayoutItem( line1 );
points.clear();
points << QPointF( 0, 30 + map->rect().height() ) << QPointF( 10, 30 + map->rect().height() );
QgsLayoutItemPolyline *line2 = new QgsLayoutItemPolyline( points, &l );
l.addLayoutItem( line2 );
points.clear();
points << QPointF( 20, 0 ) << QPointF( 20, 20 );
QgsLayoutItemPolyline *line3 = new QgsLayoutItemPolyline( points, &l );
l.addLayoutItem( line3 );
points.clear();
points << QPointF( 220, 0 ) << QPointF( 220, 20 );
QgsLayoutItemPolyline *line4 = new QgsLayoutItemPolyline( points, &l );
l.addLayoutItem( line4 );
// force rasterization
QgsLayoutItemMapGrid *grid = new QgsLayoutItemMapGrid( "test", map );
grid->setIntervalX( 10 );
grid->setIntervalY( 10 );
grid->setBlendMode( QPainter::CompositionMode_Darken );
grid->setAnnotationEnabled( true );
grid->setAnnotationDisplay( QgsLayoutItemMapGrid::ShowAll, QgsLayoutItemMapGrid::Left );
grid->setAnnotationDisplay( QgsLayoutItemMapGrid::ShowAll, QgsLayoutItemMapGrid::Top );
grid->setAnnotationDisplay( QgsLayoutItemMapGrid::ShowAll, QgsLayoutItemMapGrid::Right );
grid->setAnnotationDisplay( QgsLayoutItemMapGrid::ShowAll, QgsLayoutItemMapGrid::Bottom );
map->grids()->addGrid( grid );
map->updateBoundingRect();
QVERIFY( map->containsAdvancedEffects() );
QgsLayoutChecker checker( QStringLiteral( "layoutmap_rasterized" ), &l );
checker.setControlPathPrefix( QStringLiteral( "composer_map" ) );
QVERIFY( checker.testLayout( mReport, 0, 0 ) );
// try rendering again, without requiring rasterization, for comparison
// (we can use the same test image, because CompositionMode_Darken doesn't actually have any noticable
// rendering differences for the black grid!)
grid->setBlendMode( QPainter::CompositionMode_SourceOver );
QVERIFY( !map->containsAdvancedEffects() );
QVERIFY( checker.testLayout( mReport, 0, 0 ) );
}
QGSTEST_MAIN( TestQgsLayoutMap )
#include "testqgslayoutmap.moc"

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB