diff options
Diffstat (limited to 'drawinglayer')
-rw-r--r-- | drawinglayer/source/primitive2d/animatedprimitive2d.cxx | 9 | ||||
-rw-r--r-- | drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx | 516 |
2 files changed, 386 insertions, 139 deletions
diff --git a/drawinglayer/source/primitive2d/animatedprimitive2d.cxx b/drawinglayer/source/primitive2d/animatedprimitive2d.cxx index 5533b265426b..2fa2916f1498 100644 --- a/drawinglayer/source/primitive2d/animatedprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/animatedprimitive2d.cxx @@ -31,6 +31,15 @@ namespace drawinglayer { namespace primitive2d { + void AnimatedSwitchPrimitive2D::setAnimationEntry(const animation::AnimationEntry& rNew) + { + // delete cloned animation description + delete mpAnimationEntry; + + // clone given animation description + mpAnimationEntry = rNew.clone(); + } + AnimatedSwitchPrimitive2D::AnimatedSwitchPrimitive2D( const animation::AnimationEntry& rAnimationEntry, const Primitive2DContainer& rChildren, diff --git a/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx b/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx index b51fc91f5b43..66a67d4c5bbf 100644 --- a/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx +++ b/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx @@ -25,6 +25,7 @@ #include <drawinglayer/primitive2d/transformprimitive2d.hxx> #include <drawinglayer/primitive2d/maskprimitive2d.hxx> #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> #include <basegfx/numeric/ftools.hxx> @@ -37,152 +38,413 @@ #include <vcl/svapp.hxx> #include <vcl/metaact.hxx> -namespace +namespace drawinglayer { - struct animationStep + namespace primitive2d { - BitmapEx maBitmapEx; - sal_uInt32 mnTime; - }; + class AnimatedGraphicPrimitive2D : public AnimatedSwitchPrimitive2D + { + private: + /// the geometric definition + basegfx::B2DHomMatrix maTransform; - class animatedBitmapExPreparator - { - ::Animation maAnimation; - ::std::vector< animationStep > maSteps; + /** the Graphic with all its content possibilities, here only + animated is allowed and gets checked by isValidData(). + an instance of Graphic is used here since it's ref-counted + and thus a safe cpoy for now + */ + const Graphic maGraphic; - sal_uInt32 generateStepTime(sal_uInt32 nIndex) const; + /// local animation processing data, excerpt from maGraphic + ::Animation maAnimation; - public: - explicit animatedBitmapExPreparator(const Graphic& rGraphic); + /// the on-demand created VirtualDevices for frame creation + ScopedVclPtrInstance< VirtualDevice > maVirtualDevice; + ScopedVclPtrInstance< VirtualDevice > maVirtualDeviceMask; - sal_uInt32 count() const { return maSteps.size(); } - sal_uInt32 loopCount() const { return (sal_uInt32)maAnimation.GetLoopCount(); } - sal_uInt32 stepTime(sal_uInt32 a) const { return maSteps[a].mnTime; } - const BitmapEx& stepBitmapEx(sal_uInt32 a) const { return maSteps[a].maBitmapEx; } - }; + // index of the next frame that would be regularly prepared + sal_uInt32 mnNextFrameToPrepare; - sal_uInt32 animatedBitmapExPreparator::generateStepTime(sal_uInt32 nIndex) const - { - const AnimationBitmap& rAnimBitmap = maAnimation.Get(sal_uInt16(nIndex)); - sal_uInt32 nWaitTime(rAnimBitmap.nWait * 10); + /// buffering of 1st frame (always active) + Primitive2DReference maBufferedFirstFrame; - // Take care of special value for MultiPage TIFFs. ATM these shall just - // show their first page. Later we will offer some switching when object - // is selected. - if(ANIMATION_TIMEOUT_ON_CLICK == rAnimBitmap.nWait) - { - // ATM the huge value would block the timer, so - // use a long time to show first page (whole day) - nWaitTime = 100 * 60 * 60 * 24; - } + /// buffering of all frames + Primitive2DContainer maBufferedPrimitives; + bool mbBufferingAllowed; - // Bad trap: There are animated gifs with no set WaitTime (!). - // In that case use a default value. - if(0L == nWaitTime) - { - nWaitTime = 100L; - } + /// set if the animation is huge so that just always the next frame + /// is used instead of using timing + bool mbHugeSize; - return nWaitTime; - } + /// helper methods + bool isValidData() const + { + return (GraphicType::Bitmap == maGraphic.GetType() + && maGraphic.IsAnimated() + && maAnimation.Count()); + } - animatedBitmapExPreparator::animatedBitmapExPreparator(const Graphic& rGraphic) - : maAnimation(rGraphic.GetAnimation()) - { - OSL_ENSURE(GraphicType::Bitmap == rGraphic.GetType() && rGraphic.IsAnimated(), "animatedBitmapExPreparator: graphic is not animated (!)"); + void ensureVirtualDeviceSizeAndState() + { + if (isValidData()) + { + const Size aCurrent(maVirtualDevice->GetOutputSizePixel()); + const Size aTarget(maAnimation.GetDisplaySizePixel()); - // #128539# secure access to Animation, looks like there exist animated GIFs out there - // with a step count of zero - if(maAnimation.Count()) - { - ScopedVclPtrInstance< VirtualDevice > aVirtualDevice(*Application::GetDefaultDevice()); - ScopedVclPtrInstance< VirtualDevice > aVirtualDeviceMask(*Application::GetDefaultDevice(), - DeviceFormat::BITMASK); - - // Prepare VirtualDevices and their states - aVirtualDevice->EnableMapMode(false); - aVirtualDeviceMask->EnableMapMode(false); - aVirtualDevice->SetOutputSizePixel(maAnimation.GetDisplaySizePixel()); - aVirtualDeviceMask->SetOutputSizePixel(maAnimation.GetDisplaySizePixel()); - aVirtualDevice->Erase(); - aVirtualDeviceMask->Erase(); - - for(size_t a(0); a < maAnimation.Count(); a++) + if (aCurrent != aTarget) + { + maVirtualDevice->EnableMapMode(false); + maVirtualDeviceMask->EnableMapMode(false); + maVirtualDevice->SetOutputSizePixel(aTarget); + maVirtualDeviceMask->SetOutputSizePixel(aTarget); + } + + maVirtualDevice->Erase(); + maVirtualDeviceMask->Erase(); + } + } + + sal_uInt32 generateStepTime(sal_uInt32 nIndex) const { - animationStep aNextStep; - aNextStep.mnTime = generateStepTime(a); + const AnimationBitmap& rAnimBitmap = maAnimation.Get(sal_uInt16(nIndex)); + sal_uInt32 nWaitTime(rAnimBitmap.nWait * 10); + + // Take care of special value for MultiPage TIFFs. ATM these shall just + // show their first page. Later we will offer some switching when object + // is selected. + if (ANIMATION_TIMEOUT_ON_CLICK == rAnimBitmap.nWait) + { + // ATM the huge value would block the timer, so + // use a long time to show first page (whole day) + nWaitTime = 100 * 60 * 60 * 24; + } - // prepare step - const AnimationBitmap& rAnimBitmap = maAnimation.Get(sal_uInt16(a)); + // Bad trap: There are animated gifs with no set WaitTime (!). + // In that case use a default value. + if (0L == nWaitTime) + { + nWaitTime = 100L; + } + + return nWaitTime; + } - switch(rAnimBitmap.eDisposal) + void createAndSetAnimationTiming() + { + if (isValidData()) { - case Disposal::Not: + animation::AnimationEntryLoop aAnimationLoop(maAnimation.GetLoopCount() ? maAnimation.GetLoopCount() : 0xffff); + const sal_uInt32 nCount(maAnimation.Count()); + + for (sal_uInt32 a(0); a < nCount; a++) { - aVirtualDevice->DrawBitmapEx(rAnimBitmap.aPosPix, rAnimBitmap.aBmpEx); - Bitmap aMask = rAnimBitmap.aBmpEx.GetMask(); + const sal_uInt32 aStepTime(generateStepTime(a)); + const animation::AnimationEntryFixed aTime((double)aStepTime, (double)a / (double)nCount); - if(aMask.IsEmpty()) - { - const Point aEmpty; - const Rectangle aRect(aEmpty, aVirtualDeviceMask->GetOutputSizePixel()); - const Wallpaper aWallpaper(COL_BLACK); - aVirtualDeviceMask->DrawWallpaper(aRect, aWallpaper); - } - else + aAnimationLoop.append(aTime); + } + + animation::AnimationEntryList aAnimationEntryList; + aAnimationEntryList.append(aAnimationLoop); + + setAnimationEntry(aAnimationEntryList); + } + } + + Primitive2DReference createFromBuffer() const + { + // create BitmapEx by extracting from VirtualDevices + const Bitmap aMainBitmap(maVirtualDevice->GetBitmap(Point(), maVirtualDevice->GetOutputSizePixel())); +#if defined(MACOSX) || defined(IOS) + const AlphaMask aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel())); +#else + const Bitmap aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel())); +#endif + + return Primitive2DReference( + new BitmapPrimitive2D( + BitmapEx(aMainBitmap, aMaskBitmap), + getTransform())); + } + + void checkSafeToBuffer(sal_uInt32 nIndex) + { + if (mbBufferingAllowed) + { + // all frames buffered + if (mbBufferingAllowed && maBufferedPrimitives.size() && nIndex < maBufferedPrimitives.size()) + { + if (!maBufferedPrimitives[nIndex].is()) { - BitmapEx aExpandVisibilityMask = BitmapEx(aMask, aMask); - aVirtualDeviceMask->DrawBitmapEx(rAnimBitmap.aPosPix, aExpandVisibilityMask); - } + maBufferedPrimitives[nIndex] = createFromBuffer(); - break; + // check if buffering is complete + bool bBufferingComplete(true); + + for (sal_uInt32 a(0); bBufferingComplete && a < maBufferedPrimitives.size(); a++) + { + bBufferingComplete = maBufferedPrimitives[a].is(); + } + + if (bBufferingComplete) + { + maVirtualDevice.disposeAndClear(); + maVirtualDeviceMask.disposeAndClear(); + } + } + } + } + else + { + // always buffer first frame + if (0 == nIndex && !maBufferedFirstFrame.is()) + { + maBufferedFirstFrame = createFromBuffer(); } - case Disposal::Back: + } + } + + void createFrame(sal_uInt32 nTarget) + { + // mnNextFrameToPrepare is the target frame to create next (which implies that + // mnNextFrameToPrepare-1 *is* currently in the VirtualDevice when + // 0 != mnNextFrameToPrepare. nTarget is the traget frame. + if (isValidData()) + { + if (mnNextFrameToPrepare > nTarget) { - // #i70772# react on no mask, for primitives, too. - const Bitmap aMask(rAnimBitmap.aBmpEx.GetMask()); - const Bitmap aContent(rAnimBitmap.aBmpEx.GetBitmap()); + // we are ahead request, reset mechanism to start at frame zero + ensureVirtualDeviceSizeAndState(); + mnNextFrameToPrepare = 0; + } - aVirtualDeviceMask->Erase(); - aVirtualDevice->DrawBitmap(rAnimBitmap.aPosPix, aContent); + while (mnNextFrameToPrepare <= nTarget) + { + // prepare step + const AnimationBitmap& rAnimBitmap = maAnimation.Get(sal_uInt16(mnNextFrameToPrepare)); - if(aMask.IsEmpty()) + switch (rAnimBitmap.eDisposal) { - const Rectangle aRect(rAnimBitmap.aPosPix, aContent.GetSizePixel()); - aVirtualDeviceMask->SetFillColor(COL_BLACK); - aVirtualDeviceMask->SetLineColor(); - aVirtualDeviceMask->DrawRect(aRect); + case Disposal::Not: + { + maVirtualDevice->DrawBitmapEx(rAnimBitmap.aPosPix, rAnimBitmap.aBmpEx); + Bitmap aMask = rAnimBitmap.aBmpEx.GetMask(); + + if (aMask.IsEmpty()) + { + const Point aEmpty; + const Rectangle aRect(aEmpty, maVirtualDeviceMask->GetOutputSizePixel()); + const Wallpaper aWallpaper(COL_BLACK); + maVirtualDeviceMask->DrawWallpaper(aRect, aWallpaper); + } + else + { + BitmapEx aExpandVisibilityMask = BitmapEx(aMask, aMask); + maVirtualDeviceMask->DrawBitmapEx(rAnimBitmap.aPosPix, aExpandVisibilityMask); + } + + break; + } + case Disposal::Back: + { + // #i70772# react on no mask, for primitives, too. + const Bitmap aMask(rAnimBitmap.aBmpEx.GetMask()); + const Bitmap aContent(rAnimBitmap.aBmpEx.GetBitmap()); + + maVirtualDeviceMask->Erase(); + maVirtualDevice->DrawBitmap(rAnimBitmap.aPosPix, aContent); + + if (aMask.IsEmpty()) + { + const Rectangle aRect(rAnimBitmap.aPosPix, aContent.GetSizePixel()); + maVirtualDeviceMask->SetFillColor(COL_BLACK); + maVirtualDeviceMask->SetLineColor(); + maVirtualDeviceMask->DrawRect(aRect); + } + else + { + maVirtualDeviceMask->DrawBitmap(rAnimBitmap.aPosPix, aMask); + } + + break; + } + case Disposal::Previous: + { + maVirtualDevice->DrawBitmapEx(rAnimBitmap.aPosPix, rAnimBitmap.aBmpEx); + maVirtualDeviceMask->DrawBitmap(rAnimBitmap.aPosPix, rAnimBitmap.aBmpEx.GetMask()); + break; + } } - else + + // to not waste created data, check adding to buffers + checkSafeToBuffer(mnNextFrameToPrepare); + + mnNextFrameToPrepare++; + } + } + } + + Primitive2DReference tryTogetFromBuffer(sal_uInt32 nIndex) const + { + if (mbBufferingAllowed) + { + // all frames buffered, check if available + if (maBufferedPrimitives.size() && nIndex < maBufferedPrimitives.size()) + { + if (maBufferedPrimitives[nIndex].is()) { - aVirtualDeviceMask->DrawBitmap(rAnimBitmap.aPosPix, aMask); + return maBufferedPrimitives[nIndex]; } - - break; } - case Disposal::Previous : + } + else + { + // always buffer first frame, it's sometimes requested out-of-order + if (0 == nIndex && maBufferedFirstFrame.is()) { - aVirtualDevice->DrawBitmapEx(rAnimBitmap.aPosPix, rAnimBitmap.aBmpEx); - aVirtualDeviceMask->DrawBitmap(rAnimBitmap.aPosPix, rAnimBitmap.aBmpEx.GetMask()); - break; + return maBufferedFirstFrame; } } - // create BitmapEx - Bitmap aMainBitmap = aVirtualDevice->GetBitmap(Point(), aVirtualDevice->GetOutputSizePixel()); -#if defined(MACOSX) || defined(IOS) - AlphaMask aMaskBitmap( aVirtualDeviceMask->GetBitmap( Point(), aVirtualDeviceMask->GetOutputSizePixel())); -#else - Bitmap aMaskBitmap = aVirtualDeviceMask->GetBitmap( Point(), aVirtualDeviceMask->GetOutputSizePixel()); -#endif - aNextStep.maBitmapEx = BitmapEx(aMainBitmap, aMaskBitmap); + return Primitive2DReference(); + } + + public: + /// constructor + AnimatedGraphicPrimitive2D( + const Graphic& rGraphic, + const basegfx::B2DHomMatrix& rTransform); + + /// data read access + const Graphic& getGraphic() const { return maGraphic; } + const basegfx::B2DHomMatrix& getTransform() const { return maTransform; } + + /// compare operator + virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; - // add to vector - maSteps.push_back(aNextStep); + /// override to deliver the correct expected frame dependent of timing + virtual Primitive2DContainer get2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const override; + }; + + AnimatedGraphicPrimitive2D::AnimatedGraphicPrimitive2D( + const Graphic& rGraphic, + const basegfx::B2DHomMatrix& rTransform) + : AnimatedSwitchPrimitive2D( + animation::AnimationEntryList(), + Primitive2DContainer(), + false), + maTransform(rTransform), + maGraphic(rGraphic), + maAnimation(rGraphic.GetAnimation()), + maVirtualDevice(*Application::GetDefaultDevice()), + maVirtualDeviceMask(*Application::GetDefaultDevice(), DeviceFormat::BITMASK), + mnNextFrameToPrepare(SAL_MAX_UINT32), + maBufferedFirstFrame(), + maBufferedPrimitives(), + mbBufferingAllowed(false), + mbHugeSize(false) + { + // initialize AnimationTiming, needed to detect which frame is requested + // in get2DDecomposition + createAndSetAnimationTiming(); + + // check if we allow buffering + if (isValidData()) + { + // allow buffering up to a size of: + // - 64 frames + // - sizes of 256x256 pixels + // This may be offered in option values if needed + static const sal_uInt64 nAllowedSize(64 * 256 * 256); + static const sal_uInt64 nHugeSize(10000000); + const Size aTarget(maAnimation.GetDisplaySizePixel()); + const sal_uInt64 nUsedSize((sal_uInt64)maAnimation.Count() * aTarget.Width() * aTarget.Height()); + + if (nUsedSize < nAllowedSize) + { + mbBufferingAllowed = true; + } + + if (nUsedSize > nHugeSize) + { + mbHugeSize = true; + } + } + + // prepare buffer space + if (mbBufferingAllowed && isValidData()) + { + maBufferedPrimitives = Primitive2DContainer(maAnimation.Count()); } } - } -} // end of anonymous namespace + + bool AnimatedGraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + // do not use 'GroupPrimitive2D::operator==' here, that would compare + // the children. Also do not use 'BasePrimitive2D::operator==', that would + // check the ID-Type. Since we are a simple derivation without own ID, + // use the dynamic_cast RTTI directly + const AnimatedGraphicPrimitive2D* pCompare = dynamic_cast<const AnimatedGraphicPrimitive2D*>(&rPrimitive); + + // use operator== of Graphic - if that is equal, the basic definition is equal + return (nullptr != pCompare + && getTransform() == pCompare->getTransform() + && getGraphic() == pCompare->getGraphic()); + } + + Primitive2DContainer AnimatedGraphicPrimitive2D::get2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const + { + if (isValidData()) + { + Primitive2DContainer aRetval(1); + const double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime())); + const sal_uInt32 nLen(maAnimation.Count()); + sal_uInt32 nIndex(basegfx::fround(fState * (double)nLen)); + + // nIndex is the requested frame - it is in range [0..nLen[ + // create frame representation in VirtualDevices + if (nIndex >= nLen) + { + nIndex = nLen - 1L; + } + + // check buffering shortcuts, may already be created + aRetval[0] = tryTogetFromBuffer(nIndex); + + if (aRetval[0].is()) + { + return aRetval; + } + + // if huge size (and not the buffered 1st frame) simply + // create next frame + if (mbHugeSize && 0 != nIndex && mnNextFrameToPrepare <= nIndex) + { + nIndex = mnNextFrameToPrepare % nLen; + } + + // frame not (yet) buffered or no buffering allowed, create it + const_cast<AnimatedGraphicPrimitive2D*>(this)->createFrame(nIndex); + + // try to get from buffer again, may have been added from createFrame + aRetval[0] = tryTogetFromBuffer(nIndex); + + if (aRetval[0].is()) + { + return aRetval; + } + + // did not work (not buffered and not 1st frame), create from buffer + aRetval[0] = createFromBuffer(); + + return aRetval; + } + + return Primitive2DContainer(); + } + + } // end of namespace primitive2d +} // end of namespace drawinglayer namespace drawinglayer { @@ -200,35 +462,11 @@ namespace drawinglayer { if(rGraphic.IsAnimated()) { - // prepare animation data - animatedBitmapExPreparator aData(rGraphic); - - if(aData.count()) - { - // create sub-primitives for animated bitmap and the needed animation loop - animation::AnimationEntryLoop aAnimationLoop(aData.loopCount() ? aData.loopCount() : 0xffff); - Primitive2DContainer aBitmapPrimitives(aData.count()); - - for(sal_uInt32 a(0); a < aData.count(); a++) - { - animation::AnimationEntryFixed aTime((double)aData.stepTime(a), (double)a / (double)aData.count()); - aAnimationLoop.append(aTime); - aBitmapPrimitives[a] = new BitmapPrimitive2D( - aData.stepBitmapEx(a), - rTransform); - } - - // prepare animation list - animation::AnimationEntryList aAnimationList; - aAnimationList.append(aAnimationLoop); - - // create and add animated switch primitive - aRetval.resize(1); - aRetval[0] = new AnimatedSwitchPrimitive2D( - aAnimationList, - aBitmapPrimitives, - false); - } + // prepare specialized AnimatedGraphicPrimitive2D + aRetval.resize(1); + aRetval[0] = new AnimatedGraphicPrimitive2D( + rGraphic, + rTransform); } else if(rGraphic.getSvgData().get()) { |