summaryrefslogtreecommitdiff
path: root/vcl/source/graphic/grfcache.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/source/graphic/grfcache.cxx')
-rw-r--r--vcl/source/graphic/grfcache.cxx1221
1 files changed, 1221 insertions, 0 deletions
diff --git a/vcl/source/graphic/grfcache.cxx b/vcl/source/graphic/grfcache.cxx
new file mode 100644
index 000000000000..53fa0f268abe
--- /dev/null
+++ b/vcl/source/graphic/grfcache.cxx
@@ -0,0 +1,1221 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cstdlib>
+
+#include <salhelper/timer.hxx>
+#include <vcl/GraphicObject.hxx>
+#include <tools/debug.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <tools/poly.hxx>
+#include <rtl/strbuf.hxx>
+#include "grfcache.hxx"
+#include <rtl/crc.h>
+#include <memory>
+
+#define MAX_BMP_EXTENT 4096
+
+using namespace com::sun::star;
+
+static const char aHexData[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+class GraphicID
+{
+private:
+
+ sal_uInt32 mnID1;
+ sal_uInt32 mnID2;
+ sal_uInt32 mnID3;
+ BitmapChecksum mnID4;
+
+public:
+ explicit GraphicID( const GraphicObject& rObj );
+
+ bool operator==( const GraphicID& rID ) const
+ {
+ return( rID.mnID1 == mnID1 && rID.mnID2 == mnID2 &&
+ rID.mnID3 == mnID3 && rID.mnID4 == mnID4 );
+ }
+
+ OString GetIDString() const;
+ bool IsEmpty() const { return( 0 == mnID4 ); }
+};
+
+GraphicID::GraphicID( const GraphicObject& rObj )
+{
+ const Graphic& rGraphic = rObj.GetGraphic();
+
+ mnID1 = static_cast<sal_uLong>(rGraphic.GetType()) << 28;
+
+ switch( rGraphic.GetType() )
+ {
+ case GraphicType::Bitmap:
+ {
+ if(rGraphic.getVectorGraphicData().get())
+ {
+ const VectorGraphicDataPtr& rVectorGraphicDataPtr = rGraphic.getVectorGraphicData();
+ const basegfx::B2DRange& rRange = rVectorGraphicDataPtr->getRange();
+
+ mnID1 |= rVectorGraphicDataPtr->getVectorGraphicDataArrayLength();
+ mnID2 = basegfx::fround(rRange.getWidth());
+ mnID3 = basegfx::fround(rRange.getHeight());
+ mnID4 = vcl_get_checksum(0, rVectorGraphicDataPtr->getVectorGraphicDataArray().getConstArray(), rVectorGraphicDataPtr->getVectorGraphicDataArrayLength());
+ }
+ else if( rGraphic.IsAnimated() )
+ {
+ const Animation aAnimation( rGraphic.GetAnimation() );
+
+ mnID1 |= ( aAnimation.Count() & 0x0fffffff );
+ mnID2 = aAnimation.GetDisplaySizePixel().Width();
+ mnID3 = aAnimation.GetDisplaySizePixel().Height();
+ mnID4 = rGraphic.GetChecksum();
+ }
+ else
+ {
+ const BitmapEx aBmpEx( rGraphic.GetBitmapEx() );
+
+ mnID1 |= ( ( ( static_cast<sal_uLong>(aBmpEx.GetTransparentType()) << 8 ) | ( aBmpEx.IsAlpha() ? 1 : 0 ) ) & 0x0fffffff );
+ mnID2 = aBmpEx.GetSizePixel().Width();
+ mnID3 = aBmpEx.GetSizePixel().Height();
+ mnID4 = rGraphic.GetChecksum();
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ const GDIMetaFile& rMtf = rGraphic.GetGDIMetaFile();
+
+ mnID1 |= ( rMtf.GetActionSize() & 0x0fffffff );
+ mnID2 = rMtf.GetPrefSize().Width();
+ mnID3 = rMtf.GetPrefSize().Height();
+ mnID4 = rGraphic.GetChecksum();
+ }
+ break;
+
+ default:
+ mnID2 = mnID3 = mnID4 = 0;
+ break;
+ }
+}
+
+OString GraphicID::GetIDString() const
+{
+ OStringBuffer aHexStr;
+ sal_Int32 nShift, nIndex = 0;
+ aHexStr.setLength(24 + (2 * BITMAP_CHECKSUM_SIZE));
+
+ for( nShift = 28; nShift >= 0; nShift -= 4 )
+ aHexStr[nIndex++] = aHexData[ ( mnID1 >> static_cast<sal_uInt32>(nShift) ) & 0xf ];
+
+ for( nShift = 28; nShift >= 0; nShift -= 4 )
+ aHexStr[nIndex++] = aHexData[ ( mnID2 >> static_cast<sal_uInt32>(nShift) ) & 0xf ];
+
+ for( nShift = 28; nShift >= 0; nShift -= 4 )
+ aHexStr[nIndex++] = aHexData[ ( mnID3 >> static_cast<sal_uInt32>(nShift) ) & 0xf ];
+
+ for( nShift = ( 8 * BITMAP_CHECKSUM_SIZE ) - 4; nShift >= 0; nShift -= 4 )
+ aHexStr[nIndex++] = aHexData[ ( mnID4 >> static_cast<sal_uInt32>(nShift) ) & 0xf ];
+
+ return aHexStr.makeStringAndClear();
+}
+
+class GraphicCacheEntry
+{
+private:
+
+ std::vector< GraphicObject* > maGraphicObjectList;
+
+ GraphicID maID;
+ GfxLink maGfxLink;
+ std::unique_ptr<BitmapEx> mpBmpEx;
+ std::unique_ptr<GDIMetaFile> mpMtf;
+ std::unique_ptr<Animation> mpAnimation;
+ bool mbSwappedAll;
+
+ // VectorGraphicData support
+ VectorGraphicDataPtr maVectorGraphicData;
+ uno::Sequence<sal_Int8> maPdfData;
+
+ bool ImplInit( const GraphicObject& rObj );
+ void ImplFillSubstitute( Graphic& rSubstitute );
+
+public:
+
+ explicit GraphicCacheEntry( const GraphicObject& rObj );
+ ~GraphicCacheEntry();
+
+ const GraphicID& GetID() const { return maID; }
+
+ void AddGraphicObjectReference( const GraphicObject& rObj, Graphic& rSubstitute );
+ bool ReleaseGraphicObjectReference( const GraphicObject& rObj );
+ size_t GetGraphicObjectReferenceCount() { return maGraphicObjectList.size(); }
+ bool HasGraphicObjectReference( const GraphicObject& rObj );
+
+ void TryToSwapIn();
+ void GraphicObjectWasSwappedOut();
+ void GraphicObjectWasSwappedIn( const GraphicObject& rObj );
+};
+
+GraphicCacheEntry::GraphicCacheEntry( const GraphicObject& rObj ) :
+ maID ( rObj ),
+ mbSwappedAll ( true )
+{
+ mbSwappedAll = !ImplInit( rObj );
+ maGraphicObjectList.push_back( const_cast<GraphicObject*>(&rObj) );
+}
+
+GraphicCacheEntry::~GraphicCacheEntry()
+{
+ DBG_ASSERT(
+ maGraphicObjectList.empty(),
+ "GraphicCacheEntry::~GraphicCacheEntry(): Not all GraphicObjects are removed from this entry"
+ );
+}
+
+bool GraphicCacheEntry::ImplInit( const GraphicObject& rObj )
+{
+ bool bRet = false;
+
+ if( !rObj.IsSwappedOut() )
+ {
+ const Graphic& rGraphic = rObj.GetGraphic();
+
+ mpBmpEx.reset();
+ mpMtf.reset();
+ mpAnimation.reset();
+
+ switch( rGraphic.GetType() )
+ {
+ case GraphicType::Bitmap:
+ {
+ if(rGraphic.getVectorGraphicData().get())
+ {
+ maVectorGraphicData = rGraphic.getVectorGraphicData();
+ }
+ else if( rGraphic.IsAnimated() )
+ {
+ mpAnimation.reset(new Animation( rGraphic.GetAnimation() ));
+ }
+ else
+ {
+ mpBmpEx.reset(new BitmapEx( rGraphic.GetBitmapEx() ));
+ if (rGraphic.getPdfData().hasElements())
+ maPdfData = rGraphic.getPdfData();
+ }
+ }
+ break;
+
+ case GraphicType::GdiMetafile:
+ {
+ mpMtf.reset(new GDIMetaFile( rGraphic.GetGDIMetaFile() ));
+ }
+ break;
+
+ default:
+ DBG_ASSERT( GetID().IsEmpty(), "GraphicCacheEntry::ImplInit: Could not initialize graphic! (=>KA)" );
+ break;
+ }
+
+ if( rGraphic.IsLink() )
+ maGfxLink = rGraphic.GetLink();
+ else
+ maGfxLink = GfxLink();
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+void GraphicCacheEntry::ImplFillSubstitute( Graphic& rSubstitute )
+{
+ // create substitute for graphic;
+ const Size aPrefSize( rSubstitute.GetPrefSize() );
+ const MapMode aPrefMapMode( rSubstitute.GetPrefMapMode() );
+ const Link<Animation*,void> aAnimationNotifyHdl( rSubstitute.GetAnimationNotifyHdl() );
+ const GraphicType eOldType = rSubstitute.GetType();
+ const bool bDefaultType = ( rSubstitute.GetType() == GraphicType::Default );
+
+ if( rSubstitute.IsLink() && ( GfxLinkType::NONE == maGfxLink.GetType() ) )
+ maGfxLink = rSubstitute.GetLink();
+
+ if(maVectorGraphicData.get())
+ {
+ rSubstitute = maVectorGraphicData;
+ }
+ else if( mpBmpEx )
+ {
+ rSubstitute = *mpBmpEx;
+ if (maPdfData.hasElements())
+ rSubstitute.setPdfData(maPdfData);
+ }
+ else if( mpAnimation )
+ {
+ rSubstitute = *mpAnimation;
+ }
+ else if( mpMtf )
+ {
+ rSubstitute = *mpMtf;
+ }
+ else
+ {
+ rSubstitute.Clear();
+ }
+
+ if( eOldType != GraphicType::NONE )
+ {
+ rSubstitute.SetPrefSize( aPrefSize );
+ rSubstitute.SetPrefMapMode( aPrefMapMode );
+ rSubstitute.SetAnimationNotifyHdl( aAnimationNotifyHdl );
+ }
+
+ if( GfxLinkType::NONE != maGfxLink.GetType() )
+ {
+ rSubstitute.SetLink( maGfxLink );
+ }
+
+ if( bDefaultType )
+ {
+ rSubstitute.SetDefaultType();
+ }
+}
+
+void GraphicCacheEntry::AddGraphicObjectReference( const GraphicObject& rObj, Graphic& rSubstitute )
+{
+ if( mbSwappedAll )
+ mbSwappedAll = !ImplInit( rObj );
+
+ OUString rOriginURL = rObj.GetGraphic().getOriginURL();
+ ImplFillSubstitute( rSubstitute );
+ rSubstitute.setOriginURL(rOriginURL);
+ maGraphicObjectList.push_back( const_cast<GraphicObject*>(&rObj) );
+}
+
+bool GraphicCacheEntry::ReleaseGraphicObjectReference( const GraphicObject& rObj )
+{
+ for(
+ auto it = maGraphicObjectList.begin();
+ it != maGraphicObjectList.end();
+ ++it
+ ) {
+ if( &rObj == *it )
+ {
+ maGraphicObjectList.erase( it );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool GraphicCacheEntry::HasGraphicObjectReference( const GraphicObject& rObj )
+{
+ bool bRet = false;
+
+ for( size_t i = 0, n = maGraphicObjectList.size(); ( i < n ) && !bRet; ++i )
+ if( &rObj == maGraphicObjectList[ i ] )
+ bRet = true;
+
+ return bRet;
+}
+
+void GraphicCacheEntry::TryToSwapIn()
+{
+ if( mbSwappedAll && !maGraphicObjectList.empty() )
+ maGraphicObjectList.front()->FireSwapInRequest();
+}
+
+void GraphicCacheEntry::GraphicObjectWasSwappedOut()
+{
+ mbSwappedAll = true;
+
+ for( size_t i = 0, n = maGraphicObjectList.size(); ( i < n ) && mbSwappedAll; ++i )
+ if( !maGraphicObjectList[ i ]->IsSwappedOut() )
+ mbSwappedAll = false;
+
+ if( !mbSwappedAll )
+ return;
+
+ mpBmpEx.reset();
+ mpMtf.reset();
+ mpAnimation.reset();
+
+ // #119176# also reset VectorGraphicData
+ maVectorGraphicData.reset();
+ maPdfData = uno::Sequence<sal_Int8>();
+}
+
+void GraphicCacheEntry::GraphicObjectWasSwappedIn( const GraphicObject& rObj )
+{
+ if( mbSwappedAll )
+ mbSwappedAll = !ImplInit( rObj );
+}
+
+class GraphicDisplayCacheEntry
+{
+private:
+
+ ::salhelper::TTimeValue maReleaseTime;
+ const GraphicCacheEntry* mpRefCacheEntry;
+ std::unique_ptr<GDIMetaFile> mpMtf;
+ std::unique_ptr<BitmapEx> mpBmpEx;
+ GraphicAttr maAttr;
+ Size maOutSizePix;
+ sal_uLong mnCacheSize;
+ DrawModeFlags mnOutDevDrawMode;
+ sal_uInt16 mnOutDevBitCount;
+
+ static bool IsCacheableAsBitmap( const GDIMetaFile& rMtf, OutputDevice const * pOut, const Size& rSz );
+
+ // Copy assignment is forbidden and not implemented.
+ GraphicDisplayCacheEntry (const GraphicDisplayCacheEntry &) = delete;
+ GraphicDisplayCacheEntry & operator= (const GraphicDisplayCacheEntry &) = delete;
+
+public:
+
+ static sal_uLong GetNeededSize( OutputDevice const * pOut, const Point& rPt, const Size& rSz,
+ const GraphicObject& rObj, const GraphicAttr& rAttr );
+
+public:
+
+ GraphicDisplayCacheEntry( const GraphicCacheEntry* pRefCacheEntry,
+ OutputDevice const * pOut, const Point& rPt, const Size& rSz,
+ const GraphicObject& rObj, const GraphicAttr& rAttr,
+ const BitmapEx& rBmpEx ) :
+ mpRefCacheEntry( pRefCacheEntry ),
+ mpBmpEx( new BitmapEx( rBmpEx ) ),
+ maAttr( rAttr ), maOutSizePix( pOut->LogicToPixel( rSz ) ),
+ mnCacheSize( GetNeededSize( pOut, rPt, rSz, rObj, rAttr ) ),
+ mnOutDevDrawMode( pOut->GetDrawMode() ),
+ mnOutDevBitCount( pOut->GetBitCount() )
+ {
+ }
+
+ GraphicDisplayCacheEntry( const GraphicCacheEntry* pRefCacheEntry,
+ OutputDevice const * pOut, const Point& rPt, const Size& rSz,
+ const GraphicObject& rObj, const GraphicAttr& rAttr,
+ const GDIMetaFile& rMtf ) :
+ mpRefCacheEntry( pRefCacheEntry ),
+ mpMtf( new GDIMetaFile( rMtf ) ),
+ maAttr( rAttr ), maOutSizePix( pOut->LogicToPixel( rSz ) ),
+ mnCacheSize( GetNeededSize( pOut, rPt, rSz, rObj, rAttr ) ),
+ mnOutDevDrawMode( pOut->GetDrawMode() ),
+ mnOutDevBitCount( pOut->GetBitCount() )
+ {
+ }
+
+
+ sal_uLong GetCacheSize() const { return mnCacheSize; }
+ const GraphicCacheEntry* GetReferencedCacheEntry() const { return mpRefCacheEntry; }
+
+ void SetReleaseTime( const ::salhelper::TTimeValue& rReleaseTime ) { maReleaseTime = rReleaseTime; }
+ const ::salhelper::TTimeValue& GetReleaseTime() const { return maReleaseTime; }
+
+ bool Matches( OutputDevice const * pOut, const Point& /*rPtPixel*/, const Size& rSzPixel,
+ const GraphicCacheEntry* pCacheEntry, const GraphicAttr& rAttr ) const
+ {
+ // #i46805# Additional match
+ // criteria: outdev draw mode and
+ // bit count. One cannot reuse
+ // this cache object, if it's
+ // e.g. generated for
+ // DrawModeFlags::GrayBitmap.
+ return( ( pCacheEntry == mpRefCacheEntry ) &&
+ ( maAttr == rAttr ) &&
+ ( ( maOutSizePix == rSzPixel ) || ( !maOutSizePix.Width() && !maOutSizePix.Height() ) ) &&
+ ( pOut->GetBitCount() == mnOutDevBitCount ) &&
+ ( pOut->GetDrawMode() == mnOutDevDrawMode ) );
+ }
+
+ void Draw( OutputDevice* pOut, const Point& rPt, const Size& rSz ) const;
+};
+
+// This whole function is based on checkMetadataBitmap() from grfmgr2.cxx, see that one for details.
+// If you do changes here, change the original function too.
+static void checkMetadataBitmap( const BitmapEx& rBmpEx,
+ Point /*rSrcPoint*/,
+ Size rSrcSize,
+ const Point& rDestPoint,
+ const Size& rDestSize,
+ const Size& rRefSize,
+ bool& o_rbNonBitmapActionEncountered )
+{
+ if( rSrcSize == Size())
+ rSrcSize = rBmpEx.GetSizePixel();
+
+ if( rDestPoint != Point( 0, 0 ))
+ {
+ o_rbNonBitmapActionEncountered = true;
+ return;
+ }
+ if( rDestSize != rRefSize )
+ { if( rBmpEx.GetSizePixel().Width() > 100 && rBmpEx.GetSizePixel().Height() > 100
+ && std::abs( rDestSize.Width() - rRefSize.Width()) < 5
+ && std::abs( rDestSize.Height() - rRefSize.Height()) < 5 )
+ ; // ok, assume it's close enough
+ else
+ { // fall back to mtf rendering
+ o_rbNonBitmapActionEncountered = true;
+ return;
+ }
+ }
+}
+
+// This function is based on GraphicManager::ImplCreateOutput(), in fact it mostly copies
+// it, the difference is that this one does not create anything, it only checks if
+// ImplCreateOutput() would use the optimization of using the single bitmap.
+// If you do changes here, change the original function too.
+bool GraphicDisplayCacheEntry::IsCacheableAsBitmap( const GDIMetaFile& rMtf,
+ OutputDevice const * pOut, const Size& rSz )
+{
+ const Size aNewSize( rMtf.GetPrefSize() );
+ GDIMetaFile rOutMtf = rMtf;
+
+ // Count bitmap actions, and flag actions that paint, but
+ // are no bitmaps.
+ sal_Int32 nNumBitmaps(0);
+ bool bNonBitmapActionEncountered(false);
+ if( aNewSize.Width() && aNewSize.Height() && rSz.Width() && rSz.Height() )
+ {
+ const MapMode& rPrefMapMode( rMtf.GetPrefMapMode() );
+ const Size rSizePix( pOut->LogicToPixel( aNewSize, rPrefMapMode ) );
+
+ sal_uInt32 nCurPos;
+ MetaAction* pAct;
+ for( nCurPos = 0, pAct = rOutMtf.FirstAction(); pAct;
+ pAct = rOutMtf.NextAction(), nCurPos++ )
+ {
+ switch( pAct->GetType() )
+ {
+ case MetaActionType::FONT:
+ // FALLTHROUGH intended
+ case MetaActionType::NONE:
+ // FALLTHROUGH intended
+
+ // OutDev state changes (which don't affect bitmap
+ // output)
+ case MetaActionType::LINECOLOR:
+ // FALLTHROUGH intended
+ case MetaActionType::FILLCOLOR:
+ // FALLTHROUGH intended
+ case MetaActionType::TEXTCOLOR:
+ // FALLTHROUGH intended
+ case MetaActionType::TEXTFILLCOLOR:
+ // FALLTHROUGH intended
+ case MetaActionType::TEXTALIGN:
+ // FALLTHROUGH intended
+ case MetaActionType::TEXTLINECOLOR:
+ // FALLTHROUGH intended
+ case MetaActionType::TEXTLINE:
+ // FALLTHROUGH intended
+ case MetaActionType::PUSH:
+ // FALLTHROUGH intended
+ case MetaActionType::POP:
+ // FALLTHROUGH intended
+ case MetaActionType::LAYOUTMODE:
+ // FALLTHROUGH intended
+ case MetaActionType::TEXTLANGUAGE:
+ // FALLTHROUGH intended
+ case MetaActionType::COMMENT:
+ break;
+
+ // bitmap output methods
+ case MetaActionType::BMP:
+ if( !nNumBitmaps && !bNonBitmapActionEncountered )
+ {
+ MetaBmpAction* pAction = static_cast<MetaBmpAction*>(pAct);
+
+ checkMetadataBitmap(
+ BitmapEx( pAction->GetBitmap()),
+ Point(), Size(),
+ pOut->LogicToPixel( pAction->GetPoint(),
+ rPrefMapMode ),
+ pAction->GetBitmap().GetSizePixel(),
+ rSizePix,
+ bNonBitmapActionEncountered );
+ }
+ ++nNumBitmaps;
+ break;
+
+ case MetaActionType::BMPSCALE:
+ if( !nNumBitmaps && !bNonBitmapActionEncountered )
+ {
+ MetaBmpScaleAction* pAction = static_cast<MetaBmpScaleAction*>(pAct);
+
+ checkMetadataBitmap(
+ BitmapEx( pAction->GetBitmap()),
+ Point(), Size(),
+ pOut->LogicToPixel( pAction->GetPoint(),
+ rPrefMapMode ),
+ pOut->LogicToPixel( pAction->GetSize(),
+ rPrefMapMode ),
+ rSizePix,
+ bNonBitmapActionEncountered );
+ }
+ ++nNumBitmaps;
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ if( !nNumBitmaps && !bNonBitmapActionEncountered )
+ {
+ MetaBmpScalePartAction* pAction = static_cast<MetaBmpScalePartAction*>(pAct);
+
+ checkMetadataBitmap( BitmapEx( pAction->GetBitmap() ),
+ pAction->GetSrcPoint(),
+ pAction->GetSrcSize(),
+ pOut->LogicToPixel( pAction->GetDestPoint(),
+ rPrefMapMode ),
+ pOut->LogicToPixel( pAction->GetDestSize(),
+ rPrefMapMode ),
+ rSizePix,
+ bNonBitmapActionEncountered );
+ }
+ ++nNumBitmaps;
+ break;
+
+ case MetaActionType::BMPEX:
+ if( !nNumBitmaps && !bNonBitmapActionEncountered )
+ {
+ MetaBmpExAction* pAction = static_cast<MetaBmpExAction*>(pAct);
+
+ checkMetadataBitmap(
+ pAction->GetBitmapEx(),
+ Point(), Size(),
+ pOut->LogicToPixel( pAction->GetPoint(),
+ rPrefMapMode ),
+ pAction->GetBitmapEx().GetSizePixel(),
+ rSizePix,
+ bNonBitmapActionEncountered );
+ }
+ ++nNumBitmaps;
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ if( !nNumBitmaps && !bNonBitmapActionEncountered )
+ {
+ MetaBmpExScaleAction* pAction = static_cast<MetaBmpExScaleAction*>(pAct);
+
+ checkMetadataBitmap(
+ pAction->GetBitmapEx(),
+ Point(), Size(),
+ pOut->LogicToPixel( pAction->GetPoint(),
+ rPrefMapMode ),
+ pOut->LogicToPixel( pAction->GetSize(),
+ rPrefMapMode ),
+ rSizePix,
+ bNonBitmapActionEncountered );
+ }
+ ++nNumBitmaps;
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ if( !nNumBitmaps && !bNonBitmapActionEncountered )
+ {
+ MetaBmpExScalePartAction* pAction = static_cast<MetaBmpExScalePartAction*>(pAct);
+
+ checkMetadataBitmap( pAction->GetBitmapEx(),
+ pAction->GetSrcPoint(),
+ pAction->GetSrcSize(),
+ pOut->LogicToPixel( pAction->GetDestPoint(),
+ rPrefMapMode ),
+ pOut->LogicToPixel( pAction->GetDestSize(),
+ rPrefMapMode ),
+ rSizePix,
+ bNonBitmapActionEncountered );
+ }
+ ++nNumBitmaps;
+ break;
+
+ // these actions actually output something (that's
+ // different from a bitmap)
+ case MetaActionType::RASTEROP:
+ if( static_cast<MetaRasterOpAction*>(pAct)->GetRasterOp() == RasterOp::OverPaint )
+ break;
+ SAL_FALLTHROUGH;
+ case MetaActionType::PIXEL:
+ SAL_FALLTHROUGH;
+ case MetaActionType::POINT:
+ SAL_FALLTHROUGH;
+ case MetaActionType::LINE:
+ SAL_FALLTHROUGH;
+ case MetaActionType::RECT:
+ SAL_FALLTHROUGH;
+ case MetaActionType::ROUNDRECT:
+ SAL_FALLTHROUGH;
+ case MetaActionType::ELLIPSE:
+ SAL_FALLTHROUGH;
+ case MetaActionType::ARC:
+ SAL_FALLTHROUGH;
+ case MetaActionType::PIE:
+ SAL_FALLTHROUGH;
+ case MetaActionType::CHORD:
+ SAL_FALLTHROUGH;
+ case MetaActionType::POLYLINE:
+ SAL_FALLTHROUGH;
+ case MetaActionType::POLYGON:
+ SAL_FALLTHROUGH;
+ case MetaActionType::POLYPOLYGON:
+ SAL_FALLTHROUGH;
+
+ case MetaActionType::TEXT:
+ SAL_FALLTHROUGH;
+ case MetaActionType::TEXTARRAY:
+ SAL_FALLTHROUGH;
+ case MetaActionType::STRETCHTEXT:
+ SAL_FALLTHROUGH;
+ case MetaActionType::TEXTRECT:
+ SAL_FALLTHROUGH;
+
+ case MetaActionType::MASK:
+ SAL_FALLTHROUGH;
+ case MetaActionType::MASKSCALE:
+ SAL_FALLTHROUGH;
+ case MetaActionType::MASKSCALEPART:
+ SAL_FALLTHROUGH;
+
+ case MetaActionType::GRADIENT:
+ SAL_FALLTHROUGH;
+ case MetaActionType::HATCH:
+ SAL_FALLTHROUGH;
+ case MetaActionType::WALLPAPER:
+ SAL_FALLTHROUGH;
+
+ case MetaActionType::Transparent:
+ SAL_FALLTHROUGH;
+ case MetaActionType::EPS:
+ SAL_FALLTHROUGH;
+ case MetaActionType::FLOATTRANSPARENT:
+ SAL_FALLTHROUGH;
+ case MetaActionType::GRADIENTEX:
+ SAL_FALLTHROUGH;
+
+ // OutDev state changes that _do_ affect bitmap
+ // output
+ case MetaActionType::CLIPREGION:
+ SAL_FALLTHROUGH;
+ case MetaActionType::ISECTRECTCLIPREGION:
+ SAL_FALLTHROUGH;
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ SAL_FALLTHROUGH;
+ case MetaActionType::MOVECLIPREGION:
+ SAL_FALLTHROUGH;
+
+ case MetaActionType::MAPMODE:
+ SAL_FALLTHROUGH;
+ case MetaActionType::REFPOINT:
+ SAL_FALLTHROUGH;
+ default:
+ bNonBitmapActionEncountered = true;
+ break;
+ }
+ }
+ }
+ return nNumBitmaps == 1 && !bNonBitmapActionEncountered;
+}
+
+sal_uLong GraphicDisplayCacheEntry::GetNeededSize( OutputDevice const * pOut, const Point& /*rPt*/, const Size& rSz,
+ const GraphicObject& rObj, const GraphicAttr& rAttr )
+{
+ const Graphic& rGraphic = rObj.GetGraphic();
+ const GraphicType eType = rGraphic.GetType();
+
+ bool canCacheAsBitmap = false;
+ if( GraphicType::Bitmap == eType )
+ canCacheAsBitmap = true;
+ else if( GraphicType::GdiMetafile == eType )
+ canCacheAsBitmap = IsCacheableAsBitmap( rGraphic.GetGDIMetaFile(), pOut, rSz );
+ else
+ return 0;
+ if( canCacheAsBitmap )
+ {
+ const Size aOutSizePix( pOut->LogicToPixel( rSz ) );
+ const long nBitCount = pOut->GetBitCount();
+
+ if( ( aOutSizePix.Width() > MAX_BMP_EXTENT ) ||
+ ( aOutSizePix.Height() > MAX_BMP_EXTENT ) )
+ {
+ return ULONG_MAX;
+ }
+ else if( nBitCount )
+ {
+ sal_uLong nNeededSize = aOutSizePix.Width() * aOutSizePix.Height() * nBitCount / 8;
+ if( rObj.IsTransparent() || ( rAttr.GetRotation() % 3600 ) )
+ nNeededSize += nNeededSize / nBitCount;
+ return nNeededSize;
+ }
+ else
+ {
+ OSL_FAIL( "GraphicDisplayCacheEntry::GetNeededSize(): pOut->GetBitCount() == 0" );
+ return 256000;
+ }
+ }
+ else
+ return rGraphic.GetSizeBytes();
+}
+
+void GraphicDisplayCacheEntry::Draw( OutputDevice* pOut, const Point& rPt, const Size& rSz ) const
+{
+ if( mpMtf )
+ GraphicManager::ImplDraw( pOut, rPt, rSz, *mpMtf, maAttr );
+ else if( mpBmpEx )
+ {
+ if( maAttr.IsRotated() )
+ {
+ tools::Polygon aPoly( tools::Rectangle( rPt, rSz ) );
+
+ aPoly.Rotate( rPt, maAttr.GetRotation() % 3600 );
+ const tools::Rectangle aRotBoundRect( aPoly.GetBoundRect() );
+ pOut->DrawBitmapEx( aRotBoundRect.TopLeft(), aRotBoundRect.GetSize(), *mpBmpEx );
+ }
+ else
+ pOut->DrawBitmapEx( rPt, rSz, *mpBmpEx );
+ }
+}
+
+GraphicCache::GraphicCache( sal_uLong nDisplayCacheSize, sal_uLong nMaxObjDisplayCacheSize ) :
+ maReleaseTimer ( "svtools::GraphicCache maReleaseTimer" ),
+ mnReleaseTimeoutSeconds ( 0 ),
+ mnMaxDisplaySize ( nDisplayCacheSize ),
+ mnMaxObjDisplaySize ( nMaxObjDisplayCacheSize ),
+ mnUsedDisplaySize ( 0 )
+{
+ maReleaseTimer.SetInvokeHandler( LINK( this, GraphicCache, ReleaseTimeoutHdl ) );
+ maReleaseTimer.SetTimeout( 10000 );
+ maReleaseTimer.SetDebugName( "svtools::GraphicCache maReleaseTimer" );
+ maReleaseTimer.Start();
+}
+
+GraphicCache::~GraphicCache()
+{
+ DBG_ASSERT( !maGraphicCache.size(), "GraphicCache::~GraphicCache(): there are some GraphicObjects in cache" );
+ DBG_ASSERT( maDisplayCache.empty(), "GraphicCache::~GraphicCache(): there are some GraphicObjects in display cache" );
+}
+
+void GraphicCache::AddGraphicObject(
+ const GraphicObject& rObj,
+ Graphic& rSubstitute,
+ const OString* pID,
+ const GraphicObject* pCopyObj
+)
+{
+ bool bInserted = false;
+
+ if( !rObj.IsSwappedOut()
+ && ( pID
+ || ( pCopyObj
+ && ( pCopyObj->GetType() != GraphicType::NONE )
+ )
+ || ( rObj.GetType() != GraphicType::NONE )
+ )
+ )
+ {
+ if( pCopyObj
+ && !maGraphicCache.empty()
+ )
+ {
+ for (auto const& elem : maGraphicCache)
+ {
+ if( elem->HasGraphicObjectReference( *pCopyObj ) )
+ {
+ elem->AddGraphicObjectReference( rObj, rSubstitute );
+ bInserted = true;
+ break;
+ }
+ }
+ }
+
+ if( !bInserted )
+ {
+ std::unique_ptr< GraphicID > apID;
+
+ if( !pID )
+ {
+ apID.reset( new GraphicID( rObj ) );
+ }
+
+ for (auto const& elem : maGraphicCache)
+ {
+ const GraphicID& rEntryID = elem->GetID();
+
+ if( pID )
+ {
+ if( rEntryID.GetIDString() == *pID )
+ {
+ elem->TryToSwapIn();
+
+ // since pEntry->TryToSwapIn can modify our current list, we have to
+ // iterate from beginning to add a reference to the appropriate
+ // CacheEntry object; after this, quickly jump out of the outer iteration
+ for (auto const& subelem : maGraphicCache)
+ {
+ const GraphicID& rID = subelem->GetID();
+
+ if( rID.GetIDString() == *pID )
+ {
+ subelem->AddGraphicObjectReference( rObj, rSubstitute );
+ bInserted = true;
+ break;
+ }
+ }
+
+ if( !bInserted )
+ {
+ maGraphicCache.push_back( new GraphicCacheEntry( rObj ) );
+ bInserted = true;
+ }
+ }
+ }
+ else
+ {
+ if( rEntryID == *apID )
+ {
+ elem->AddGraphicObjectReference( rObj, rSubstitute );
+ bInserted = true;
+ }
+ }
+
+ if(bInserted)
+ break;
+ }
+ }
+ }
+
+ if( !bInserted )
+ maGraphicCache.push_back( new GraphicCacheEntry( rObj ) );
+}
+
+void GraphicCache::ReleaseGraphicObject( const GraphicObject& rObj )
+{
+ // Release cached object
+ bool bRemoved = false;
+ GraphicCacheEntryVector::iterator it = maGraphicCache.begin();
+ while (!bRemoved && it != maGraphicCache.end())
+ {
+ bRemoved = (*it)->ReleaseGraphicObjectReference( rObj );
+
+ if( bRemoved && (0 == (*it)->GetGraphicObjectReferenceCount()) )
+ {
+ // if graphic cache entry has no more references,
+ // the corresponding display cache object can be removed
+ GraphicDisplayCacheEntryVector::iterator it2 = maDisplayCache.begin();
+ while( it2 != maDisplayCache.end() )
+ {
+ GraphicDisplayCacheEntry* pDisplayEntry = *it2;
+ if( pDisplayEntry->GetReferencedCacheEntry() == *it )
+ {
+ mnUsedDisplaySize -= pDisplayEntry->GetCacheSize();
+ it2 = maDisplayCache.erase( it2 );
+ delete pDisplayEntry;
+ }
+ else
+ ++it2;
+ }
+
+ // delete graphic cache entry
+ delete *it;
+ it = maGraphicCache.erase( it );
+ }
+ else
+ ++it;
+ }
+
+ DBG_ASSERT( bRemoved, "GraphicCache::ReleaseGraphicObject(...): GraphicObject not found in cache" );
+}
+
+void GraphicCache::GraphicObjectWasSwappedOut( const GraphicObject& rObj )
+{
+ // notify cache that rObj is swapped out (and can thus be pruned
+ // from the cache)
+ GraphicCacheEntry* pEntry = ImplGetCacheEntry( rObj );
+
+ if( pEntry )
+ pEntry->GraphicObjectWasSwappedOut();
+}
+
+void GraphicCache::GraphicObjectWasSwappedIn( const GraphicObject& rObj )
+{
+ GraphicCacheEntry* pEntry = ImplGetCacheEntry( rObj );
+
+ if( pEntry )
+ {
+ if( pEntry->GetID().IsEmpty() )
+ {
+ ReleaseGraphicObject( rObj );
+ AddGraphicObject( rObj, const_cast<Graphic&>(rObj.GetGraphic()), nullptr, nullptr );
+ }
+ else
+ pEntry->GraphicObjectWasSwappedIn( rObj );
+ }
+}
+
+void GraphicCache::SetMaxDisplayCacheSize( sal_uLong nNewCacheSize )
+{
+ mnMaxDisplaySize = nNewCacheSize;
+
+ if( GetMaxDisplayCacheSize() < GetUsedDisplayCacheSize() )
+ ImplFreeDisplayCacheSpace( GetUsedDisplayCacheSize() - GetMaxDisplayCacheSize() );
+}
+
+void GraphicCache::SetCacheTimeout( sal_uLong nTimeoutSeconds )
+{
+ if( mnReleaseTimeoutSeconds == nTimeoutSeconds )
+ return;
+
+ ::salhelper::TTimeValue aReleaseTime;
+
+ if( ( mnReleaseTimeoutSeconds = nTimeoutSeconds ) != 0 )
+ {
+ osl_getSystemTime( &aReleaseTime );
+ aReleaseTime.addTime( ::salhelper::TTimeValue( nTimeoutSeconds, 0 ) );
+ }
+
+ for (auto const& elem : maDisplayCache)
+ {
+ elem->SetReleaseTime( aReleaseTime );
+ }
+}
+
+bool GraphicCache::IsDisplayCacheable( OutputDevice const * pOut, const Point& rPt, const Size& rSz,
+ const GraphicObject& rObj, const GraphicAttr& rAttr ) const
+{
+ return( GraphicDisplayCacheEntry::GetNeededSize( pOut, rPt, rSz, rObj, rAttr ) <=
+ GetMaxObjDisplayCacheSize() );
+}
+
+bool GraphicCache::IsInDisplayCache( OutputDevice const * pOut, const Point& rPt, const Size& rSz,
+ const GraphicObject& rObj, const GraphicAttr& rAttr ) const
+{
+ const Point aPtPixel( pOut->LogicToPixel( rPt ) );
+ const Size aSzPixel( pOut->LogicToPixel( rSz ) );
+ const GraphicCacheEntry* pCacheEntry = const_cast<GraphicCache*>(this)->ImplGetCacheEntry( rObj );
+ bool bFound = false;
+
+ if( pCacheEntry )
+ {
+ for (auto const& elem : maDisplayCache)
+ {
+ if( elem->Matches( pOut, aPtPixel, aSzPixel, pCacheEntry, rAttr ) )
+ {
+ bFound = true;
+ break;
+ }
+ }
+ }
+
+ return bFound;
+}
+
+OString GraphicCache::GetUniqueID( const GraphicObject& rObj ) const
+{
+ OString aRet;
+ GraphicCacheEntry* pEntry = const_cast<GraphicCache*>(this)->ImplGetCacheEntry( rObj );
+
+ // ensure that the entry is correctly initialized (it has to be read at least once)
+ if( pEntry && pEntry->GetID().IsEmpty() )
+ {
+ pEntry->TryToSwapIn();
+ // do another call to ImplGetCacheEntry in case of modified entry list
+ pEntry = const_cast<GraphicCache*>(this)->ImplGetCacheEntry( rObj );
+ }
+
+ if( pEntry )
+ aRet = pEntry->GetID().GetIDString();
+
+ return aRet;
+}
+
+bool GraphicCache::CreateDisplayCacheObj( OutputDevice const * pOut, const Point& rPt, const Size& rSz,
+ const GraphicObject& rObj, const GraphicAttr& rAttr,
+ const BitmapEx& rBmpEx )
+{
+ const sal_uLong nNeededSize = GraphicDisplayCacheEntry::GetNeededSize( pOut, rPt, rSz, rObj, rAttr );
+ bool bRet = false;
+
+ if( nNeededSize <= GetMaxObjDisplayCacheSize() )
+ {
+ if( nNeededSize > GetFreeDisplayCacheSize() )
+ ImplFreeDisplayCacheSpace( nNeededSize - GetFreeDisplayCacheSize() );
+
+ GraphicDisplayCacheEntry* pNewEntry = new GraphicDisplayCacheEntry( ImplGetCacheEntry( rObj ),
+ pOut, rPt, rSz, rObj, rAttr, rBmpEx );
+
+ if( GetCacheTimeout() )
+ {
+ ::salhelper::TTimeValue aReleaseTime;
+
+ osl_getSystemTime( &aReleaseTime );
+ aReleaseTime.addTime( ::salhelper::TTimeValue( GetCacheTimeout(), 0 ) );
+ pNewEntry->SetReleaseTime( aReleaseTime );
+ }
+
+ maDisplayCache.push_back( pNewEntry );
+ mnUsedDisplaySize += pNewEntry->GetCacheSize();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool GraphicCache::CreateDisplayCacheObj( OutputDevice const * pOut, const Point& rPt, const Size& rSz,
+ const GraphicObject& rObj, const GraphicAttr& rAttr,
+ const GDIMetaFile& rMtf )
+{
+ const sal_uLong nNeededSize = GraphicDisplayCacheEntry::GetNeededSize( pOut, rPt, rSz, rObj, rAttr );
+ bool bRet = false;
+
+ if( nNeededSize <= GetMaxObjDisplayCacheSize() )
+ {
+ if( nNeededSize > GetFreeDisplayCacheSize() )
+ ImplFreeDisplayCacheSpace( nNeededSize - GetFreeDisplayCacheSize() );
+
+ GraphicDisplayCacheEntry* pNewEntry = new GraphicDisplayCacheEntry( ImplGetCacheEntry( rObj ),
+ pOut, rPt, rSz, rObj, rAttr, rMtf );
+
+ if( GetCacheTimeout() )
+ {
+ ::salhelper::TTimeValue aReleaseTime;
+
+ osl_getSystemTime( &aReleaseTime );
+ aReleaseTime.addTime( ::salhelper::TTimeValue( GetCacheTimeout(), 0 ) );
+ pNewEntry->SetReleaseTime( aReleaseTime );
+ }
+
+ maDisplayCache.push_back( pNewEntry );
+ mnUsedDisplaySize += pNewEntry->GetCacheSize();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool GraphicCache::DrawDisplayCacheObj( OutputDevice* pOut, const Point& rPt, const Size& rSz,
+ const GraphicObject& rObj, const GraphicAttr& rAttr )
+{
+ const Point aPtPixel( pOut->LogicToPixel( rPt ) );
+ const Size aSzPixel( pOut->LogicToPixel( rSz ) );
+ const GraphicCacheEntry* pCacheEntry = ImplGetCacheEntry( rObj );
+ GraphicDisplayCacheEntry* pDisplayCacheEntry = nullptr;
+ GraphicDisplayCacheEntryVector::iterator it = maDisplayCache.begin();
+ bool bRet = false;
+
+ while( !bRet && it != maDisplayCache.end() )
+ {
+ pDisplayCacheEntry = *it;
+ if( pDisplayCacheEntry->Matches( pOut, aPtPixel, aSzPixel, pCacheEntry, rAttr ) )
+ {
+ ::salhelper::TTimeValue aReleaseTime;
+
+ // put found object at last used position
+ it = maDisplayCache.erase( it );
+ maDisplayCache.push_back( pDisplayCacheEntry );
+
+ if( GetCacheTimeout() )
+ {
+ osl_getSystemTime( &aReleaseTime );
+ aReleaseTime.addTime( ::salhelper::TTimeValue( GetCacheTimeout(), 0 ) );
+ }
+
+ pDisplayCacheEntry->SetReleaseTime( aReleaseTime );
+ bRet = true;
+ }
+ else
+ ++it;
+ }
+
+ if( bRet )
+ pDisplayCacheEntry->Draw( pOut, rPt, rSz );
+
+ return bRet;
+}
+
+bool GraphicCache::ImplFreeDisplayCacheSpace( sal_uLong nSizeToFree )
+{
+ sal_uLong nFreedSize = 0;
+
+ if( nSizeToFree )
+ {
+ GraphicDisplayCacheEntryVector::iterator it = maDisplayCache.begin();
+
+ if( nSizeToFree > mnUsedDisplaySize )
+ nSizeToFree = mnUsedDisplaySize;
+
+ while( it != maDisplayCache.end() )
+ {
+ GraphicDisplayCacheEntry* pCacheObj = *it;
+
+ nFreedSize += pCacheObj->GetCacheSize();
+ mnUsedDisplaySize -= pCacheObj->GetCacheSize();
+ it = maDisplayCache.erase( it );
+ delete pCacheObj;
+
+ if( nFreedSize >= nSizeToFree )
+ break;
+ }
+ }
+
+ return( nFreedSize >= nSizeToFree );
+}
+
+GraphicCacheEntry* GraphicCache::ImplGetCacheEntry( const GraphicObject& rObj )
+{
+ GraphicCacheEntry* pRet = nullptr;
+
+ for (auto const& elem : maGraphicCache)
+ {
+ if( elem->HasGraphicObjectReference( rObj ) )
+ {
+ return elem;
+ }
+ }
+
+ return pRet;
+}
+
+IMPL_LINK( GraphicCache, ReleaseTimeoutHdl, Timer*, pTimer, void )
+{
+ pTimer->Stop();
+
+ ::salhelper::TTimeValue aCurTime;
+ GraphicDisplayCacheEntryVector::iterator it = maDisplayCache.begin();
+
+ osl_getSystemTime( &aCurTime );
+
+ while( it != maDisplayCache.end() )
+ {
+ GraphicDisplayCacheEntry* pDisplayEntry = *it;
+ const ::salhelper::TTimeValue& rReleaseTime = pDisplayEntry->GetReleaseTime();
+
+ if( !rReleaseTime.isEmpty() && ( rReleaseTime < aCurTime ) )
+ {
+ mnUsedDisplaySize -= pDisplayEntry->GetCacheSize();
+ it = maDisplayCache.erase( it );
+ delete pDisplayEntry;
+ }
+ else
+ ++it;
+ }
+
+ pTimer->Start();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */