diff options
Diffstat (limited to 'vcl/source/animate')
-rw-r--r-- | vcl/source/animate/Animation.cxx | 706 | ||||
-rw-r--r-- | vcl/source/animate/AnimationFrame.cxx | 58 | ||||
-rw-r--r-- | vcl/source/animate/AnimationRenderer.cxx | 326 |
3 files changed, 1090 insertions, 0 deletions
diff --git a/vcl/source/animate/Animation.cxx b/vcl/source/animate/Animation.cxx new file mode 100644 index 0000000000..daa9e1f1be --- /dev/null +++ b/vcl/source/animate/Animation.cxx @@ -0,0 +1,706 @@ +/* -*- 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 <algorithm> +#include <sal/config.h> + +#include <rtl/crc.h> +#include <tools/stream.hxx> +#include <tools/GenericTypeSerializer.hxx> +#include <sal/log.hxx> + +#include <vcl/animate/Animation.hxx> +#include <vcl/outdev.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/BitmapColorQuantizationFilter.hxx> + +#include <animate/AnimationRenderer.hxx> + +sal_uLong Animation::gAnimationRendererCount = 0; + +Animation::Animation() + : maTimer("vcl::Animation") + , mnLoopCount(0) + , mnLoops(0) + , mnFrameIndex(0) + , mbIsInAnimation(false) + , mbLoopTerminated(false) +{ + maTimer.SetInvokeHandler(LINK(this, Animation, ImplTimeoutHdl)); +} + +Animation::Animation(const Animation& rAnimation) + : maBitmapEx(rAnimation.maBitmapEx) + , maTimer("vcl::Animation") + , maGlobalSize(rAnimation.maGlobalSize) + , mnLoopCount(rAnimation.mnLoopCount) + , mnFrameIndex(rAnimation.mnFrameIndex) + , mbIsInAnimation(false) + , mbLoopTerminated(rAnimation.mbLoopTerminated) +{ + for (auto const& rFrame : rAnimation.maFrames) + maFrames.emplace_back(new AnimationFrame(*rFrame)); + + maTimer.SetInvokeHandler(LINK(this, Animation, ImplTimeoutHdl)); + mnLoops = mbLoopTerminated ? 0 : mnLoopCount; +} + +Animation::~Animation() +{ + if (mbIsInAnimation) + Stop(); +} + +Animation& Animation::operator=(const Animation& rAnimation) +{ + if (this != &rAnimation) + { + Clear(); + + for (auto const& i : rAnimation.maFrames) + maFrames.emplace_back(new AnimationFrame(*i)); + + maGlobalSize = rAnimation.maGlobalSize; + maBitmapEx = rAnimation.maBitmapEx; + mnLoopCount = rAnimation.mnLoopCount; + mnFrameIndex = rAnimation.mnFrameIndex; + mbLoopTerminated = rAnimation.mbLoopTerminated; + mnLoops = mbLoopTerminated ? 0 : mnLoopCount; + } + return *this; +} + +bool Animation::operator==(const Animation& rAnimation) const +{ + return maFrames.size() == rAnimation.maFrames.size() && maBitmapEx == rAnimation.maBitmapEx + && maGlobalSize == rAnimation.maGlobalSize + && std::equal(maFrames.begin(), maFrames.end(), rAnimation.maFrames.begin(), + [](const std::unique_ptr<AnimationFrame>& pAnim1, + const std::unique_ptr<AnimationFrame>& pAnim2) -> bool { + return *pAnim1 == *pAnim2; + }); +} + +void Animation::Clear() +{ + maTimer.Stop(); + mbIsInAnimation = false; + maGlobalSize = Size(); + maBitmapEx.SetEmpty(); + maFrames.clear(); + maRenderers.clear(); +} + +bool Animation::IsTransparent() const +{ + tools::Rectangle aRect{ Point(), maGlobalSize }; + + // If some small bitmap needs to be replaced by the background, + // we need to be transparent, in order to be displayed correctly + // as the application (?) does not invalidate on non-transparent + // graphics due to performance reasons. + + return maBitmapEx.IsAlpha() + || std::any_of(maFrames.begin(), maFrames.end(), + [&aRect](const std::unique_ptr<AnimationFrame>& pAnim) -> bool { + return pAnim->meDisposal == Disposal::Back + && tools::Rectangle{ pAnim->maPositionPixel, + pAnim->maSizePixel } + != aRect; + }); +} + +sal_uLong Animation::GetSizeBytes() const +{ + sal_uLong nSizeBytes = GetBitmapEx().GetSizeBytes(); + + for (auto const& pAnimationFrame : maFrames) + { + nSizeBytes += pAnimationFrame->maBitmapEx.GetSizeBytes(); + } + + return nSizeBytes; +} + +BitmapChecksum Animation::GetChecksum() const +{ + SVBT32 aBT32; + BitmapChecksumOctetArray aBCOA; + BitmapChecksum nCrc = GetBitmapEx().GetChecksum(); + + UInt32ToSVBT32(maFrames.size(), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + Int32ToSVBT32(maGlobalSize.Width(), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + Int32ToSVBT32(maGlobalSize.Height(), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + for (auto const& i : maFrames) + { + BCToBCOA(i->GetChecksum(), aBCOA); + nCrc = rtl_crc32(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE); + } + + return nCrc; +} + +bool Animation::Start(OutputDevice& rOut, const Point& rDestPt, const Size& rDestSz, + tools::Long nRendererId, OutputDevice* pFirstFrameOutDev) +{ + bool bRet = false; + + if (!maFrames.empty()) + { + if ((rOut.GetOutDevType() == OUTDEV_WINDOW) && !mbLoopTerminated + && (ANIMATION_TIMEOUT_ON_CLICK != maFrames[mnFrameIndex]->mnWait)) + { + bool differs = true; + + auto itAnimView = std::find_if( + maRenderers.begin(), maRenderers.end(), + [&rOut, nRendererId](const std::unique_ptr<AnimationRenderer>& pRenderer) -> bool { + return pRenderer->matches(&rOut, nRendererId); + }); + + if (itAnimView != maRenderers.end()) + { + if ((*itAnimView)->getOriginPosition() == rDestPt + && (*itAnimView)->getOutSizePix() == rOut.LogicToPixel(rDestSz)) + { + (*itAnimView)->repaint(); + differs = false; + } + else + { + maRenderers.erase(itAnimView); + } + } + + if (maRenderers.empty()) + { + maTimer.Stop(); + mbIsInAnimation = false; + mnFrameIndex = 0; + } + + if (differs) + maRenderers.emplace_back(new AnimationRenderer(this, &rOut, rDestPt, rDestSz, + nRendererId, pFirstFrameOutDev)); + + if (!mbIsInAnimation) + { + ImplRestartTimer(maFrames[mnFrameIndex]->mnWait); + mbIsInAnimation = true; + } + } + else + Draw(rOut, rDestPt, rDestSz); + + bRet = true; + } + + return bRet; +} + +void Animation::Stop(const OutputDevice* pOut, tools::Long nRendererId) +{ + std::erase_if(maRenderers, [=](const std::unique_ptr<AnimationRenderer>& pRenderer) -> bool { + return pRenderer->matches(pOut, nRendererId); + }); + + if (maRenderers.empty()) + { + maTimer.Stop(); + mbIsInAnimation = false; + } +} + +void Animation::Draw(OutputDevice& rOut, const Point& rDestPt) const +{ + Draw(rOut, rDestPt, rOut.PixelToLogic(maGlobalSize)); +} + +void Animation::Draw(OutputDevice& rOut, const Point& rDestPt, const Size& rDestSz) const +{ + const size_t nCount = maFrames.size(); + + if (!nCount) + return; + + AnimationFrame* pObj = maFrames[std::min(mnFrameIndex, nCount - 1)].get(); + + if (rOut.GetConnectMetaFile() || (rOut.GetOutDevType() == OUTDEV_PRINTER)) + { + maFrames[0]->maBitmapEx.Draw(&rOut, rDestPt, rDestSz); + } + else if (ANIMATION_TIMEOUT_ON_CLICK == pObj->mnWait) + { + pObj->maBitmapEx.Draw(&rOut, rDestPt, rDestSz); + } + else + { + const size_t nOldPos = mnFrameIndex; + if (mbLoopTerminated) + const_cast<Animation*>(this)->mnFrameIndex = nCount - 1; + + { + AnimationRenderer{ const_cast<Animation*>(this), &rOut, rDestPt, rDestSz, 0 }; + } + + const_cast<Animation*>(this)->mnFrameIndex = nOldPos; + } +} + +namespace +{ +constexpr sal_uLong constMinTimeout = 2; +} + +void Animation::ImplRestartTimer(sal_uLong nTimeout) +{ + maTimer.SetTimeout(std::max(nTimeout, constMinTimeout) * 10); + maTimer.Start(); +} + +std::vector<std::unique_ptr<AnimationData>> Animation::CreateAnimationDataItems() +{ + std::vector<std::unique_ptr<AnimationData>> aDataItems; + + for (auto const& rItem : maRenderers) + { + aDataItems.emplace_back(rItem->createAnimationData()); + } + + return aDataItems; +} + +void Animation::PopulateRenderers() +{ + for (auto& pDataItem : CreateAnimationDataItems()) + { + AnimationRenderer* pRenderer = nullptr; + if (!pDataItem->mpRendererData) + { + pRenderer = new AnimationRenderer(this, pDataItem->mpRenderContext, + pDataItem->maOriginStartPt, pDataItem->maStartSize, + pDataItem->mnRendererId); + + maRenderers.push_back(std::unique_ptr<AnimationRenderer>(pRenderer)); + } + else + { + pRenderer = pDataItem->mpRendererData; + } + + pRenderer->pause(pDataItem->mbIsPaused); + pRenderer->setMarked(true); + } +} + +void Animation::RenderNextFrameInAllRenderers() +{ + AnimationFrame* pCurrentFrameBmp + = (++mnFrameIndex < maFrames.size()) ? maFrames[mnFrameIndex].get() : nullptr; + + if (!pCurrentFrameBmp) + { + if (mnLoops == 1) + { + Stop(); + mbLoopTerminated = true; + mnFrameIndex = maFrames.size() - 1; + maBitmapEx = maFrames[mnFrameIndex]->maBitmapEx; + return; + } + else + { + if (mnLoops) + mnLoops--; + + mnFrameIndex = 0; + pCurrentFrameBmp = maFrames[mnFrameIndex].get(); + } + } + + // Paint all views. + std::for_each(maRenderers.cbegin(), maRenderers.cend(), + [this](const auto& pRenderer) { pRenderer->draw(mnFrameIndex); }); + /* + * If a view is marked, remove the view, because + * area of output lies out of display area of window. + * Mark state is set from view itself. + */ + std::erase_if(maRenderers, [](const auto& pRenderer) { return pRenderer->isMarked(); }); + + // stop or restart timer + if (maRenderers.empty()) + Stop(); + else + ImplRestartTimer(pCurrentFrameBmp->mnWait); +} + +void Animation::PruneMarkedRenderers() +{ + // delete all unmarked views + std::erase_if(maRenderers, [](const auto& pRenderer) { return !pRenderer->isMarked(); }); + + // reset marked state + std::for_each(maRenderers.cbegin(), maRenderers.cend(), + [](const auto& pRenderer) { pRenderer->setMarked(false); }); +} + +bool Animation::IsAnyRendererActive() +{ + return std::any_of(maRenderers.cbegin(), maRenderers.cend(), + [](const auto& pRenderer) { return !pRenderer->isPaused(); }); +} + +IMPL_LINK_NOARG(Animation, ImplTimeoutHdl, Timer*, void) +{ + const size_t nAnimCount = maFrames.size(); + + if (nAnimCount) + { + bool bIsAnyRendererActive = true; + + if (maNotifyLink.IsSet()) + { + maNotifyLink.Call(this); + PopulateRenderers(); + PruneMarkedRenderers(); + bIsAnyRendererActive = IsAnyRendererActive(); + } + + if (maRenderers.empty()) + Stop(); + else if (!bIsAnyRendererActive) + ImplRestartTimer(10); + else + RenderNextFrameInAllRenderers(); + } + else + { + Stop(); + } +} + +bool Animation::Insert(const AnimationFrame& rStepBmp) +{ + bool bRet = false; + + if (!IsInAnimation()) + { + tools::Rectangle aGlobalRect(Point(), maGlobalSize); + + maGlobalSize + = aGlobalRect.Union(tools::Rectangle(rStepBmp.maPositionPixel, rStepBmp.maSizePixel)) + .GetSize(); + maFrames.emplace_back(new AnimationFrame(rStepBmp)); + + // As a start, we make the first BitmapEx the replacement BitmapEx + if (maFrames.size() == 1) + maBitmapEx = rStepBmp.maBitmapEx; + + bRet = true; + } + + return bRet; +} + +const AnimationFrame& Animation::Get(sal_uInt16 nAnimation) const +{ + SAL_WARN_IF((nAnimation >= maFrames.size()), "vcl", "No object at this position"); + return *maFrames[nAnimation]; +} + +void Animation::Replace(const AnimationFrame& rNewAnimationFrame, sal_uInt16 nAnimation) +{ + SAL_WARN_IF((nAnimation >= maFrames.size()), "vcl", "No object at this position"); + + maFrames[nAnimation].reset(new AnimationFrame(rNewAnimationFrame)); + + // If we insert at first position we also need to + // update the replacement BitmapEx + if ((!nAnimation && (!mbLoopTerminated || (maFrames.size() == 1))) + || ((nAnimation == maFrames.size() - 1) && mbLoopTerminated)) + { + maBitmapEx = rNewAnimationFrame.maBitmapEx; + } +} + +void Animation::SetLoopCount(const sal_uInt32 nLoopCount) +{ + mnLoopCount = nLoopCount; + ResetLoopCount(); +} + +void Animation::ResetLoopCount() +{ + mnLoops = mnLoopCount; + mbLoopTerminated = false; +} + +void Animation::Convert(BmpConversion eConversion) +{ + SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated"); + + bool bRet; + + if (!IsInAnimation() && !maFrames.empty()) + { + bRet = true; + + for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i) + bRet = maFrames[i]->maBitmapEx.Convert(eConversion); + + maBitmapEx.Convert(eConversion); + } +} + +bool Animation::ReduceColors(sal_uInt16 nNewColorCount) +{ + SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated"); + + bool bRet; + + if (!IsInAnimation() && !maFrames.empty()) + { + bRet = true; + + for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i) + { + bRet = BitmapFilter::Filter(maFrames[i]->maBitmapEx, + BitmapColorQuantizationFilter(nNewColorCount)); + } + + BitmapFilter::Filter(maBitmapEx, BitmapColorQuantizationFilter(nNewColorCount)); + } + else + { + bRet = false; + } + + return bRet; +} + +bool Animation::Invert() +{ + SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated"); + + bool bRet; + + if (!IsInAnimation() && !maFrames.empty()) + { + bRet = true; + + for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i) + bRet = maFrames[i]->maBitmapEx.Invert(); + + maBitmapEx.Invert(); + } + else + bRet = false; + + return bRet; +} + +void Animation::Mirror(BmpMirrorFlags nMirrorFlags) +{ + SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated"); + + bool bRet; + + if (IsInAnimation() || maFrames.empty()) + return; + + bRet = true; + + if (nMirrorFlags == BmpMirrorFlags::NONE) + return; + + for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i) + { + AnimationFrame* pCurrentFrameBmp = maFrames[i].get(); + bRet = pCurrentFrameBmp->maBitmapEx.Mirror(nMirrorFlags); + if (bRet) + { + if (nMirrorFlags & BmpMirrorFlags::Horizontal) + pCurrentFrameBmp->maPositionPixel.setX(maGlobalSize.Width() + - pCurrentFrameBmp->maPositionPixel.X() + - pCurrentFrameBmp->maSizePixel.Width()); + + if (nMirrorFlags & BmpMirrorFlags::Vertical) + pCurrentFrameBmp->maPositionPixel.setY(maGlobalSize.Height() + - pCurrentFrameBmp->maPositionPixel.Y() + - pCurrentFrameBmp->maSizePixel.Height()); + } + } + + maBitmapEx.Mirror(nMirrorFlags); +} + +void Animation::Adjust(short nLuminancePercent, short nContrastPercent, short nChannelRPercent, + short nChannelGPercent, short nChannelBPercent, double fGamma, bool bInvert) +{ + SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated"); + + bool bRet; + + if (IsInAnimation() || maFrames.empty()) + return; + + bRet = true; + + for (size_t i = 0, n = maFrames.size(); (i < n) && bRet; ++i) + { + bRet = maFrames[i]->maBitmapEx.Adjust(nLuminancePercent, nContrastPercent, nChannelRPercent, + nChannelGPercent, nChannelBPercent, fGamma, bInvert); + } + + maBitmapEx.Adjust(nLuminancePercent, nContrastPercent, nChannelRPercent, nChannelGPercent, + nChannelBPercent, fGamma, bInvert); +} + +SvStream& WriteAnimation(SvStream& rOStm, const Animation& rAnimation) +{ + const sal_uInt16 nCount = rAnimation.Count(); + + if (nCount) + { + const sal_uInt32 nDummy32 = 0; + + // If no BitmapEx was set we write the first Bitmap of + // the Animation + if (rAnimation.GetBitmapEx().GetBitmap().IsEmpty()) + WriteDIBBitmapEx(rAnimation.Get(0).maBitmapEx, rOStm); + else + WriteDIBBitmapEx(rAnimation.GetBitmapEx(), rOStm); + + // Write identifier ( SDANIMA1 ) + rOStm.WriteUInt32(0x5344414e).WriteUInt32(0x494d4931); + + for (sal_uInt16 i = 0; i < nCount; i++) + { + const AnimationFrame& rAnimationFrame = rAnimation.Get(i); + const sal_uInt16 nRest = nCount - i - 1; + + // Write AnimationFrame + WriteDIBBitmapEx(rAnimationFrame.maBitmapEx, rOStm); + tools::GenericTypeSerializer aSerializer(rOStm); + aSerializer.writePoint(rAnimationFrame.maPositionPixel); + aSerializer.writeSize(rAnimationFrame.maSizePixel); + aSerializer.writeSize(rAnimation.maGlobalSize); + rOStm.WriteUInt16((ANIMATION_TIMEOUT_ON_CLICK == rAnimationFrame.mnWait) + ? 65535 + : rAnimationFrame.mnWait); + rOStm.WriteUInt16(static_cast<sal_uInt16>(rAnimationFrame.meDisposal)); + rOStm.WriteBool(rAnimationFrame.mbUserInput); + rOStm.WriteUInt32(rAnimation.mnLoopCount); + rOStm.WriteUInt32(nDummy32); // Unused + rOStm.WriteUInt32(nDummy32); // Unused + rOStm.WriteUInt32(nDummy32); // Unused + write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, ""); // dummy + rOStm.WriteUInt16(nRest); // Count of remaining structures + } + } + + return rOStm; +} + +SvStream& ReadAnimation(SvStream& rIStm, Animation& rAnimation) +{ + sal_uLong nStmPos; + sal_uInt32 nAnimMagic1, nAnimMagic2; + SvStreamEndian nOldFormat = rIStm.GetEndian(); + bool bReadAnimations = false; + + rIStm.SetEndian(SvStreamEndian::LITTLE); + nStmPos = rIStm.Tell(); + rIStm.ReadUInt32(nAnimMagic1).ReadUInt32(nAnimMagic2); + + rAnimation.Clear(); + + // If the BitmapEx at the beginning have already been read (by Graphic) + // we can start reading the AnimationFrames right away + if ((nAnimMagic1 == 0x5344414e) && (nAnimMagic2 == 0x494d4931) && !rIStm.GetError()) + bReadAnimations = true; + // Else, we try reading the Bitmap(-Ex) + else + { + rIStm.Seek(nStmPos); + ReadDIBBitmapEx(rAnimation.maBitmapEx, rIStm); + nStmPos = rIStm.Tell(); + rIStm.ReadUInt32(nAnimMagic1).ReadUInt32(nAnimMagic2); + + if ((nAnimMagic1 == 0x5344414e) && (nAnimMagic2 == 0x494d4931) && !rIStm.GetError()) + bReadAnimations = true; + else + rIStm.Seek(nStmPos); + } + + // Read AnimationFrames + if (bReadAnimations) + { + AnimationFrame aAnimationFrame; + sal_uInt32 nTmp32; + sal_uInt16 nTmp16; + bool cTmp; + + do + { + ReadDIBBitmapEx(aAnimationFrame.maBitmapEx, rIStm); + tools::GenericTypeSerializer aSerializer(rIStm); + aSerializer.readPoint(aAnimationFrame.maPositionPixel); + aSerializer.readSize(aAnimationFrame.maSizePixel); + aSerializer.readSize(rAnimation.maGlobalSize); + rIStm.ReadUInt16(nTmp16); + aAnimationFrame.mnWait = ((65535 == nTmp16) ? ANIMATION_TIMEOUT_ON_CLICK : nTmp16); + rIStm.ReadUInt16(nTmp16); + aAnimationFrame.meDisposal = static_cast<Disposal>(nTmp16); + rIStm.ReadCharAsBool(cTmp); + aAnimationFrame.mbUserInput = cTmp; + rIStm.ReadUInt32(rAnimation.mnLoopCount); + rIStm.ReadUInt32(nTmp32); // Unused + rIStm.ReadUInt32(nTmp32); // Unused + rIStm.ReadUInt32(nTmp32); // Unused + read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Unused + rIStm.ReadUInt16(nTmp16); // The rest to read + + rAnimation.Insert(aAnimationFrame); + } while (nTmp16 && !rIStm.GetError()); + + rAnimation.ResetLoopCount(); + } + + rIStm.SetEndian(nOldFormat); + + return rIStm; +} + +AnimationData::AnimationData() + : mpRenderContext(nullptr) + , mpRendererData(nullptr) + , mnRendererId(0) + , mbIsPaused(false) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/animate/AnimationFrame.cxx b/vcl/source/animate/AnimationFrame.cxx new file mode 100644 index 0000000000..697167ebdc --- /dev/null +++ b/vcl/source/animate/AnimationFrame.cxx @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <o3tl/underlyingenumvalue.hxx> +#include <tools/solar.h> +#include <vcl/animate/AnimationFrame.hxx> +#include <rtl/crc.h> + +BitmapChecksum AnimationFrame::GetChecksum() const +{ + BitmapChecksum nCrc = maBitmapEx.GetChecksum(); + SVBT32 aBT32; + + Int32ToSVBT32(maPositionPixel.X(), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + Int32ToSVBT32(maPositionPixel.Y(), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + Int32ToSVBT32(maSizePixel.Width(), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + Int32ToSVBT32(maSizePixel.Height(), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + Int32ToSVBT32(mnWait, aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + UInt32ToSVBT32(o3tl::to_underlying(meDisposal), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + UInt32ToSVBT32(o3tl::to_underlying(meBlend), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + UInt32ToSVBT32(sal_uInt32(mbUserInput), aBT32); + nCrc = rtl_crc32(nCrc, aBT32, 4); + + return nCrc; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/animate/AnimationRenderer.cxx b/vcl/source/animate/AnimationRenderer.cxx new file mode 100644 index 0000000000..29f386e0d3 --- /dev/null +++ b/vcl/source/animate/AnimationRenderer.cxx @@ -0,0 +1,326 @@ +/* -*- 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 <memory> +#include <animate/AnimationRenderer.hxx> + +#include <vcl/virdev.hxx> +#include <vcl/window.hxx> +#include <tools/helpers.hxx> + +#include <window.h> + +AnimationRenderer::AnimationRenderer( Animation* pParent, OutputDevice* pOut, + const Point& rPt, const Size& rSz, + sal_uLong nRendererId, + OutputDevice* pFirstFrameOutDev ) : + mpParent ( pParent ), + mpRenderContext ( pFirstFrameOutDev ? pFirstFrameOutDev : pOut ), + mnRendererId ( nRendererId ), + maOriginPt ( rPt ), + maLogicalSize ( rSz ), + maSizePx ( mpRenderContext->LogicToPixel( maLogicalSize ) ), + maClip ( mpRenderContext->GetClipRegion() ), + mpBackground ( VclPtr<VirtualDevice>::Create() ), + mpRestore ( VclPtr<VirtualDevice>::Create() ), + mnActIndex ( 0 ), + meLastDisposal ( Disposal::Back ), + mbIsPaused ( false ), + mbIsMarked ( false ), + mbIsMirroredHorizontally( maLogicalSize.Width() < 0 ), + mbIsMirroredVertically( maLogicalSize.Height() < 0 ) +{ + Animation::ImplIncAnimCount(); + + // Mirrored horizontally? + if( mbIsMirroredHorizontally ) + { + maDispPt.setX( maOriginPt.X() + maLogicalSize.Width() + 1 ); + maDispSz.setWidth( -maLogicalSize.Width() ); + maSizePx.setWidth( -maSizePx.Width() ); + } + else + { + maDispPt.setX( maOriginPt.X() ); + maDispSz.setWidth( maLogicalSize.Width() ); + } + + // Mirrored vertically? + if( mbIsMirroredVertically ) + { + maDispPt.setY( maOriginPt.Y() + maLogicalSize.Height() + 1 ); + maDispSz.setHeight( -maLogicalSize.Height() ); + maSizePx.setHeight( -maSizePx.Height() ); + } + else + { + maDispPt.setY( maOriginPt.Y() ); + maDispSz.setHeight( maLogicalSize.Height() ); + } + + // save background + mpBackground->SetOutputSizePixel( maSizePx ); + mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSizePx); + + // Initialize drawing to actual position + drawToIndex( mpParent->ImplGetCurPos() ); + + // If first frame OutputDevice is set, update variables now for real OutputDevice + if( pFirstFrameOutDev ) + { + mpRenderContext = pOut; + maClip = mpRenderContext->GetClipRegion(); + } +} + +AnimationRenderer::~AnimationRenderer() +{ + mpBackground.disposeAndClear(); + mpRestore.disposeAndClear(); + + Animation::ImplDecAnimCount(); +} + +bool AnimationRenderer::matches(const OutputDevice* pOut, tools::Long nRendererId) const +{ + return (!pOut || pOut == mpRenderContext) && (nRendererId == 0 || nRendererId == mnRendererId); +} + +void AnimationRenderer::getPosSize( const AnimationFrame& rAnimationFrame, Point& rPosPix, Size& rSizePix ) +{ + const Size& rAnmSize = mpParent->GetDisplaySizePixel(); + Point aPt2( rAnimationFrame.maPositionPixel.X() + rAnimationFrame.maSizePixel.Width() - 1, + rAnimationFrame.maPositionPixel.Y() + rAnimationFrame.maSizePixel.Height() - 1 ); + double fFactX, fFactY; + + // calculate x scaling + if( rAnmSize.Width() > 1 ) + fFactX = static_cast<double>( maSizePx.Width() - 1 ) / ( rAnmSize.Width() - 1 ); + else + fFactX = 1.0; + + // calculate y scaling + if( rAnmSize.Height() > 1 ) + fFactY = static_cast<double>( maSizePx.Height() - 1 ) / ( rAnmSize.Height() - 1 ); + else + fFactY = 1.0; + + rPosPix.setX( FRound( rAnimationFrame.maPositionPixel.X() * fFactX ) ); + rPosPix.setY( FRound( rAnimationFrame.maPositionPixel.Y() * fFactY ) ); + + aPt2.setX( FRound( aPt2.X() * fFactX ) ); + aPt2.setY( FRound( aPt2.Y() * fFactY ) ); + + rSizePix.setWidth( aPt2.X() - rPosPix.X() + 1 ); + rSizePix.setHeight( aPt2.Y() - rPosPix.Y() + 1 ); + + // Mirrored horizontally? + if( mbIsMirroredHorizontally ) + rPosPix.setX( maSizePx.Width() - 1 - aPt2.X() ); + + // Mirrored vertically? + if( mbIsMirroredVertically ) + rPosPix.setY( maSizePx.Height() - 1 - aPt2.Y() ); +} + +void AnimationRenderer::drawToIndex( sal_uLong nIndex ) +{ + VclPtr<vcl::RenderContext> pRenderContext = mpRenderContext; + + vcl::PaintBufferGuardPtr pGuard; + if (mpRenderContext->GetOutDevType() == OUTDEV_WINDOW) + { + vcl::Window* pWindow = static_cast<vcl::WindowOutputDevice*>(mpRenderContext.get())->GetOwnerWindow(); + pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow)); + pRenderContext = pGuard->GetRenderContext(); + } + + ScopedVclPtrInstance<VirtualDevice> aVDev; + std::optional<vcl::Region> xOldClip; + if (!maClip.IsNull()) + xOldClip = pRenderContext->GetClipRegion(); + + aVDev->SetOutputSizePixel( maSizePx, false ); + nIndex = std::min( nIndex, static_cast<sal_uLong>(mpParent->Count()) - 1 ); + + for( sal_uLong i = 0; i <= nIndex; i++ ) + draw( i, aVDev.get() ); + + if (xOldClip) + pRenderContext->SetClipRegion( maClip ); + + pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSizePx, *aVDev ); + if (pGuard) + pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz)); + + if (xOldClip) + pRenderContext->SetClipRegion(*xOldClip); +} + +void AnimationRenderer::draw( sal_uLong nIndex, VirtualDevice* pVDev ) +{ + VclPtr<vcl::RenderContext> pRenderContext = mpRenderContext; + + vcl::PaintBufferGuardPtr pGuard; + if (!pVDev && mpRenderContext->GetOutDevType() == OUTDEV_WINDOW) + { + vcl::Window* pWindow = static_cast<vcl::WindowOutputDevice*>(mpRenderContext.get())->GetOwnerWindow(); + pGuard.reset(new vcl::PaintBufferGuard(pWindow->ImplGetWindowImpl()->mpFrameData, pWindow)); + pRenderContext = pGuard->GetRenderContext(); + } + + tools::Rectangle aOutRect( pRenderContext->PixelToLogic( Point() ), pRenderContext->GetOutputSize() ); + + // check, if output lies out of display + if( aOutRect.Intersection( tools::Rectangle( maDispPt, maDispSz ) ).IsEmpty() ) + { + setMarked( true ); + } + else if( !mbIsPaused ) + { + VclPtr<VirtualDevice> pDev; + Point aPosPix; + Point aBmpPosPix; + Size aSizePix; + Size aBmpSizePix; + const sal_uLong nLastPos = mpParent->Count() - 1; + mnActIndex = std::min( nIndex, nLastPos ); + const AnimationFrame& rAnimationFrame = mpParent->Get( static_cast<sal_uInt16>( mnActIndex ) ); + + getPosSize( rAnimationFrame, aPosPix, aSizePix ); + + // Mirrored horizontally? + if( mbIsMirroredHorizontally ) + { + aBmpPosPix.setX( aPosPix.X() + aSizePix.Width() - 1 ); + aBmpSizePix.setWidth( -aSizePix.Width() ); + } + else + { + aBmpPosPix.setX( aPosPix.X() ); + aBmpSizePix.setWidth( aSizePix.Width() ); + } + + // Mirrored vertically? + if( mbIsMirroredVertically ) + { + aBmpPosPix.setY( aPosPix.Y() + aSizePix.Height() - 1 ); + aBmpSizePix.setHeight( -aSizePix.Height() ); + } + else + { + aBmpPosPix.setY( aPosPix.Y() ); + aBmpSizePix.setHeight( aSizePix.Height() ); + } + + // get output device + if( !pVDev ) + { + pDev = VclPtr<VirtualDevice>::Create(); + pDev->SetOutputSizePixel( maSizePx, false ); + pDev->DrawOutDev( Point(), maSizePx, maDispPt, maDispSz, *pRenderContext ); + } + else + pDev = pVDev; + + // restore background after each run + if( !nIndex ) + { + meLastDisposal = Disposal::Back; + maRestPt = Point(); + maRestSz = maSizePx; + } + + // restore + if( ( Disposal::Not != meLastDisposal ) && maRestSz.Width() && maRestSz.Height() ) + { + if( Disposal::Back == meLastDisposal ) + pDev->DrawOutDev( maRestPt, maRestSz, maRestPt, maRestSz, *mpBackground ); + else + pDev->DrawOutDev( maRestPt, maRestSz, Point(), maRestSz, *mpRestore ); + } + + meLastDisposal = rAnimationFrame.meDisposal; + maRestPt = aPosPix; + maRestSz = aSizePix; + + // What do we need to restore the next time? + // Put it into a bitmap if needed, else delete + // SaveBitmap to conserve memory + if( ( meLastDisposal == Disposal::Back ) || ( meLastDisposal == Disposal::Not ) ) + mpRestore->SetOutputSizePixel( Size( 1, 1 ), false ); + else + { + mpRestore->SetOutputSizePixel( maRestSz, false ); + mpRestore->DrawOutDev( Point(), maRestSz, aPosPix, aSizePix, *pDev ); + } + + pDev->DrawBitmapEx( aBmpPosPix, aBmpSizePix, rAnimationFrame.maBitmapEx ); + + if( !pVDev ) + { + std::optional<vcl::Region> xOldClip; + if (!maClip.IsNull()) + xOldClip = pRenderContext->GetClipRegion(); + + if (xOldClip) + pRenderContext->SetClipRegion( maClip ); + + pRenderContext->DrawOutDev( maDispPt, maDispSz, Point(), maSizePx, *pDev ); + if (pGuard) + pGuard->SetPaintRect(tools::Rectangle(maDispPt, maDispSz)); + + if( xOldClip) + { + pRenderContext->SetClipRegion(*xOldClip); + xOldClip.reset(); + } + + pDev.disposeAndClear(); + pRenderContext->Flush(); + } + } +} + +void AnimationRenderer::repaint() +{ + const bool bOldPause = mbIsPaused; + + mpRenderContext->SaveBackground(*mpBackground, maDispPt, maDispSz, maSizePx); + + mbIsPaused = false; + drawToIndex( mnActIndex ); + mbIsPaused = bOldPause; +} + +AnimationData* AnimationRenderer::createAnimationData() const +{ + AnimationData* pDataItem = new AnimationData; + + pDataItem->maOriginStartPt = maOriginPt; + pDataItem->maStartSize = maLogicalSize; + pDataItem->mpRenderContext = mpRenderContext; + pDataItem->mpRendererData = const_cast<AnimationRenderer *>(this); + pDataItem->mnRendererId = mnRendererId; + pDataItem->mbIsPaused = mbIsPaused; + + return pDataItem; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |