bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / animate / Animation.cxx
blobe3fb37125af925c9966275f3ed182afe9f527c67
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <algorithm>
21 #include <sal/config.h>
23 #include <tools/stream.hxx>
24 #include <tools/GenericTypeSerializer.hxx>
25 #include <sal/log.hxx>
27 #include <vcl/animate/Animation.hxx>
28 #include <vcl/outdev.hxx>
29 #include <vcl/dibtools.hxx>
30 #include <vcl/BitmapColorQuantizationFilter.hxx>
32 #include <impanmvw.hxx>
34 sal_uLong Animation::mnAnimCount = 0;
36 Animation::Animation()
37 : mnLoopCount(0)
38 , mnLoops(0)
39 , mnPos(0)
40 , mbIsInAnimation(false)
41 , mbLoopTerminated(false)
43 maTimer.SetInvokeHandler(LINK(this, Animation, ImplTimeoutHdl));
46 Animation::Animation(const Animation& rAnimation)
47 : maBitmapEx(rAnimation.maBitmapEx)
48 , maGlobalSize(rAnimation.maGlobalSize)
49 , mnLoopCount(rAnimation.mnLoopCount)
50 , mnPos(rAnimation.mnPos)
51 , mbIsInAnimation(false)
52 , mbLoopTerminated(rAnimation.mbLoopTerminated)
54 for (auto const& i : rAnimation.maList)
55 maList.emplace_back(new AnimationBitmap(*i));
57 maTimer.SetInvokeHandler(LINK(this, Animation, ImplTimeoutHdl));
58 mnLoops = mbLoopTerminated ? 0 : mnLoopCount;
61 Animation::~Animation()
63 if (mbIsInAnimation)
64 Stop();
67 Animation& Animation::operator=(const Animation& rAnimation)
69 if (this != &rAnimation)
71 Clear();
73 for (auto const& i : rAnimation.maList)
74 maList.emplace_back(new AnimationBitmap(*i));
76 maGlobalSize = rAnimation.maGlobalSize;
77 maBitmapEx = rAnimation.maBitmapEx;
78 mnLoopCount = rAnimation.mnLoopCount;
79 mnPos = rAnimation.mnPos;
80 mbLoopTerminated = rAnimation.mbLoopTerminated;
81 mnLoops = mbLoopTerminated ? 0 : mnLoopCount;
83 return *this;
86 bool Animation::operator==(const Animation& rAnimation) const
88 return maList.size() == rAnimation.maList.size() && maBitmapEx == rAnimation.maBitmapEx
89 && maGlobalSize == rAnimation.maGlobalSize
90 && std::equal(maList.begin(), maList.end(), rAnimation.maList.begin(),
91 [](const std::unique_ptr<AnimationBitmap>& pAnim1,
92 const std::unique_ptr<AnimationBitmap>& pAnim2) -> bool {
93 return *pAnim1 == *pAnim2;
94 });
97 void Animation::Clear()
99 maTimer.Stop();
100 mbIsInAnimation = false;
101 maGlobalSize = Size();
102 maBitmapEx.SetEmpty();
103 maList.clear();
104 maViewList.clear();
107 bool Animation::IsTransparent() const
109 tools::Rectangle aRect{ Point(), maGlobalSize };
111 // If some small bitmap needs to be replaced by the background,
112 // we need to be transparent, in order to be displayed correctly
113 // as the application (?) does not invalidate on non-transparent
114 // graphics due to performance reasons.
116 return maBitmapEx.IsTransparent()
117 || std::any_of(maList.begin(), maList.end(),
118 [&aRect](const std::unique_ptr<AnimationBitmap>& pAnim) -> bool {
119 return pAnim->meDisposal == Disposal::Back
120 && tools::Rectangle{ pAnim->maPositionPixel,
121 pAnim->maSizePixel }
122 != aRect;
126 sal_uLong Animation::GetSizeBytes() const
128 sal_uLong nSizeBytes = GetBitmapEx().GetSizeBytes();
130 for (auto const& pAnimationBitmap : maList)
132 nSizeBytes += pAnimationBitmap->maBitmapEx.GetSizeBytes();
135 return nSizeBytes;
138 BitmapChecksum Animation::GetChecksum() const
140 SVBT32 aBT32;
141 BitmapChecksumOctetArray aBCOA;
142 BitmapChecksum nCrc = GetBitmapEx().GetChecksum();
144 UInt32ToSVBT32(maList.size(), aBT32);
145 nCrc = vcl_get_checksum(nCrc, aBT32, 4);
147 Int32ToSVBT32(maGlobalSize.Width(), aBT32);
148 nCrc = vcl_get_checksum(nCrc, aBT32, 4);
150 Int32ToSVBT32(maGlobalSize.Height(), aBT32);
151 nCrc = vcl_get_checksum(nCrc, aBT32, 4);
153 for (auto const& i : maList)
155 BCToBCOA(i->GetChecksum(), aBCOA);
156 nCrc = vcl_get_checksum(nCrc, aBCOA, BITMAP_CHECKSUM_SIZE);
159 return nCrc;
162 bool Animation::Start(OutputDevice* pOut, const Point& rDestPt, const Size& rDestSz,
163 long nExtraData, OutputDevice* pFirstFrameOutDev)
165 bool bRet = false;
167 if (!maList.empty())
169 if ((pOut->GetOutDevType() == OUTDEV_WINDOW) && !mbLoopTerminated
170 && (ANIMATION_TIMEOUT_ON_CLICK != maList[mnPos]->mnWait))
172 ImplAnimView* pView;
173 ImplAnimView* pMatch = nullptr;
175 for (size_t i = 0; i < maViewList.size(); ++i)
177 pView = maViewList[i].get();
178 if (pView->matches(pOut, nExtraData))
180 if (pView->getOutPos() == rDestPt
181 && pView->getOutSizePix() == pOut->LogicToPixel(rDestSz))
183 pView->repaint();
184 pMatch = pView;
186 else
188 maViewList.erase(maViewList.begin() + i);
189 pView = nullptr;
192 break;
196 if (maViewList.empty())
198 maTimer.Stop();
199 mbIsInAnimation = false;
200 mnPos = 0;
203 if (!pMatch)
204 maViewList.emplace_back(
205 new ImplAnimView(this, pOut, rDestPt, rDestSz, nExtraData, pFirstFrameOutDev));
207 if (!mbIsInAnimation)
209 ImplRestartTimer(maList[mnPos]->mnWait);
210 mbIsInAnimation = true;
213 else
214 Draw(pOut, rDestPt, rDestSz);
216 bRet = true;
219 return bRet;
222 void Animation::Stop(OutputDevice* pOut, long nExtraData)
224 maViewList.erase(std::remove_if(maViewList.begin(), maViewList.end(),
225 [=](const std::unique_ptr<ImplAnimView>& pAnimView) -> bool {
226 return pAnimView->matches(pOut, nExtraData);
228 maViewList.end());
230 if (maViewList.empty())
232 maTimer.Stop();
233 mbIsInAnimation = false;
237 void Animation::Draw(OutputDevice* pOut, const Point& rDestPt) const
239 Draw(pOut, rDestPt, pOut->PixelToLogic(maGlobalSize));
242 void Animation::Draw(OutputDevice* pOut, const Point& rDestPt, const Size& rDestSz) const
244 const size_t nCount = maList.size();
246 if (nCount)
248 AnimationBitmap* pObj = maList[std::min(mnPos, nCount - 1)].get();
250 if (pOut->GetConnectMetaFile() || (pOut->GetOutDevType() == OUTDEV_PRINTER))
251 maList[0]->maBitmapEx.Draw(pOut, rDestPt, rDestSz);
252 else if (ANIMATION_TIMEOUT_ON_CLICK == pObj->mnWait)
253 pObj->maBitmapEx.Draw(pOut, rDestPt, rDestSz);
254 else
256 const size_t nOldPos = mnPos;
257 if (mbLoopTerminated)
258 const_cast<Animation*>(this)->mnPos = nCount - 1;
261 ImplAnimView{ const_cast<Animation*>(this), pOut, rDestPt, rDestSz, 0 };
264 const_cast<Animation*>(this)->mnPos = nOldPos;
269 namespace
271 constexpr sal_uLong constMinTimeout = 2;
274 void Animation::ImplRestartTimer(sal_uLong nTimeout)
276 maTimer.SetTimeout(std::max(nTimeout, constMinTimeout) * 10);
277 maTimer.Start();
280 IMPL_LINK_NOARG(Animation, ImplTimeoutHdl, Timer*, void)
282 const size_t nAnimCount = maList.size();
284 if (nAnimCount)
286 ImplAnimView* pView;
287 bool bGlobalPause = true;
289 if (maNotifyLink.IsSet())
291 std::vector<std::unique_ptr<AInfo>> aAInfoList;
292 // create AInfo-List
293 for (auto const& i : maViewList)
294 aAInfoList.emplace_back(i->createAInfo());
296 maNotifyLink.Call(this);
298 // set view state from AInfo structure
299 for (auto& pAInfo : aAInfoList)
301 if (!pAInfo->pViewData)
303 pView = new ImplAnimView(this, pAInfo->pOutDev, pAInfo->aStartOrg,
304 pAInfo->aStartSize, pAInfo->nExtraData);
306 maViewList.push_back(std::unique_ptr<ImplAnimView>(pView));
308 else
309 pView = static_cast<ImplAnimView*>(pAInfo->pViewData);
311 pView->pause(pAInfo->bPause);
312 pView->setMarked(true);
315 // delete all unmarked views and reset marked state
316 for (size_t i = 0; i < maViewList.size();)
318 pView = maViewList[i].get();
319 if (!pView->isMarked())
321 maViewList.erase(maViewList.begin() + i);
323 else
325 if (!pView->isPause())
326 bGlobalPause = false;
328 pView->setMarked(false);
329 i++;
333 else
334 bGlobalPause = false;
336 if (maViewList.empty())
337 Stop();
338 else if (bGlobalPause)
339 ImplRestartTimer(10);
340 else
342 AnimationBitmap* pStepBmp = (++mnPos < maList.size()) ? maList[mnPos].get() : nullptr;
344 if (!pStepBmp)
346 if (mnLoops == 1)
348 Stop();
349 mbLoopTerminated = true;
350 mnPos = nAnimCount - 1;
351 maBitmapEx = maList[mnPos]->maBitmapEx;
352 return;
354 else
356 if (mnLoops)
357 mnLoops--;
359 mnPos = 0;
360 pStepBmp = maList[mnPos].get();
364 // Paint all views; after painting check, if view is
365 // marked; in this case remove view, because area of output
366 // lies out of display area of window; mark state is
367 // set from view itself
368 for (size_t i = 0; i < maViewList.size();)
370 pView = maViewList[i].get();
371 pView->draw(mnPos);
373 if (pView->isMarked())
375 maViewList.erase(maViewList.begin() + i);
377 else
378 i++;
381 // stop or restart timer
382 if (maViewList.empty())
383 Stop();
384 else
385 ImplRestartTimer(pStepBmp->mnWait);
388 else
389 Stop();
392 bool Animation::Insert(const AnimationBitmap& rStepBmp)
394 bool bRet = false;
396 if (!IsInAnimation())
398 tools::Rectangle aGlobalRect(Point(), maGlobalSize);
400 maGlobalSize
401 = aGlobalRect.Union(tools::Rectangle(rStepBmp.maPositionPixel, rStepBmp.maSizePixel))
402 .GetSize();
403 maList.emplace_back(new AnimationBitmap(rStepBmp));
405 // As a start, we make the first BitmapEx the replacement BitmapEx
406 if (maList.size() == 1)
407 maBitmapEx = rStepBmp.maBitmapEx;
409 bRet = true;
412 return bRet;
415 const AnimationBitmap& Animation::Get(sal_uInt16 nAnimation) const
417 SAL_WARN_IF((nAnimation >= maList.size()), "vcl", "No object at this position");
418 return *maList[nAnimation];
421 void Animation::Replace(const AnimationBitmap& rNewAnimationBitmap, sal_uInt16 nAnimation)
423 SAL_WARN_IF((nAnimation >= maList.size()), "vcl", "No object at this position");
425 maList[nAnimation].reset(new AnimationBitmap(rNewAnimationBitmap));
427 // If we insert at first position we also need to
428 // update the replacement BitmapEx
429 if ((!nAnimation && (!mbLoopTerminated || (maList.size() == 1)))
430 || ((nAnimation == maList.size() - 1) && mbLoopTerminated))
432 maBitmapEx = rNewAnimationBitmap.maBitmapEx;
436 void Animation::SetLoopCount(const sal_uInt32 nLoopCount)
438 mnLoopCount = nLoopCount;
439 ResetLoopCount();
442 void Animation::ResetLoopCount()
444 mnLoops = mnLoopCount;
445 mbLoopTerminated = false;
448 void Animation::Convert(BmpConversion eConversion)
450 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
452 bool bRet;
454 if (!IsInAnimation() && !maList.empty())
456 bRet = true;
458 for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
459 bRet = maList[i]->maBitmapEx.Convert(eConversion);
461 maBitmapEx.Convert(eConversion);
465 bool Animation::ReduceColors(sal_uInt16 nNewColorCount)
467 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
469 bool bRet;
471 if (!IsInAnimation() && !maList.empty())
473 bRet = true;
475 for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
477 bRet = BitmapFilter::Filter(maList[i]->maBitmapEx,
478 BitmapColorQuantizationFilter(nNewColorCount));
481 BitmapFilter::Filter(maBitmapEx, BitmapColorQuantizationFilter(nNewColorCount));
483 else
485 bRet = false;
488 return bRet;
491 bool Animation::Invert()
493 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
495 bool bRet;
497 if (!IsInAnimation() && !maList.empty())
499 bRet = true;
501 for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
502 bRet = maList[i]->maBitmapEx.Invert();
504 maBitmapEx.Invert();
506 else
507 bRet = false;
509 return bRet;
512 void Animation::Mirror(BmpMirrorFlags nMirrorFlags)
514 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
516 bool bRet;
518 if (!IsInAnimation() && !maList.empty())
520 bRet = true;
522 if (nMirrorFlags != BmpMirrorFlags::NONE)
524 for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
526 AnimationBitmap* pStepBmp = maList[i].get();
527 bRet = pStepBmp->maBitmapEx.Mirror(nMirrorFlags);
528 if (bRet)
530 if (nMirrorFlags & BmpMirrorFlags::Horizontal)
531 pStepBmp->maPositionPixel.setX(maGlobalSize.Width()
532 - pStepBmp->maPositionPixel.X()
533 - pStepBmp->maSizePixel.Width());
535 if (nMirrorFlags & BmpMirrorFlags::Vertical)
536 pStepBmp->maPositionPixel.setY(maGlobalSize.Height()
537 - pStepBmp->maPositionPixel.Y()
538 - pStepBmp->maSizePixel.Height());
542 maBitmapEx.Mirror(nMirrorFlags);
547 void Animation::Adjust(short nLuminancePercent, short nContrastPercent, short nChannelRPercent,
548 short nChannelGPercent, short nChannelBPercent, double fGamma, bool bInvert)
550 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
552 bool bRet;
554 if (!IsInAnimation() && !maList.empty())
556 bRet = true;
558 for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
560 bRet = maList[i]->maBitmapEx.Adjust(nLuminancePercent, nContrastPercent,
561 nChannelRPercent, nChannelGPercent,
562 nChannelBPercent, fGamma, bInvert);
565 maBitmapEx.Adjust(nLuminancePercent, nContrastPercent, nChannelRPercent, nChannelGPercent,
566 nChannelBPercent, fGamma, bInvert);
570 SvStream& WriteAnimation(SvStream& rOStm, const Animation& rAnimation)
572 const sal_uInt16 nCount = rAnimation.Count();
574 if (nCount)
576 const sal_uInt32 nDummy32 = 0;
578 // If no BitmapEx was set we write the first Bitmap of
579 // the Animation
580 if (!rAnimation.GetBitmapEx().GetBitmap())
581 WriteDIBBitmapEx(rAnimation.Get(0).maBitmapEx, rOStm);
582 else
583 WriteDIBBitmapEx(rAnimation.GetBitmapEx(), rOStm);
585 // Write identifier ( SDANIMA1 )
586 rOStm.WriteUInt32(0x5344414e).WriteUInt32(0x494d4931);
588 for (sal_uInt16 i = 0; i < nCount; i++)
590 const AnimationBitmap& rAnimationBitmap = rAnimation.Get(i);
591 const sal_uInt16 nRest = nCount - i - 1;
593 // Write AnimationBitmap
594 WriteDIBBitmapEx(rAnimationBitmap.maBitmapEx, rOStm);
595 tools::GenericTypeSerializer aSerializer(rOStm);
596 aSerializer.writePoint(rAnimationBitmap.maPositionPixel);
597 aSerializer.writeSize(rAnimationBitmap.maSizePixel);
598 aSerializer.writeSize(rAnimation.maGlobalSize);
599 rOStm.WriteUInt16((ANIMATION_TIMEOUT_ON_CLICK == rAnimationBitmap.mnWait)
600 ? 65535
601 : rAnimationBitmap.mnWait);
602 rOStm.WriteUInt16(static_cast<sal_uInt16>(rAnimationBitmap.meDisposal));
603 rOStm.WriteBool(rAnimationBitmap.mbUserInput);
604 rOStm.WriteUInt32(rAnimation.mnLoopCount);
605 rOStm.WriteUInt32(nDummy32); // Unused
606 rOStm.WriteUInt32(nDummy32); // Unused
607 rOStm.WriteUInt32(nDummy32); // Unused
608 write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, OString()); // dummy
609 rOStm.WriteUInt16(nRest); // Count of remaining structures
613 return rOStm;
616 SvStream& ReadAnimation(SvStream& rIStm, Animation& rAnimation)
618 sal_uLong nStmPos;
619 sal_uInt32 nAnimMagic1, nAnimMagic2;
620 SvStreamEndian nOldFormat = rIStm.GetEndian();
621 bool bReadAnimations = false;
623 rIStm.SetEndian(SvStreamEndian::LITTLE);
624 nStmPos = rIStm.Tell();
625 rIStm.ReadUInt32(nAnimMagic1).ReadUInt32(nAnimMagic2);
627 rAnimation.Clear();
629 // If the BitmapEx at the beginning have already been read (by Graphic)
630 // we can start reading the AnimationBitmaps right away
631 if ((nAnimMagic1 == 0x5344414e) && (nAnimMagic2 == 0x494d4931) && !rIStm.GetError())
632 bReadAnimations = true;
633 // Else, we try reading the Bitmap(-Ex)
634 else
636 rIStm.Seek(nStmPos);
637 ReadDIBBitmapEx(rAnimation.maBitmapEx, rIStm);
638 nStmPos = rIStm.Tell();
639 rIStm.ReadUInt32(nAnimMagic1).ReadUInt32(nAnimMagic2);
641 if ((nAnimMagic1 == 0x5344414e) && (nAnimMagic2 == 0x494d4931) && !rIStm.GetError())
642 bReadAnimations = true;
643 else
644 rIStm.Seek(nStmPos);
647 // Read AnimationBitmaps
648 if (bReadAnimations)
650 AnimationBitmap aAnimationBitmap;
651 sal_uInt32 nTmp32;
652 sal_uInt16 nTmp16;
653 bool cTmp;
657 ReadDIBBitmapEx(aAnimationBitmap.maBitmapEx, rIStm);
658 tools::GenericTypeSerializer aSerializer(rIStm);
659 aSerializer.readPoint(aAnimationBitmap.maPositionPixel);
660 aSerializer.readSize(aAnimationBitmap.maSizePixel);
661 aSerializer.readSize(rAnimation.maGlobalSize);
662 rIStm.ReadUInt16(nTmp16);
663 aAnimationBitmap.mnWait = ((65535 == nTmp16) ? ANIMATION_TIMEOUT_ON_CLICK : nTmp16);
664 rIStm.ReadUInt16(nTmp16);
665 aAnimationBitmap.meDisposal = static_cast<Disposal>(nTmp16);
666 rIStm.ReadCharAsBool(cTmp);
667 aAnimationBitmap.mbUserInput = cTmp;
668 rIStm.ReadUInt32(rAnimation.mnLoopCount);
669 rIStm.ReadUInt32(nTmp32); // Unused
670 rIStm.ReadUInt32(nTmp32); // Unused
671 rIStm.ReadUInt32(nTmp32); // Unused
672 read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm); // Unused
673 rIStm.ReadUInt16(nTmp16); // The rest to read
675 rAnimation.Insert(aAnimationBitmap);
676 } while (nTmp16 && !rIStm.GetError());
678 rAnimation.ResetLoopCount();
681 rIStm.SetEndian(nOldFormat);
683 return rIStm;
686 AInfo::AInfo()
687 : pOutDev(nullptr)
688 , pViewData(nullptr)
689 , nExtraData(0)
690 , bPause(false)
694 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */