Implement a cache for item content renders

Speeds up redraw of items, making use of layout designer much
faster with slow to redraw items.

This will also make it possible to use live effects on layout
items without killing performance of the designer.
This commit is contained in:
Nyall Dawson 2017-07-14 16:44:04 +10:00
parent 436710a177
commit 38cbbe23aa
5 changed files with 100 additions and 9 deletions

View File

@ -20,6 +20,8 @@
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#define CACHE_SIZE_LIMIT 5000
QgsLayoutItem::QgsLayoutItem( QgsLayout *layout )
: QgsLayoutObject( layout )
, QGraphicsRectItem( 0 )
@ -44,23 +46,89 @@ void QgsLayoutItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *it
}
//TODO - remember to disable saving/restoring on graphics view!!
painter->save();
preparePainter( painter );
if ( shouldDrawDebugRect() )
{
drawDebugRect( painter );
return;
}
double destinationDpi = itemStyle->matrix.m11() * 25.4;
bool useImageCache = true;
if ( useImageCache )
{
double widthInPixels = boundingRect().width() * itemStyle->matrix.m11();
double heightInPixels = boundingRect().height() * itemStyle->matrix.m11();
// limit size of image for better performance
double scale = 1.0;
if ( widthInPixels > CACHE_SIZE_LIMIT || heightInPixels > CACHE_SIZE_LIMIT )
{
if ( widthInPixels > heightInPixels )
{
scale = widthInPixels / CACHE_SIZE_LIMIT;
widthInPixels = CACHE_SIZE_LIMIT;
heightInPixels /= scale;
}
else
{
scale = heightInPixels / CACHE_SIZE_LIMIT;
heightInPixels = CACHE_SIZE_LIMIT;
widthInPixels /= scale;
}
destinationDpi = destinationDpi / scale;
}
if ( !mItemCachedImage.isNull() && qgsDoubleNear( mItemCacheDpi, destinationDpi ) )
{
// can reuse last cached image
QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, painter, destinationDpi );
painter->save();
preparePainter( painter );
double cacheScale = destinationDpi / mItemCacheDpi;
painter->scale( cacheScale / context.scaleFactor(), cacheScale / context.scaleFactor() );
painter->drawImage( boundingRect().x() * context.scaleFactor() / cacheScale,
boundingRect().y() * context.scaleFactor() / cacheScale, mItemCachedImage );
painter->restore();
return;
}
else
{
mItemCacheDpi = destinationDpi;
mItemCachedImage = QImage( widthInPixels, heightInPixels, QImage::Format_ARGB32 );
mItemCachedImage.fill( Qt::transparent );
mItemCachedImage.setDotsPerMeterX( 1000 * destinationDpi * 25.4 );
mItemCachedImage.setDotsPerMeterY( 1000 * destinationDpi * 25.4 );
QPainter p( &mItemCachedImage );
preparePainter( &p );
QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, &p, destinationDpi );
// painter is already scaled to dots
// need to translate so that item origin is at 0,0 in painter coordinates (not bounding rect origin)
p.translate( -boundingRect().x() * context.scaleFactor(), -boundingRect().y() * context.scaleFactor() );
draw( context, itemStyle );
p.end();
painter->save();
// scale painter from mm to dots
painter->scale( 1.0 / context.scaleFactor(), 1.0 / context.scaleFactor() );
painter->drawImage( boundingRect().x() * context.scaleFactor(),
boundingRect().y() * context.scaleFactor(), mItemCachedImage );
painter->restore();
}
}
else
{
double destinationDpi = itemStyle->matrix.m11() * 25.4;
// no caching or flattening
painter->save();
QgsRenderContext context = QgsLayoutUtils::createRenderContextForMap( nullptr, painter, destinationDpi );
// scale painter from mm to dots
painter->scale( 1.0 / context.scaleFactor(), 1.0 / context.scaleFactor() );
draw( context, itemStyle );
painter->restore();
}
painter->restore();
}
void QgsLayoutItem::setReferencePoint( const QgsLayoutItem::ReferencePoint &point )

View File

@ -252,6 +252,9 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
QgsLayoutPoint mItemPosition;
double mItemRotation = 0.0;
QImage mItemCachedImage;
double mItemCacheDpi = -1;
void initConnectionsToLayout();
//! Prepares a painter by setting rendering flags

View File

@ -95,6 +95,16 @@ TestLayoutItem::TestLayoutItem( QgsLayout *layout )
int s = ( qrand() % ( 200 - 100 + 1 ) ) + 100;
int v = ( qrand() % ( 130 - 255 + 1 ) ) + 130;
mColor = QColor::fromHsv( h, s, v );
QgsStringMap properties;
properties.insert( QStringLiteral( "color" ), mColor.name() );
properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) );
properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) );
properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
mShapeStyleSymbol = QgsFillSymbol::createSimple( properties );
}
void TestLayoutItem::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle )
@ -114,9 +124,15 @@ void TestLayoutItem::draw( QgsRenderContext &context, const QStyleOptionGraphics
painter->setBrush( mColor );
double scale = context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
QRectF r = QRectF( rect().left() * scale, rect().top() * scale,
rect().width() * scale, rect().height() * scale );
painter->drawRect( r );
QPolygonF shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * scale, rect().height() * scale ) );
QList<QPolygonF> rings; //empty list
mShapeStyleSymbol->startRender( context );
mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, nullptr, context );
mShapeStyleSymbol->stopRender( context );
// painter->drawRect( r );
painter->restore();
stack.end( context );
}

View File

@ -29,6 +29,7 @@
class QgsLayout;
class QgsLayoutView;
class QgsLayoutItem;
class QgsFillSymbol;
/**
* \ingroup core
@ -271,8 +272,10 @@ class TestLayoutItem : public QgsLayoutItem
private:
QColor mColor;
QgsFillSymbol *mShapeStyleSymbol = nullptr;
};
///@endcond
#endif

View File

@ -86,7 +86,8 @@ void QgsLayoutViewToolAddItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *even
Q_UNUSED( clickOnly );
QgsLayoutItem *item = QgsApplication::layoutItemRegistry()->createItem( mItemType, layout() );
item->setRect( rect );
item->attemptResize( QgsLayoutSize( rect.width(), rect.height(), QgsUnitTypes::LayoutMillimeters ) );
item->attemptMove( QgsLayoutPoint( rect.left(), rect.top(), QgsUnitTypes::LayoutMillimeters ) );
layout()->addItem( item );
}