/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GRAPHIC_MTFTOBMP_MAXEXT 2048 #define GRAPHIC_STREAMBUFSIZE 8192UL #define SWAP_FORMAT_ID COMPAT_FORMAT( 'S', 'W', 'A', 'P' ) using namespace com::sun::star; class ImpSwapFile { private: utl::TempFileFast maTempFile; OUString maOriginURL; public: ImpSwapFile(OUString aOriginURL) : maOriginURL(std::move(aOriginURL)) { } SvStream* getStream() { return maTempFile.GetStream(StreamMode::READWRITE); } OUString const & getOriginURL() const { return maOriginURL; } }; SvStream* ImpGraphic::getSwapFileStream() const { if (mpSwapFile) return mpSwapFile->getStream(); return nullptr; } ImpGraphic::ImpGraphic(bool bDefault) : MemoryManaged(false) , meType(bDefault ? GraphicType::Default : GraphicType::NONE) { } ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic) : MemoryManaged(rImpGraphic) , maMetaFile(rImpGraphic.maMetaFile) , maBitmapEx(rImpGraphic.maBitmapEx) , maSwapInfo(rImpGraphic.maSwapInfo) , mpContext(rImpGraphic.mpContext) , mpSwapFile(rImpGraphic.mpSwapFile) , mpGfxLink(rImpGraphic.mpGfxLink) , maVectorGraphicData(rImpGraphic.maVectorGraphicData) , meType(rImpGraphic.meType) , mnSizeBytes(rImpGraphic.mnSizeBytes) , mbSwapOut(rImpGraphic.mbSwapOut) , mbDummyContext(rImpGraphic.mbDummyContext) , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink) , mbPrepared(rImpGraphic.mbPrepared) { updateCurrentSizeInBytes(mnSizeBytes); // Special case for animations if (rImpGraphic.mpAnimation) { mpAnimation = std::make_unique(*rImpGraphic.mpAnimation); maBitmapEx = mpAnimation->GetBitmapEx(); } } ImpGraphic::ImpGraphic(ImpGraphic&& rImpGraphic) noexcept : MemoryManaged(rImpGraphic) , maMetaFile(std::move(rImpGraphic.maMetaFile)) , maBitmapEx(std::move(rImpGraphic.maBitmapEx)) , maSwapInfo(std::move(rImpGraphic.maSwapInfo)) , mpAnimation(std::move(rImpGraphic.mpAnimation)) , mpContext(std::move(rImpGraphic.mpContext)) , mpSwapFile(std::move(rImpGraphic.mpSwapFile)) , mpGfxLink(std::move(rImpGraphic.mpGfxLink)) , maVectorGraphicData(std::move(rImpGraphic.maVectorGraphicData)) , meType(rImpGraphic.meType) , mnSizeBytes(rImpGraphic.mnSizeBytes) , mbSwapOut(rImpGraphic.mbSwapOut) , mbDummyContext(rImpGraphic.mbDummyContext) , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink) , mbPrepared (rImpGraphic.mbPrepared) { updateCurrentSizeInBytes(mnSizeBytes); rImpGraphic.clear(); rImpGraphic.mbDummyContext = false; } ImpGraphic::ImpGraphic(std::shared_ptr xGfxLink, sal_Int32 nPageIndex) : MemoryManaged(true) , mpGfxLink(std::move(xGfxLink)) , meType(GraphicType::Bitmap) , mbSwapOut(true) { maSwapInfo.mbIsTransparent = true; maSwapInfo.mbIsAlpha = true; maSwapInfo.mbIsEPS = false; maSwapInfo.mbIsAnimated = false; maSwapInfo.mnAnimationLoopCount = 0; maSwapInfo.mnPageIndex = nPageIndex; ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(GraphicExternalLink aGraphicExternalLink) : MemoryManaged(true) , meType(GraphicType::Default) , maGraphicExternalLink(std::move(aGraphicExternalLink)) { ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(const BitmapEx& rBitmapEx) : MemoryManaged(!rBitmapEx.IsEmpty()) , maBitmapEx(rBitmapEx) , meType(rBitmapEx.IsEmpty() ? GraphicType::NONE : GraphicType::Bitmap) { ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(const std::shared_ptr& rVectorGraphicDataPtr) : MemoryManaged(bool(rVectorGraphicDataPtr)) , maVectorGraphicData(rVectorGraphicDataPtr) , meType(rVectorGraphicDataPtr ? GraphicType::Bitmap : GraphicType::NONE) { ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(const Animation& rAnimation) : MemoryManaged(true) , maBitmapEx(rAnimation.GetBitmapEx()) , mpAnimation(std::make_unique(rAnimation)) , meType(GraphicType::Bitmap) { ensureCurrentSizeInBytes(); } ImpGraphic::ImpGraphic(const GDIMetaFile& rMetafile) : MemoryManaged(true) , maMetaFile(rMetafile) , meType(GraphicType::GdiMetafile) { ensureCurrentSizeInBytes(); } ImpGraphic::~ImpGraphic() { } ImpGraphic& ImpGraphic::operator=( const ImpGraphic& rImpGraphic ) { if( &rImpGraphic != this ) { maMetaFile = rImpGraphic.maMetaFile; meType = rImpGraphic.meType; mnSizeBytes = rImpGraphic.mnSizeBytes; updateCurrentSizeInBytes(mnSizeBytes); maSwapInfo = rImpGraphic.maSwapInfo; mpContext = rImpGraphic.mpContext; mbDummyContext = rImpGraphic.mbDummyContext; maGraphicExternalLink = rImpGraphic.maGraphicExternalLink; mpAnimation.reset(); if ( rImpGraphic.mpAnimation ) { mpAnimation = std::make_unique( *rImpGraphic.mpAnimation ); maBitmapEx = mpAnimation->GetBitmapEx(); } else { maBitmapEx = rImpGraphic.maBitmapEx; } mbSwapOut = rImpGraphic.mbSwapOut; mpSwapFile = rImpGraphic.mpSwapFile; mbPrepared = rImpGraphic.mbPrepared; mpGfxLink = rImpGraphic.mpGfxLink; maVectorGraphicData = rImpGraphic.maVectorGraphicData; resetLastUsed(); changeExisting(mnSizeBytes); } return *this; } ImpGraphic& ImpGraphic::operator=(ImpGraphic&& rImpGraphic) { maMetaFile = std::move(rImpGraphic.maMetaFile); meType = rImpGraphic.meType; mnSizeBytes = rImpGraphic.mnSizeBytes; maSwapInfo = std::move(rImpGraphic.maSwapInfo); mpContext = std::move(rImpGraphic.mpContext); mbDummyContext = rImpGraphic.mbDummyContext; mpAnimation = std::move(rImpGraphic.mpAnimation); maBitmapEx = std::move(rImpGraphic.maBitmapEx); mbSwapOut = rImpGraphic.mbSwapOut; mpSwapFile = std::move(rImpGraphic.mpSwapFile); mpGfxLink = std::move(rImpGraphic.mpGfxLink); maVectorGraphicData = std::move(rImpGraphic.maVectorGraphicData); maGraphicExternalLink = rImpGraphic.maGraphicExternalLink; mbPrepared = rImpGraphic.mbPrepared; rImpGraphic.clear(); rImpGraphic.mbDummyContext = false; resetLastUsed(); changeExisting(mnSizeBytes); return *this; } bool ImpGraphic::operator==( const ImpGraphic& rOther ) const { if( this == &rOther ) return true; if (mbPrepared && rOther.mbPrepared) return (*mpGfxLink == *rOther.mpGfxLink); if (!isAvailable() || !rOther.isAvailable()) return false; if ( meType != rOther.meType ) return false; bool bRet = false; switch( meType ) { case GraphicType::NONE: case GraphicType::Default: return true; case GraphicType::GdiMetafile: return ( rOther.maMetaFile == maMetaFile ); case GraphicType::Bitmap: { if(maVectorGraphicData) { if(maVectorGraphicData == rOther.maVectorGraphicData) { // equal instances bRet = true; } else if(rOther.maVectorGraphicData) { // equal content bRet = (*maVectorGraphicData) == (*rOther.maVectorGraphicData); } } else if( mpAnimation ) { if( rOther.mpAnimation && ( *rOther.mpAnimation == *mpAnimation ) ) bRet = true; } else if( !rOther.mpAnimation && ( rOther.maBitmapEx == maBitmapEx ) ) { bRet = true; } } break; } return bRet; } const std::shared_ptr& ImpGraphic::getVectorGraphicData() const { ensureAvailable(); return maVectorGraphicData; } void ImpGraphic::createSwapInfo() { if (isSwappedOut()) return; if (!maBitmapEx.IsEmpty()) maSwapInfo.maSizePixel = maBitmapEx.GetSizePixel(); else maSwapInfo.maSizePixel = Size(); maSwapInfo.maPrefMapMode = getPrefMapMode(); maSwapInfo.maPrefSize = getPrefSize(); maSwapInfo.mbIsAnimated = isAnimated(); maSwapInfo.mbIsEPS = isEPS(); maSwapInfo.mbIsTransparent = isTransparent(); maSwapInfo.mbIsAlpha = isAlpha(); maSwapInfo.mnAnimationLoopCount = getAnimationLoopCount(); maSwapInfo.mnPageIndex = getPageNumber(); } void ImpGraphic::clearGraphics() { maBitmapEx.Clear(); maMetaFile.Clear(); mpAnimation.reset(); maVectorGraphicData.reset(); } void ImpGraphic::setPrepared(bool bAnimated, const Size* pSizeHint) { mbPrepared = true; mbSwapOut = true; meType = GraphicType::Bitmap; SvMemoryStream aMemoryStream(const_cast(mpGfxLink->GetData()), mpGfxLink->GetDataSize(), StreamMode::READ | StreamMode::WRITE); if (pSizeHint) { maSwapInfo.maPrefSize = *pSizeHint; maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM); } GraphicDescriptor aDescriptor(aMemoryStream, nullptr); if (aDescriptor.Detect(true)) { if (!pSizeHint) { // If we have logic size, work with that, as later pixel -> logic // conversion will work with the output device DPI, not the graphic // DPI. Size aLogSize = aDescriptor.GetSize_100TH_MM(); if (aDescriptor.GetPreferredLogSize() && aDescriptor.GetPreferredMapMode()) { maSwapInfo.maPrefSize = *aDescriptor.GetPreferredLogSize(); maSwapInfo.maPrefMapMode = *aDescriptor.GetPreferredMapMode(); } else if (aLogSize.getWidth() && aLogSize.getHeight()) { maSwapInfo.maPrefSize = aLogSize; maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM); } else { maSwapInfo.maPrefSize = aDescriptor.GetSizePixel(); maSwapInfo.maPrefMapMode = MapMode(MapUnit::MapPixel); } } maSwapInfo.maSizePixel = aDescriptor.GetSizePixel(); maSwapInfo.mbIsTransparent = aDescriptor.IsTransparent(); maSwapInfo.mbIsAlpha = aDescriptor.IsAlpha(); } else { maSwapInfo.mbIsTransparent = false; maSwapInfo.mbIsAlpha = false; } maSwapInfo.mnAnimationLoopCount = 0; maSwapInfo.mbIsEPS = false; maSwapInfo.mbIsAnimated = bAnimated; if (maVectorGraphicData) maSwapInfo.mnPageIndex = maVectorGraphicData->getPageIndex(); } void ImpGraphic::clear() { mpSwapFile.reset(); mbSwapOut = false; mbPrepared = false; // cleanup clearGraphics(); meType = GraphicType::NONE; mnSizeBytes = 0; changeExisting(mnSizeBytes); maGraphicExternalLink.msURL.clear(); } bool ImpGraphic::isSupportedGraphic() const { return meType != GraphicType::NONE; } bool ImpGraphic::isTransparent() const { bool bRet(true); if (mbSwapOut) { bRet = maSwapInfo.mbIsTransparent; } else if (meType == GraphicType::Bitmap && !maVectorGraphicData) { bRet = mpAnimation ? mpAnimation->IsTransparent() : maBitmapEx.IsAlpha(); } return bRet; } bool ImpGraphic::isAlpha() const { bool bRet(false); if (mbSwapOut) { bRet = maSwapInfo.mbIsAlpha; } else if (maVectorGraphicData) { bRet = true; } else if (meType == GraphicType::Bitmap) { bRet = (nullptr == mpAnimation && maBitmapEx.IsAlpha()); } return bRet; } bool ImpGraphic::isAnimated() const { return mbSwapOut ? maSwapInfo.mbIsAnimated : mpAnimation != nullptr; } bool ImpGraphic::isEPS() const { if (mbSwapOut) return maSwapInfo.mbIsEPS; return( ( meType == GraphicType::GdiMetafile ) && ( maMetaFile.GetActionSize() > 0 ) && ( maMetaFile.GetAction( 0 )->GetType() == MetaActionType::EPS ) ); } bool ImpGraphic::isAvailable() const { return !mbPrepared && !mbSwapOut; } bool ImpGraphic::makeAvailable() { return ensureAvailable(); } void ImpGraphic::updateBitmapFromVectorGraphic(const Size& pixelSize) const { assert (maVectorGraphicData); // use maBitmapEx as local buffer for rendered vector image if (pixelSize.Width() && pixelSize.Height()) { if (maBitmapEx.IsEmpty() || maBitmapEx.GetSizePixel() != pixelSize) const_cast(this)->maBitmapEx = maVectorGraphicData->getBitmap(pixelSize); } else // maVectorGraphicData caches the replacement, so updating unconditionally is cheap { const_cast(this)->maBitmapEx = maVectorGraphicData->getReplacement(); } if (maExPrefSize.getWidth() && maExPrefSize.getHeight()) const_cast(this)->maBitmapEx.SetPrefSize(maExPrefSize); } Bitmap ImpGraphic::getBitmap(const GraphicConversionParameters& rParameters) const { Bitmap aRetBmp; ensureAvailable(); if( meType == GraphicType::Bitmap ) { if (!mpAnimation && maVectorGraphicData) updateBitmapFromVectorGraphic(rParameters.getSizePixel()); const BitmapEx& rRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx ); aRetBmp = rRetBmpEx.GetBitmap( COL_WHITE ); if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height()) aRetBmp.Scale(rParameters.getSizePixel()); } else if( ( meType != GraphicType::Default ) && isSupportedGraphic() ) { if(maBitmapEx.IsEmpty()) { // calculate size ScopedVclPtrInstance< VirtualDevice > aVDev; Size aDrawSize(aVDev->LogicToPixel(maMetaFile.GetPrefSize(), maMetaFile.GetPrefMapMode())); if(rParameters.getSizePixel().Width() && rParameters.getSizePixel().Height()) { // apply given size if exists aDrawSize = rParameters.getSizePixel(); } if(aDrawSize.Width() && aDrawSize.Height() && !rParameters.getUnlimitedSize() && (aDrawSize.Width() > GRAPHIC_MTFTOBMP_MAXEXT || aDrawSize.Height() > GRAPHIC_MTFTOBMP_MAXEXT)) { // limit bitmap size to a maximum of GRAPHIC_MTFTOBMP_MAXEXT x GRAPHIC_MTFTOBMP_MAXEXT double fWH(static_cast(aDrawSize.Width()) / static_cast(aDrawSize.Height())); if(fWH <= 1.0) { aDrawSize.setWidth(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT * fWH)); aDrawSize.setHeight(GRAPHIC_MTFTOBMP_MAXEXT); } else { aDrawSize.setWidth(GRAPHIC_MTFTOBMP_MAXEXT); aDrawSize.setHeight(basegfx::fround(GRAPHIC_MTFTOBMP_MAXEXT / fWH)); } } // calculate pixel size. Normally, it's the same as aDrawSize, but may // need to be extended when hairlines are on the right or bottom edge Size aPixelSize(aDrawSize); if(GraphicType::GdiMetafile == getType()) { // tdf#126319 Removed correction based on hairline-at-the-extremes of // the metafile. The task shows that this is no longer sufficient since // less hairlines get used in general - what is good, but breaks that // old fix. Anyways, hairlines are a left-over from non-AA times // when it was not possible to paint lines taller than one pixel. // This might need to be corrected further using primitives and // the possibility to get better-quality ranges for correction. For // now, always add that one pixel. aPixelSize.setWidth(aPixelSize.getWidth() + 1); aPixelSize.setHeight(aPixelSize.getHeight() + 1); } if(aVDev->SetOutputSizePixel(aPixelSize)) { if(rParameters.getAntiAliase()) { aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::Enable); } if(rParameters.getSnapHorVerLines()) { aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::PixelSnapHairline); } draw(*aVDev, Point(), aDrawSize); // use maBitmapEx as local buffer for rendered metafile const_cast< ImpGraphic* >(this)->maBitmapEx = aVDev->GetBitmapEx( Point(), aVDev->GetOutputSizePixel() ); } } aRetBmp = maBitmapEx.GetBitmap(); } if( !aRetBmp.IsEmpty() ) { aRetBmp.SetPrefMapMode(getPrefMapMode()); aRetBmp.SetPrefSize(getPrefSize()); } return aRetBmp; } BitmapEx ImpGraphic::getBitmapEx(const GraphicConversionParameters& rParameters) const { BitmapEx aRetBmpEx; ensureAvailable(); if( meType == GraphicType::Bitmap ) { if (!mpAnimation && maVectorGraphicData) updateBitmapFromVectorGraphic(rParameters.getSizePixel()); aRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx ); if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height()) { aRetBmpEx.Scale( rParameters.getSizePixel(), BmpScaleFlag::Fast); } } else if( ( meType != GraphicType::Default ) && isSupportedGraphic() ) { if(maBitmapEx.IsEmpty()) { const ImpGraphic aMonoMask( maMetaFile.GetMonochromeMtf( COL_BLACK ) ); // use maBitmapEx as local buffer for rendered metafile const_cast< ImpGraphic* >(this)->maBitmapEx = BitmapEx(getBitmap(rParameters), aMonoMask.getBitmap(rParameters)); } aRetBmpEx = maBitmapEx; } return aRetBmpEx; } Animation ImpGraphic::getAnimation() const { Animation aAnimation; ensureAvailable(); if( mpAnimation ) aAnimation = *mpAnimation; return aAnimation; } const BitmapEx& ImpGraphic::getBitmapExRef() const { ensureAvailable(); return maBitmapEx; } const GDIMetaFile& ImpGraphic::getGDIMetaFile() const { ensureAvailable(); if (!maMetaFile.GetActionSize() && maVectorGraphicData && (VectorGraphicDataType::Emf == maVectorGraphicData->getType() || VectorGraphicDataType::Wmf == maVectorGraphicData->getType())) { // If we have a Emf/Wmf VectorGraphic object, we // need a way to get the Metafile data out of the primitive // representation. Use a strict virtual hook (MetafileAccessor) // to access the MetafilePrimitive2D directly. Also see comments in // XEmfParser about this. const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > > aSequence(maVectorGraphicData->getPrimitive2DSequence()); if (1 == aSequence.size()) { // try to cast to MetafileAccessor implementation const css::uno::Reference< css::graphic::XPrimitive2D > xReference(aSequence[0]); auto pUnoPrimitive = static_cast< const drawinglayer::primitive2d::UnoPrimitive2D* >(xReference.get()); if (pUnoPrimitive) { const MetafileAccessor* pMetafileAccessor = dynamic_cast< const MetafileAccessor* >(pUnoPrimitive->getBasePrimitive2D().get()); if (pMetafileAccessor) { // it is a MetafileAccessor implementation, get Metafile pMetafileAccessor->accessMetafile(const_cast< ImpGraphic* >(this)->maMetaFile); } } } } if (GraphicType::Bitmap == meType && !maMetaFile.GetActionSize()) { if (maVectorGraphicData) updateBitmapFromVectorGraphic(); // #i119735# // Use the local maMetaFile as container for a metafile-representation // of the bitmap graphic. This will be done only once, thus be buffered. // I checked all usages of maMetaFile, it is only used when type is not // GraphicType::Bitmap. In operator= it will get copied, thus buffering will // survive copying (change this if not wanted) ImpGraphic* pThat = const_cast< ImpGraphic* >(this); // #123983# directly create a metafile with the same PrefSize and PrefMapMode // the bitmap has, this will be an always correct metafile if(maBitmapEx.IsAlpha()) { pThat->maMetaFile.AddAction(new MetaBmpExScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx)); } else { pThat->maMetaFile.AddAction(new MetaBmpScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx.GetBitmap())); } pThat->maMetaFile.Stop(); pThat->maMetaFile.WindStart(); pThat->maMetaFile.SetPrefSize(maBitmapEx.GetPrefSize()); pThat->maMetaFile.SetPrefMapMode(maBitmapEx.GetPrefMapMode()); } return maMetaFile; } Size ImpGraphic::getSizePixel() const { Size aSize; if (isSwappedOut()) aSize = maSwapInfo.maSizePixel; else aSize = getBitmapEx(GraphicConversionParameters()).GetSizePixel(); return aSize; } Size ImpGraphic::getPrefSize() const { Size aSize; if (isSwappedOut()) { aSize = maSwapInfo.maPrefSize; } else { switch (meType) { case GraphicType::Bitmap: { if (maVectorGraphicData && maBitmapEx.IsEmpty()) { if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight()) { // svg not yet buffered in maBitmapEx, return size derived from range const basegfx::B2DRange& rRange = maVectorGraphicData->getRange(); #ifdef MACOSX // tdf#157680 scale down estimated size of embedded PDF // For some unknown reason, the embedded PDF sizes // are 20x larger than expected. This only occurs on // macOS so possibly there is some special conversion // from MapUnit::MapPoint to MapUnit::MapTwip elsewhere // in the code. if (maVectorGraphicData->getType() == VectorGraphicDataType::Pdf) aSize = Size(basegfx::fround(rRange.getWidth() / 20.0f), basegfx::fround(rRange.getHeight() / 20.0f)); else #endif aSize = Size(basegfx::fround(rRange.getWidth()), basegfx::fround(rRange.getHeight())); } else { aSize = maExPrefSize; } } else { aSize = maBitmapEx.GetPrefSize(); if( !aSize.Width() || !aSize.Height() ) { aSize = maBitmapEx.GetSizePixel(); } } } break; case GraphicType::GdiMetafile: { aSize = maMetaFile.GetPrefSize(); } break; case GraphicType::NONE: case GraphicType::Default: break; } } return aSize; } void ImpGraphic::setValuesForPrefSize(const Size& rPrefSize) { switch (meType) { case GraphicType::Bitmap: { // used when importing a writer FlyFrame with SVG as graphic, added conversion // to allow setting the PrefSize at the BitmapEx to hold it if (maVectorGraphicData) { maExPrefSize = rPrefSize; } // #108077# Push through pref size to animation object, // will be lost on copy otherwise if (mpAnimation) { const_cast< BitmapEx& >(mpAnimation->GetBitmapEx()).SetPrefSize(rPrefSize); } maBitmapEx.SetPrefSize(rPrefSize); } break; case GraphicType::GdiMetafile: { if (isSupportedGraphic()) maMetaFile.SetPrefSize(rPrefSize); } break; case GraphicType::NONE: case GraphicType::Default: break; } } void ImpGraphic::setPrefSize(const Size& rPrefSize) { ensureAvailable(); setValuesForPrefSize(rPrefSize); } MapMode ImpGraphic::getPrefMapMode() const { MapMode aMapMode; if (isSwappedOut()) { aMapMode = maSwapInfo.maPrefMapMode; } else { switch (meType) { case GraphicType::Bitmap: { if (maVectorGraphicData && maBitmapEx.IsEmpty()) { // svg not yet buffered in maBitmapEx, return default PrefMapMode aMapMode = MapMode(MapUnit::Map100thMM); } else { const Size aSize(maBitmapEx.GetPrefSize()); if (aSize.Width() && aSize.Height()) aMapMode = maBitmapEx.GetPrefMapMode(); } } break; case GraphicType::GdiMetafile: { return maMetaFile.GetPrefMapMode(); } break; case GraphicType::NONE: case GraphicType::Default: break; } } return aMapMode; } void ImpGraphic::setValuesForPrefMapMod(const MapMode& rPrefMapMode) { switch (meType) { case GraphicType::Bitmap: { if (maVectorGraphicData) { // ignore for Vector Graphic Data. If this is really used (except the grfcache) // it can be extended by using maBitmapEx as buffer for updateBitmapFromVectorGraphic() } else { // #108077# Push through pref mapmode to animation object, // will be lost on copy otherwise if (mpAnimation) { const_cast(mpAnimation->GetBitmapEx()).SetPrefMapMode(rPrefMapMode); } maBitmapEx.SetPrefMapMode(rPrefMapMode); } } break; case GraphicType::GdiMetafile: { maMetaFile.SetPrefMapMode(rPrefMapMode); } break; case GraphicType::NONE: case GraphicType::Default: break; } } void ImpGraphic::setPrefMapMode(const MapMode& rPrefMapMode) { ensureAvailable(); setValuesForPrefMapMod(rPrefMapMode); } void ImpGraphic::ensureCurrentSizeInBytes() { if (isAvailable()) changeExisting(getSizeBytes()); else changeExisting(0); } sal_uLong ImpGraphic::getSizeBytes() const { if (mnSizeBytes > 0) return mnSizeBytes; if (mbPrepared) ensureAvailable(); switch (meType) { case GraphicType::Bitmap: { if (maVectorGraphicData) { std::pair aPair(maVectorGraphicData->getSizeBytes()); if (VectorGraphicData::State::UNPARSED == aPair.first) { return aPair.second; // don't cache it until Vector Graphic Data is parsed } mnSizeBytes = aPair.second; } else { mnSizeBytes = mpAnimation ? mpAnimation->GetSizeBytes() : maBitmapEx.GetSizeBytes(); } } break; case GraphicType::GdiMetafile: { mnSizeBytes = maMetaFile.GetSizeBytes(); } break; case GraphicType::NONE: case GraphicType::Default: break; } return mnSizeBytes; } void ImpGraphic::draw(OutputDevice& rOutDev, const Point& rDestPt) const { ensureAvailable(); if (isSwappedOut()) return; switch (meType) { case GraphicType::Bitmap: { if (mpAnimation) { mpAnimation->Draw(rOutDev, rDestPt); } else { if (maVectorGraphicData) updateBitmapFromVectorGraphic(); maBitmapEx.Draw(&rOutDev, rDestPt); } } break; case GraphicType::GdiMetafile: { draw(rOutDev, rDestPt, maMetaFile.GetPrefSize()); } break; case GraphicType::Default: case GraphicType::NONE: break; } } void ImpGraphic::draw(OutputDevice& rOutDev, const Point& rDestPt, const Size& rDestSize) const { ensureAvailable(); if (isSwappedOut()) return; switch (meType) { case GraphicType::Bitmap: { if (mpAnimation) { mpAnimation->Draw(rOutDev, rDestPt, rDestSize); } else { if (maVectorGraphicData) updateBitmapFromVectorGraphic(rOutDev.LogicToPixel(rDestSize)); maBitmapEx.Draw(&rOutDev, rDestPt, rDestSize); } } break; case GraphicType::GdiMetafile: { const_cast(this)->maMetaFile.WindStart(); const_cast(this)->maMetaFile.Play(rOutDev, rDestPt, rDestSize); const_cast(this)->maMetaFile.WindStart(); } break; case GraphicType::Default: case GraphicType::NONE: break; } } void ImpGraphic::startAnimation(OutputDevice& rOutDev, const Point& rDestPt, const Size& rDestSize, tools::Long nRendererId, OutputDevice* pFirstFrameOutDev ) { ensureAvailable(); if( isSupportedGraphic() && !isSwappedOut() && mpAnimation ) mpAnimation->Start(rOutDev, rDestPt, rDestSize, nRendererId, pFirstFrameOutDev); } void ImpGraphic::stopAnimation( const OutputDevice* pOutDev, tools::Long nRendererId ) { ensureAvailable(); if( isSupportedGraphic() && !isSwappedOut() && mpAnimation ) mpAnimation->Stop( pOutDev, nRendererId ); } void ImpGraphic::setAnimationNotifyHdl( const Link& rLink ) { ensureAvailable(); if( mpAnimation ) mpAnimation->SetNotifyHdl( rLink ); } Link ImpGraphic::getAnimationNotifyHdl() const { Link aLink; ensureAvailable(); if( mpAnimation ) aLink = mpAnimation->GetNotifyHdl(); return aLink; } sal_uInt32 ImpGraphic::getAnimationLoopCount() const { if (mbSwapOut) return maSwapInfo.mnAnimationLoopCount; return mpAnimation ? mpAnimation->GetLoopCount() : 0; } void ImpGraphic::setContext( const std::shared_ptr& pReader ) { mpContext = pReader; mbDummyContext = false; } bool ImpGraphic::swapInContent(SvStream& rStream) { bool bRet = false; sal_uInt32 nId; sal_Int32 nType; sal_Int32 nLength; rStream.ReadUInt32(nId); // check version if (SWAP_FORMAT_ID != nId) { SAL_WARN("vcl", "Incompatible swap file!"); return false; } rStream.ReadInt32(nType); rStream.ReadInt32(nLength); meType = static_cast(nType); if (meType == GraphicType::NONE || meType == GraphicType::Default) { return true; } else { bRet = swapInGraphic(rStream); } return bRet; } bool ImpGraphic::swapOutGraphic(SvStream& rStream) { if (rStream.GetError()) return false; ensureAvailable(); if (isSwappedOut()) { rStream.SetError(SVSTREAM_GENERALERROR); return false; } switch (meType) { case GraphicType::GdiMetafile: { if(!rStream.GetError()) { SvmWriter aWriter(rStream); aWriter.Write(maMetaFile); } } break; case GraphicType::Bitmap: { if (maVectorGraphicData) { rStream.WriteInt32(sal_Int32(GraphicContentType::Vector)); // stream out Vector Graphic defining data (length, byte array and evtl. path) // this is used e.g. in swapping out graphic data and in transporting it over UNO API // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be // no problem to extend it; only used at runtime switch (maVectorGraphicData->getType()) { case VectorGraphicDataType::Wmf: { rStream.WriteUInt32(constWmfMagic); break; } case VectorGraphicDataType::Emf: { rStream.WriteUInt32(constEmfMagic); break; } case VectorGraphicDataType::Svg: { rStream.WriteUInt32(constSvgMagic); break; } case VectorGraphicDataType::Pdf: { rStream.WriteUInt32(constPdfMagic); break; } } rStream.WriteUInt32(maVectorGraphicData->getBinaryDataContainer().getSize()); maVectorGraphicData->getBinaryDataContainer().writeToStream(rStream); } else if (mpAnimation) { rStream.WriteInt32(sal_Int32(GraphicContentType::Animation)); WriteAnimation(rStream, *mpAnimation); } else { rStream.WriteInt32(sal_Int32(GraphicContentType::Bitmap)); WriteDIBBitmapEx(maBitmapEx, rStream); } } break; case GraphicType::NONE: case GraphicType::Default: break; } if (mpGfxLink) mpGfxLink->getDataContainer().swapOut(); return true; } bool ImpGraphic::swapOutContent(SvStream& rStream) { ensureAvailable(); bool bRet = false; if (meType == GraphicType::NONE || meType == GraphicType::Default || isSwappedOut()) return false; sal_uLong nDataFieldPos; // Write the SWAP ID rStream.WriteUInt32(SWAP_FORMAT_ID); rStream.WriteInt32(static_cast(meType)); // data size is updated later nDataFieldPos = rStream.Tell(); rStream.WriteInt32(0); // write data block const sal_uInt64 nDataStart = rStream.Tell(); swapOutGraphic(rStream); if (!rStream.GetError()) { // Write the written length th the header const sal_uInt64 nCurrentPosition = rStream.Tell(); rStream.Seek(nDataFieldPos); rStream.WriteInt32(nCurrentPosition - nDataStart); rStream.Seek(nCurrentPosition); bRet = true; } return bRet; } bool ImpGraphic::swapOut() { if (isSwappedOut()) return false; bool bResult = false; // We have GfxLink so we have the source available if (mpGfxLink && mpGfxLink->IsNative()) { createSwapInfo(); clearGraphics(); // reset the swap file mpSwapFile.reset(); mpGfxLink->getDataContainer().swapOut(); // mark as swapped out mbSwapOut = true; bResult = true; } else { // Create a swap file auto pSwapFile = o3tl::make_shared(getOriginURL()); // Open a stream to write the swap file to { SvStream* pOutputStream = pSwapFile->getStream(); if (!pOutputStream) return false; // Write to stream pOutputStream->SetVersion(SOFFICE_FILEFORMAT_50); pOutputStream->SetCompressMode(SvStreamCompressFlags::NATIVE); pOutputStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE); if (!pOutputStream->GetError() && swapOutContent(*pOutputStream)) { pOutputStream->FlushBuffer(); bResult = !pOutputStream->GetError(); } } // Check if writing was successful if (bResult) { // We have swapped out, so can clean memory and prepare swap info createSwapInfo(); clearGraphics(); mpSwapFile = std::move(pSwapFile); mbSwapOut = true; } } if (bResult) { // Signal to manager that we have swapped out swappedOut(0); } return bResult; } bool ImpGraphic::ensureAvailable() const { bool bResult = true; if (isSwappedOut()) { auto pThis = const_cast(this); pThis->registerIntoManager(); bResult = pThis->swapIn(); } resetLastUsed(); return bResult; } void ImpGraphic::updateFromLoadedGraphic(const ImpGraphic* pGraphic) { if (mbPrepared) { GraphicExternalLink aLink = maGraphicExternalLink; Size aPrefSize = maSwapInfo.maPrefSize; MapMode aPrefMapMode = maSwapInfo.maPrefMapMode; *this = *pGraphic; if (aPrefSize.getWidth() && aPrefSize.getHeight() && aPrefMapMode == getPrefMapMode()) { // Use custom preferred size if it was set when the graphic was still unloaded. // Only set the size in case the unloaded and loaded unit matches. setPrefSize(aPrefSize); } maGraphicExternalLink = std::move(aLink); } else { // Move over only graphic content mpAnimation.reset(); if (pGraphic->mpAnimation) { mpAnimation = std::make_unique(*pGraphic->mpAnimation); maBitmapEx = mpAnimation->GetBitmapEx(); } else { maBitmapEx = pGraphic->maBitmapEx; } maMetaFile = pGraphic->maMetaFile; maVectorGraphicData = pGraphic->maVectorGraphicData; // Set to 0, to force recalculation mnSizeBytes = 0; mnChecksum = 0; restoreFromSwapInfo(); mbSwapOut = false; } } void ImpGraphic::dumpState(rtl::OStringBuffer &rState) { if (meType == GraphicType::NONE && mnSizeBytes == 0) return; // uninteresting. rState.append("\n\t"); if (mbSwapOut) rState.append("swapped\t"); else rState.append("loaded\t"); rState.append(static_cast(meType)); rState.append("\tsize:\t"); rState.append(static_cast(mnSizeBytes)); rState.append("\tgfxl:\t"); rState.append(static_cast(mpGfxLink ? mpGfxLink->getSizeBytes() : -1)); rState.append("\t"); rState.append(static_cast(maSwapInfo.maSizePixel.Width())); rState.append("x"); rState.append(static_cast(maSwapInfo.maSizePixel.Height())); rState.append("\t"); rState.append(static_cast(maExPrefSize.Width())); rState.append("x"); rState.append(static_cast(maExPrefSize.Height())); } void ImpGraphic::restoreFromSwapInfo() { setValuesForPrefMapMod(maSwapInfo.maPrefMapMode); setValuesForPrefSize(maSwapInfo.maPrefSize); if (maVectorGraphicData) { maVectorGraphicData->setPageIndex(maSwapInfo.mnPageIndex); } } namespace { std::optional lclConvertToVectorGraphicType(GfxLink const & rLink) { switch(rLink.GetType()) { case GfxLinkType::NativePdf: return VectorGraphicDataType::Pdf; case GfxLinkType::NativeWmf: if (rLink.IsEMF()) return VectorGraphicDataType::Emf; else return VectorGraphicDataType::Wmf; case GfxLinkType::NativeSvg: return VectorGraphicDataType::Svg; default: break; } return std::optional(); } } // end namespace bool ImpGraphic::swapIn() { if (!isSwappedOut()) return false; bool bReturn = false; if (mbPrepared) { Graphic aGraphic; if (!mpGfxLink->LoadNative(aGraphic)) return false; updateFromLoadedGraphic(aGraphic.ImplGetImpGraphic()); resetLastUsed(); bReturn = true; } else if (mpGfxLink && mpGfxLink->IsNative()) { std::optional oType = lclConvertToVectorGraphicType(*mpGfxLink); if (oType) { maVectorGraphicData = vcl::loadVectorGraphic(mpGfxLink->getDataContainer(), *oType); // Set to 0, to force recalculation mnSizeBytes = 0; mnChecksum = 0; restoreFromSwapInfo(); mbSwapOut = false; } else { Graphic aGraphic; if (!mpGfxLink->LoadNative(aGraphic)) return false; ImpGraphic* pImpGraphic = aGraphic.ImplGetImpGraphic(); if (meType != pImpGraphic->meType) return false; updateFromLoadedGraphic(pImpGraphic); } resetLastUsed(); bReturn = true; } else { SvStream* pStream = nullptr; if (mpSwapFile) pStream = mpSwapFile->getStream(); if (pStream) { pStream->SetVersion(SOFFICE_FILEFORMAT_50); pStream->SetCompressMode(SvStreamCompressFlags::NATIVE); pStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE); pStream->Seek(STREAM_SEEK_TO_BEGIN); bReturn = swapInFromStream(*pStream); restoreFromSwapInfo(); setOriginURL(mpSwapFile->getOriginURL()); mpSwapFile.reset(); } } if (bReturn) { swappedIn(getSizeBytes()); } return bReturn; } bool ImpGraphic::swapInFromStream(SvStream& rStream) { bool bRet = false; if (rStream.GetError()) return false; clearGraphics(); mnSizeBytes = 0; mnChecksum = 0; bRet = swapInContent(rStream); if (!bRet) { //throw away swapfile, etc. clear(); } mbSwapOut = false; return bRet; } bool ImpGraphic::swapInGraphic(SvStream& rStream) { bool bReturn = false; if (rStream.GetError()) return bReturn; if (meType == GraphicType::Bitmap) { sal_Int32 nContentType = -1; rStream.ReadInt32(nContentType); if (nContentType < 0) return false; auto eContentType = static_cast(nContentType); switch (eContentType) { case GraphicContentType::Bitmap: { BitmapEx aBitmapEx; ReadDIBBitmapEx(aBitmapEx, rStream); if (!rStream.GetError()) { maBitmapEx = aBitmapEx; bReturn = true; } } break; case GraphicContentType::Animation: { auto pAnimation = std::make_unique(); ReadAnimation(rStream, *pAnimation); if (!rStream.GetError()) { mpAnimation = std::move(pAnimation); maBitmapEx = mpAnimation->GetBitmapEx(); bReturn = true; } } break; case GraphicContentType::Vector: { // try to stream in Svg defining data (length, byte array and evtl. path) // See below (operator<<) for more information sal_uInt32 nMagic; rStream.ReadUInt32(nMagic); if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic || constPdfMagic == nMagic) { sal_uInt32 nVectorGraphicDataSize(0); rStream.ReadUInt32(nVectorGraphicDataSize); if (nVectorGraphicDataSize) { BinaryDataContainer aDataContainer(rStream, nVectorGraphicDataSize); if (rStream.GetError()) return false; VectorGraphicDataType aDataType; switch (nMagic) { case constSvgMagic: aDataType = VectorGraphicDataType::Svg; break; case constWmfMagic: aDataType = VectorGraphicDataType::Wmf; break; case constEmfMagic: aDataType = VectorGraphicDataType::Emf; break; case constPdfMagic: aDataType = VectorGraphicDataType::Pdf; break; default: return false; } auto aVectorGraphicDataPtr = std::make_shared(aDataContainer, aDataType); if (!rStream.GetError()) { maVectorGraphicData = std::move(aVectorGraphicDataPtr); bReturn = true; } } } } break; } } else if (meType == GraphicType::GdiMetafile) { GDIMetaFile aMetaFile; SvmReader aReader(rStream); aReader.Read(aMetaFile); if (!rStream.GetError()) { maMetaFile = aMetaFile; bReturn = true; } } return bReturn; } void ImpGraphic::setGfxLink(const std::shared_ptr& rGfxLink) { ensureAvailable(); mpGfxLink = rGfxLink; } const std::shared_ptr & ImpGraphic::getSharedGfxLink() const { return mpGfxLink; } GfxLink ImpGraphic::getGfxLink() const { ensureAvailable(); return( mpGfxLink ? *mpGfxLink : GfxLink() ); } bool ImpGraphic::isGfxLink() const { return ( bool(mpGfxLink) ); } BitmapChecksum ImpGraphic::getChecksum() const { if (mnChecksum != 0) return mnChecksum; ensureAvailable(); switch (meType) { case GraphicType::NONE: case GraphicType::Default: break; case GraphicType::Bitmap: { if (maVectorGraphicData) mnChecksum = maVectorGraphicData->GetChecksum(); else if (mpAnimation) mnChecksum = mpAnimation->GetChecksum(); else mnChecksum = maBitmapEx.GetChecksum(); } break; case GraphicType::GdiMetafile: { mnChecksum = SvmWriter::GetChecksum(maMetaFile); } break; } return mnChecksum; } sal_Int32 ImpGraphic::getPageNumber() const { if (isSwappedOut()) return maSwapInfo.mnPageIndex; if (maVectorGraphicData) return maVectorGraphicData->getPageIndex(); return -1; } bool ImpGraphic::canReduceMemory() const { if (mpContext) return false; return !isSwappedOut(); } bool ImpGraphic::reduceMemory() { return swapOut(); } std::chrono::high_resolution_clock::time_point ImpGraphic::getLastUsed() const { return maLastUsed; } void ImpGraphic::resetLastUsed() const { maLastUsed = std::chrono::high_resolution_clock::now(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */