diff options
author | Attila Szűcs <attila.szucs@collabora.com> | 2024-04-25 01:43:05 +0200 |
---|---|---|
committer | Caolán McNamara <caolan.mcnamara@collabora.com> | 2024-04-25 14:05:13 +0200 |
commit | 89998a744f9ee8efa40c0e1cb7bdbc783d3414fa (patch) | |
tree | 25c3b2de837e1d3eb2f7d59fbe796d5f64aca376 /slideshow | |
parent | a600cb4865f2b4bce872a000c2614cf6478b1ee0 (diff) |
tdf#153162 Animation load optimization
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 <caolan.mcnamara@collabora.com>
Tested-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Diffstat (limited to 'slideshow')
-rw-r--r-- | slideshow/source/engine/shapes/drawshape.cxx | 62 | ||||
-rw-r--r-- | slideshow/source/engine/shapes/drawshape.hxx | 37 | ||||
-rw-r--r-- | slideshow/source/engine/shapes/gdimtftools.cxx | 248 | ||||
-rw-r--r-- | slideshow/source/engine/shapes/gdimtftools.hxx | 24 | ||||
-rw-r--r-- | slideshow/source/engine/shapes/shapeimporter.cxx | 7 |
5 files changed, 251 insertions, 127 deletions
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<Graphic> 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<DelayedGraphicLoader>(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<Graphic> 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<Graphic> 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 <hyperlinkarea.hxx> +#include <vcl/virdev.hxx> + #include <optional> #include <vector> @@ -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<Graphic> mpGraphic; + ScopedVclPtrInstance<VirtualDevice> mpVDev; + ScopedVclPtrInstance<VirtualDevice> mpVDevMask; + + DelayedGraphicLoader(std::shared_ptr<Graphic> 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<Graphic> 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<Graphic> 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<DelayedGraphicLoader> 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<Graphic> pGraphic, + ScopedVclPtrInstance<VirtualDevice>& pVDev, + ScopedVclPtrInstance<VirtualDevice>& 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<VirtualDevice> 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(); i<nCount; ++i ) + if (bFirstRun) + { + // 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) + pVDev->SetOutputSizePixel(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<GDIMetaFile>(); + GDIMetaFileSharedPtr pMtf; + if (bFirstRun) + { + pMtf = std::make_shared<GDIMetaFile>(); + } + 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 <basegfx/range/b2drectangle.hxx> +#include <vcl/virdev.hxx> + #include <tools.hxx> #include <utility> @@ -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<Graphic> pGraphic, + ScopedVclPtrInstance<VirtualDevice> &pVDev, + ScopedVclPtrInstance<VirtualDevice> &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<Graphic> pGraphic + = ::std::make_shared<Graphic>(xGraphicObject->GetTransformedGraphic( xGraphicObject->GetPrefSize(), xGraphicObject->GetPrefMapMode(), aGraphAttrs ) ); @@ -321,7 +320,7 @@ ShapeSharedPtr ShapeImporter::createShape( return DrawShape::create( xCurrShape, mxPage, mnAscendingPrio, - aGraphic, + pGraphic, mrContext ); } else |