From 89998a744f9ee8efa40c0e1cb7bdbc783d3414fa Mon Sep 17 00:00:00 2001 From: Attila Szűcs Date: Thu, 25 Apr 2024 01:43:05 +0200 Subject: tdf#153162 Animation load optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Loading a big GIF animation can freeze LO for a long time, so: changed getAnimationFromGraphic, it can load parts of a big animation. It can be called several times to load the whole animation. Now it can load animation while it is playing. It may still load smaller animations at once before it is rendered. At first it load frames that sum maximum 5 million pixels. (But minimum 10 frame.) Changed the Graphic parameter to shared_ptr, so it won’t be deleted until the whole animation is loaded. Change-Id: I5ac16d7ee4883dbaefb604cd07757d19e5aa2939 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166608 Tested-by: Jenkins Reviewed-by: Caolán McNamara Tested-by: Caolán McNamara --- slideshow/source/engine/shapes/drawshape.cxx | 62 +++++- slideshow/source/engine/shapes/drawshape.hxx | 37 +++- slideshow/source/engine/shapes/gdimtftools.cxx | 248 +++++++++++++---------- slideshow/source/engine/shapes/gdimtftools.hxx | 24 ++- slideshow/source/engine/shapes/shapeimporter.cxx | 7 +- 5 files changed, 251 insertions(+), 127 deletions(-) (limited to 'slideshow') diff --git a/slideshow/source/engine/shapes/drawshape.cxx b/slideshow/source/engine/shapes/drawshape.cxx index 185b76cc23d3..de89822dac58 100644 --- a/slideshow/source/engine/shapes/drawshape.cxx +++ b/slideshow/source/engine/shapes/drawshape.cxx @@ -343,6 +343,7 @@ namespace slideshow::internal mxPage( xContainingPage ), maAnimationFrames(), // empty, we don't have no intrinsic animation mnCurrFrame(0), + mpGraphicLoader(), mpCurrMtf(), mnCurrMtfLoadFlags( bForeignSource ? MTF_LOAD_FOREIGN_SOURCE : MTF_LOAD_NONE ), @@ -418,12 +419,13 @@ namespace slideshow::internal DrawShape::DrawShape( const uno::Reference< drawing::XShape >& xShape, uno::Reference< drawing::XDrawPage > xContainingPage, double nPrio, - const Graphic& rGraphic, + std::shared_ptr pGraphic, const SlideShowContext& rContext ) : mxShape( xShape ), mxPage(std::move( xContainingPage )), maAnimationFrames(), mnCurrFrame(0), + mpGraphicLoader(), mpCurrMtf(), mnCurrMtfLoadFlags( MTF_LOAD_NONE ), maCurrentShapeUnitBounds(), @@ -450,12 +452,25 @@ namespace slideshow::internal mbDrawingLayerAnim( false ), mbContainsPageField( false ) { - ENSURE_OR_THROW( rGraphic.IsAnimated(), + ENSURE_OR_THROW( pGraphic->IsAnimated(), "DrawShape::DrawShape(): Graphic is no animation" ); - getAnimationFromGraphic( maAnimationFrames, - mnAnimationLoopCount, - rGraphic ); + ::Animation aAnimation(pGraphic->GetAnimation()); + const Size aAnimSize(aAnimation.GetDisplaySizePixel()); + tools::Long nBitmapPixels = aAnimSize.getWidth() * aAnimSize.getHeight(); + + tools::Long nFramesToLoad = aAnimation.Count(); + + // if the Animation is bigger then 5 million pixels, we do not load the + // whole animation now. + if (nBitmapPixels * aAnimation.Count() > 5000000) + { + nFramesToLoad = 5000000 / nBitmapPixels; + if (nFramesToLoad < 10) + nFramesToLoad = 10; + } + mpGraphicLoader = ::std::make_unique(pGraphic); + getSomeAnimationFramesFromGraphic(nFramesToLoad); ENSURE_OR_THROW( !maAnimationFrames.empty() && maAnimationFrames.front().mpMtf, @@ -475,6 +490,7 @@ namespace slideshow::internal maAnimationFrames(), // don't copy animations for subsets, // only the current frame! mnCurrFrame(0), + mpGraphicLoader(), mpCurrMtf( rSrc.mpCurrMtf ), mnCurrMtfLoadFlags( rSrc.mnCurrMtfLoadFlags ), maCurrentShapeUnitBounds(), @@ -550,13 +566,13 @@ namespace slideshow::internal const uno::Reference< drawing::XShape >& xShape, const uno::Reference< drawing::XDrawPage >& xContainingPage, double nPrio, - const Graphic& rGraphic, + std::shared_ptr pGraphic, const SlideShowContext& rContext ) { DrawShapeSharedPtr pShape( new DrawShape(xShape, xContainingPage, nPrio, - rGraphic, + pGraphic, rContext) ); if( pShape->hasIntrinsicAnimation() ) @@ -847,6 +863,10 @@ namespace slideshow::internal ENSURE_OR_RETURN_VOID( nCurrFrame < maAnimationFrames.size(), "DrawShape::setIntrinsicAnimationFrame(): frame index out of bounds" ); + // Load 1 more frame if needed. (make sure the current frame is loded) + if (mpGraphicLoader) + getSomeAnimationFramesFromGraphic(1, nCurrFrame); + if( mnCurrFrame != nCurrFrame ) { mnCurrFrame = nCurrFrame; @@ -1235,6 +1255,34 @@ namespace slideshow::internal { return maSubsetting.getSubsetTreeNode( rParentNode, nNodeIndex, eNodeType ); } + + void DrawShape::getSomeAnimationFramesFromGraphic(::std::size_t nFrameCount, + ::std::size_t nLastToLoad /* = 0*/) + { + OSL_ASSERT(mpGraphicLoader); + + //load nFrameCount frames or to nLastToLoad + ::std::size_t nFramesToLoad = nFrameCount; + if (nLastToLoad > mpGraphicLoader->mnLoadedFrames + nFrameCount) + nFramesToLoad = nLastToLoad - mpGraphicLoader->mnLoadedFrames; + + getAnimationFromGraphic(maAnimationFrames, mnAnimationLoopCount, + mpGraphicLoader->mpGraphic, mpGraphicLoader->mpVDev, + mpGraphicLoader->mpVDevMask, mpGraphicLoader->mnLoadedFrames, + nFramesToLoad); + + // If the Animation is fully loaded, no need to load anymore. + if (mpGraphicLoader->mnLoadedFrames >= maAnimationFrames.size()) + { + mpGraphicLoader.reset(); + } + } + + DelayedGraphicLoader::DelayedGraphicLoader(std::shared_ptr pGraphic) + : mpGraphic(pGraphic) + , mpVDevMask(DeviceFormat::WITHOUT_ALPHA) + { + } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/slideshow/source/engine/shapes/drawshape.hxx b/slideshow/source/engine/shapes/drawshape.hxx index 6c71d151b6fa..efa55299f435 100644 --- a/slideshow/source/engine/shapes/drawshape.hxx +++ b/slideshow/source/engine/shapes/drawshape.hxx @@ -30,6 +30,8 @@ #include "viewshape.hxx" #include +#include + #include #include @@ -43,6 +45,22 @@ namespace slideshow::internal class DrawShape; typedef ::std::shared_ptr< DrawShape > DrawShapeSharedPtr; + /** This Struct store data needed to make Animation from Graphic. + If the Animation is too big, we won't load all of it at once. + From time to time we load small parts until it is completely loaded. + Because of it, we have to keep some data alive until the animation + is fully loaded. + */ + struct DelayedGraphicLoader + { + sal_uInt16 mnLoadedFrames = 0; + ::std::shared_ptr mpGraphic; + ScopedVclPtrInstance mpVDev; + ScopedVclPtrInstance mpVDevMask; + + DelayedGraphicLoader(std::shared_ptr pGraphic); + }; + /** This class is the representation of a draw document's XShape, and implements the Shape, AnimatableShape, and AttributableShape interfaces. @@ -107,7 +125,8 @@ namespace slideshow::internal const css::uno::Reference< css::drawing::XShape >& xShape, const css::uno::Reference< css::drawing::XDrawPage >& xContainingPage, double nPrio, - const Graphic& rGraphic, + std::shared_ptr pGraphic, + const SlideShowContext& rContext ); // throw ShapeLoadFailedException; virtual css::uno::Reference< css::drawing::XShape > getXShape() const override; @@ -199,6 +218,19 @@ namespace slideshow::internal */ GDIMetaFileSharedPtr const & forceScrollTextMetaFile(); + /** extract some Animation Frames from the Graphic + set in DelayedGraphicLoader. + + @param nFrameCount + Load this many frames. + + @param nLastToLoad + If nLastToLoad > nFrameCount + loadedFrames then + Load frames until this frame. + */ + void getSomeAnimationFramesFromGraphic(::std::size_t nFrameCount, + ::std::size_t nLastToLoad = 0); + private: /** Create a shape for the given XShape @@ -251,7 +283,7 @@ namespace slideshow::internal DrawShape( const css::uno::Reference< css::drawing::XShape >& xShape, css::uno::Reference< css::drawing::XDrawPage > xContainingPage, double nPrio, - const Graphic& rGraphic, + std::shared_ptr pGraphic, const SlideShowContext& rContext ); // throw ShapeLoadFailedException; /** Private copy constructor @@ -282,6 +314,7 @@ namespace slideshow::internal */ mutable VectorOfMtfAnimationFrames maAnimationFrames; ::std::size_t mnCurrFrame; + ::std::unique_ptr mpGraphicLoader; //to load more Frames later /// Metafile of currently active frame (static for shapes w/o intrinsic animation) mutable GDIMetaFileSharedPtr mpCurrMtf; diff --git a/slideshow/source/engine/shapes/gdimtftools.cxx b/slideshow/source/engine/shapes/gdimtftools.cxx index 0ca2673daa5f..d4f55928562f 100644 --- a/slideshow/source/engine/shapes/gdimtftools.cxx +++ b/slideshow/source/engine/shapes/gdimtftools.cxx @@ -248,101 +248,119 @@ sal_Int32 getNextActionOffset( MetaAction * pCurrAct ) bool getAnimationFromGraphic( VectorOfMtfAnimationFrames& o_rFrames, sal_uInt32& o_rLoopCount, - const Graphic& rGraphic ) + std::shared_ptr pGraphic, + ScopedVclPtrInstance& pVDev, + ScopedVclPtrInstance& pVDevMask, + sal_uInt16& mnLoadedFrames, + sal_uInt16 nFramesToLoad ) { - o_rFrames.clear(); + bool bFirstRun = mnLoadedFrames == 0; + if (bFirstRun) + o_rFrames.clear(); - if( !rGraphic.IsAnimated() ) + if( !pGraphic->IsAnimated() ) return false; // some loop invariants - ::Animation aAnimation( rGraphic.GetAnimation() ); + ::Animation aAnimation( pGraphic->GetAnimation() ); const Point aEmptyPoint; const Size aAnimSize( aAnimation.GetDisplaySizePixel() ); - // setup VDev, into which all bitmaps are painted (want to - // normalize animations to n bitmaps of same size. An Animation, - // though, can contain bitmaps of varying sizes and different - // update modes) - ScopedVclPtrInstance< VirtualDevice > pVDev; - pVDev->SetOutputSizePixel( aAnimSize ); - pVDev->EnableMapMode( false ); - - // setup mask VDev (alpha VDev is currently rather slow) - ScopedVclPtrInstance pVDevMask(DeviceFormat::WITHOUT_ALPHA); - pVDevMask->SetOutputSizePixel( aAnimSize ); - pVDevMask->EnableMapMode( false ); - - // tdf#156630 make erase calls fill with transparency - pVDev->SetBackground( Wallpaper( COL_BLACK ) ); - pVDevMask->SetBackground( Wallpaper( COL_ALPHA_TRANSPARENT ) ); - - o_rLoopCount = aAnimation.GetLoopCount(); - - for( sal_uInt16 i=0, nCount=aAnimation.Count(); iSetOutputSizePixel(aAnimSize); + pVDev->EnableMapMode(false); + + // setup mask VDev (alpha VDev is currently rather slow) + pVDevMask->SetOutputSizePixel(aAnimSize); + pVDevMask->EnableMapMode(false); + + // tdf#156630 make erase calls fill with transparency + pVDev->SetBackground(Wallpaper(COL_BLACK)); + pVDevMask->SetBackground(Wallpaper(COL_ALPHA_TRANSPARENT)); + + o_rLoopCount = aAnimation.GetLoopCount(); + } + sal_uInt16 nCount = aAnimation.Count(); + if (!bFirstRun && mnLoadedFrames + nFramesToLoad < nCount) + nCount = mnLoadedFrames + nFramesToLoad; + for (sal_uInt16 i = mnLoadedFrames; i < nCount; ++i) { const AnimationFrame& rAnimationFrame( aAnimation.Get(i) ); - switch(rAnimationFrame.meDisposal) + bool bCalculateNow = !bFirstRun || i < nFramesToLoad; + if (bCalculateNow) { - case Disposal::Not: + switch (rAnimationFrame.meDisposal) { - pVDev->DrawBitmapEx(rAnimationFrame.maPositionPixel, - rAnimationFrame.maBitmapEx); - AlphaMask aMask = rAnimationFrame.maBitmapEx.GetAlphaMask(); - - if( aMask.IsEmpty() ) + case Disposal::Not: { - const tools::Rectangle aRect(aEmptyPoint, - pVDevMask->GetOutputSizePixel()); - const Wallpaper aWallpaper(COL_BLACK); - pVDevMask->DrawWallpaper(aRect, - aWallpaper); - } - else - { - BitmapEx aTmpMask(aMask.GetBitmap(), aMask); - pVDevMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, - aTmpMask ); + pVDev->DrawBitmapEx(rAnimationFrame.maPositionPixel, + rAnimationFrame.maBitmapEx); + AlphaMask aMask = rAnimationFrame.maBitmapEx.GetAlphaMask(); + + if (aMask.IsEmpty()) + { + const tools::Rectangle aRect(aEmptyPoint, pVDevMask->GetOutputSizePixel()); + const Wallpaper aWallpaper(COL_BLACK); + pVDevMask->DrawWallpaper(aRect, aWallpaper); + } + else + { + BitmapEx aTmpMask(aMask.GetBitmap(), aMask); + pVDevMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aTmpMask); + } + break; } - break; - } - - case Disposal::Back: - { - // #i70772# react on no mask - const AlphaMask aMask(rAnimationFrame.maBitmapEx.GetAlphaMask()); - const Bitmap & rContent(rAnimationFrame.maBitmapEx.GetBitmap()); - pVDevMask->Erase(); - pVDev->DrawBitmap(rAnimationFrame.maPositionPixel, rContent); - - if(aMask.IsEmpty()) + case Disposal::Back: { - const tools::Rectangle aRect(rAnimationFrame.maPositionPixel, rContent.GetSizePixel()); - pVDevMask->SetFillColor( COL_BLACK); - pVDevMask->SetLineColor(); - pVDevMask->DrawRect(aRect); + // #i70772# react on no mask + const AlphaMask aMask(rAnimationFrame.maBitmapEx.GetAlphaMask()); + const Bitmap& rContent(rAnimationFrame.maBitmapEx.GetBitmap()); + + pVDevMask->Erase(); + pVDev->DrawBitmap(rAnimationFrame.maPositionPixel, rContent); + + if (aMask.IsEmpty()) + { + const tools::Rectangle aRect(rAnimationFrame.maPositionPixel, + rContent.GetSizePixel()); + pVDevMask->SetFillColor(COL_BLACK); + pVDevMask->SetLineColor(); + pVDevMask->DrawRect(aRect); + } + else + { + pVDevMask->DrawBitmap(rAnimationFrame.maPositionPixel, aMask.GetBitmap()); + } + break; } - else + + case Disposal::Previous: { - pVDevMask->DrawBitmap(rAnimationFrame.maPositionPixel, aMask.GetBitmap()); + pVDev->DrawBitmapEx(rAnimationFrame.maPositionPixel, + rAnimationFrame.maBitmapEx); + pVDevMask->DrawBitmap(rAnimationFrame.maPositionPixel, + rAnimationFrame.maBitmapEx.GetAlphaMask().GetBitmap()); + break; } - break; - } - - case Disposal::Previous : - { - pVDev->DrawBitmapEx(rAnimationFrame.maPositionPixel, - rAnimationFrame.maBitmapEx); - pVDevMask->DrawBitmap(rAnimationFrame.maPositionPixel, - rAnimationFrame.maBitmapEx.GetAlphaMask().GetBitmap()); - break; } } - // extract current aVDev content into a new animation // frame - GDIMetaFileSharedPtr pMtf = std::make_shared(); + GDIMetaFileSharedPtr pMtf; + if (bFirstRun) + { + pMtf = std::make_shared(); + } + else + { + pMtf = o_rFrames[i].mpMtf; + } bool useAlphaMask = false; #if defined(MACOSX) || defined(IOS) useAlphaMask = true; @@ -351,52 +369,58 @@ bool getAnimationFromGraphic( VectorOfMtfAnimationFrames& o_rFrames, if( SkiaHelper::isVCLSkiaEnabled()) useAlphaMask = true; #endif - if( useAlphaMask ) + if (bCalculateNow) { - AlphaMask aAlphaMask(pVDevMask->GetBitmap(aEmptyPoint, aAnimSize)); - pMtf->AddAction( - new MetaBmpExAction( aEmptyPoint, - BitmapEx( - pVDev->GetBitmap( - aEmptyPoint, - aAnimSize ), - aAlphaMask))); + if( useAlphaMask ) + { + AlphaMask aAlphaMask(pVDevMask->GetBitmap(aEmptyPoint, aAnimSize)); + pMtf->AddAction( + new MetaBmpExAction( aEmptyPoint, + BitmapEx( + pVDev->GetBitmap( + aEmptyPoint, + aAnimSize ), + aAlphaMask))); + } + else + { + Bitmap aAlphaMask = pVDevMask->GetBitmap(aEmptyPoint, aAnimSize); + aAlphaMask.Invert(); // convert from transparency to alpha + pMtf->AddAction( + new MetaBmpExAction( aEmptyPoint, + BitmapEx( + pVDev->GetBitmap( + aEmptyPoint, + aAnimSize ), + aAlphaMask))); + } + mnLoadedFrames = i+1; } - else + if (bFirstRun) { - Bitmap aAlphaMask = pVDevMask->GetBitmap(aEmptyPoint, aAnimSize); - aAlphaMask.Invert(); // convert from transparency to alpha - pMtf->AddAction( - new MetaBmpExAction( aEmptyPoint, - BitmapEx( - pVDev->GetBitmap( - aEmptyPoint, - aAnimSize ), - aAlphaMask))); - } + // setup mtf dimensions and pref map mode (for + // simplicity, keep it all in pixel. the metafile + // renderer scales it down to (1, 1) box anyway) + pMtf->SetPrefMapMode(MapMode()); + pMtf->SetPrefSize(aAnimSize); + + // Take care of special value for MultiPage TIFFs. ATM these shall just + // show their first page for _quite_ some time. + sal_Int32 nWaitTime100thSeconds(rAnimationFrame.mnWait); + if (ANIMATION_TIMEOUT_ON_CLICK == nWaitTime100thSeconds) + { + // ATM the huge value would block the timer, so use a long + // time to show first page (whole day) + nWaitTime100thSeconds = 100 * 60 * 60 * 24; + } - // setup mtf dimensions and pref map mode (for - // simplicity, keep it all in pixel. the metafile - // renderer scales it down to (1, 1) box anyway) - pMtf->SetPrefMapMode( MapMode() ); - pMtf->SetPrefSize( aAnimSize ); + // There are animated GIFs with no WaitTime set. Take 0.1 sec, the + // same duration that is used by the edit view. + if (nWaitTime100thSeconds == 0) + nWaitTime100thSeconds = 10; - // Take care of special value for MultiPage TIFFs. ATM these shall just - // show their first page for _quite_ some time. - sal_Int32 nWaitTime100thSeconds(rAnimationFrame.mnWait); - if( ANIMATION_TIMEOUT_ON_CLICK == nWaitTime100thSeconds ) - { - // ATM the huge value would block the timer, so use a long - // time to show first page (whole day) - nWaitTime100thSeconds = 100 * 60 * 60 * 24; + o_rFrames.emplace_back(pMtf, nWaitTime100thSeconds / 100.0); } - - // There are animated GIFs with no WaitTime set. Take 0.1 sec, the - // same duration that is used by the edit view. - if( nWaitTime100thSeconds == 0 ) - nWaitTime100thSeconds = 10; - - o_rFrames.emplace_back( pMtf, nWaitTime100thSeconds / 100.0 ); } return !o_rFrames.empty(); diff --git a/slideshow/source/engine/shapes/gdimtftools.hxx b/slideshow/source/engine/shapes/gdimtftools.hxx index 8395efeb00a2..c23b16ae53c5 100644 --- a/slideshow/source/engine/shapes/gdimtftools.hxx +++ b/slideshow/source/engine/shapes/gdimtftools.hxx @@ -27,6 +27,8 @@ #include +#include + #include #include @@ -101,6 +103,8 @@ namespace slideshow::internal sal_Int32 getNextActionOffset( MetaAction * pCurrAct ); /** Extract a vector of animation frames from given Graphic. + It can be used to extract only a few frames, and can be + called later to extract more. (If the Animation is big) @param o_rFrames Resulting vector of animated metafiles @@ -108,12 +112,28 @@ namespace slideshow::internal @param o_rLoopCount Number of times the bitmap animation shall be repeated - @param rGraphic + @param pGraphic Input graphic object, to extract animations from + + @param pVDev, pVDevMask + Virtual devices. We don't want to create new everytime we load some frames. + + @param nLoadedFrames + The count of loaded Frames. + + @param nFramesToLoad + The count of Frames need to be extracted now. + Bigger nFramesToLoad not result an error, the function will + stop extracting at the end of the animation anyway. */ + bool getAnimationFromGraphic(VectorOfMtfAnimationFrames& o_rFrames, sal_uInt32& o_rLoopCount, - const Graphic& rGraphic); + std::shared_ptr pGraphic, + ScopedVclPtrInstance &pVDev, + ScopedVclPtrInstance &pVDevMask, + sal_uInt16& nLoadedFrames, + sal_uInt16 nFramesToLoad); /** Retrieve scroll text animation rectangles from given metafile diff --git a/slideshow/source/engine/shapes/shapeimporter.cxx b/slideshow/source/engine/shapes/shapeimporter.cxx index 7823a5588efd..33cb42469d31 100644 --- a/slideshow/source/engine/shapes/shapeimporter.cxx +++ b/slideshow/source/engine/shapes/shapeimporter.cxx @@ -311,9 +311,8 @@ ShapeSharedPtr ShapeImporter::createShape( // fetch readily transformed and color-modified // graphic - - Graphic aGraphic( - xGraphicObject->GetTransformedGraphic( + std::shared_ptr pGraphic + = ::std::make_shared(xGraphicObject->GetTransformedGraphic( xGraphicObject->GetPrefSize(), xGraphicObject->GetPrefMapMode(), aGraphAttrs ) ); @@ -321,7 +320,7 @@ ShapeSharedPtr ShapeImporter::createShape( return DrawShape::create( xCurrShape, mxPage, mnAscendingPrio, - aGraphic, + pGraphic, mrContext ); } else -- cgit