Fix drawing of map items (grids, overviews) when rendering map item as a raster item
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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"
|
||||
|
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |