1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
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()
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()
67 Animation
& Animation::operator=(const Animation
& rAnimation
)
69 if (this != &rAnimation
)
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
;
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
;
97 void Animation::Clear()
100 mbIsInAnimation
= false;
101 maGlobalSize
= Size();
102 maBitmapEx
.SetEmpty();
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
,
126 sal_uLong
Animation::GetSizeBytes() const
128 sal_uLong nSizeBytes
= GetBitmapEx().GetSizeBytes();
130 for (auto const& pAnimationBitmap
: maList
)
132 nSizeBytes
+= pAnimationBitmap
->maBitmapEx
.GetSizeBytes();
138 BitmapChecksum
Animation::GetChecksum() const
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
);
162 bool Animation::Start(OutputDevice
* pOut
, const Point
& rDestPt
, const Size
& rDestSz
,
163 long nExtraData
, OutputDevice
* pFirstFrameOutDev
)
169 if ((pOut
->GetOutDevType() == OUTDEV_WINDOW
) && !mbLoopTerminated
170 && (ANIMATION_TIMEOUT_ON_CLICK
!= maList
[mnPos
]->mnWait
))
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
))
188 maViewList
.erase(maViewList
.begin() + i
);
196 if (maViewList
.empty())
199 mbIsInAnimation
= false;
204 maViewList
.emplace_back(
205 new ImplAnimView(this, pOut
, rDestPt
, rDestSz
, nExtraData
, pFirstFrameOutDev
));
207 if (!mbIsInAnimation
)
209 ImplRestartTimer(maList
[mnPos
]->mnWait
);
210 mbIsInAnimation
= true;
214 Draw(pOut
, rDestPt
, rDestSz
);
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
);
230 if (maViewList
.empty())
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();
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
);
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
;
271 constexpr sal_uLong constMinTimeout
= 2;
274 void Animation::ImplRestartTimer(sal_uLong nTimeout
)
276 maTimer
.SetTimeout(std::max(nTimeout
, constMinTimeout
) * 10);
280 IMPL_LINK_NOARG(Animation
, ImplTimeoutHdl
, Timer
*, void)
282 const size_t nAnimCount
= maList
.size();
287 bool bGlobalPause
= true;
289 if (maNotifyLink
.IsSet())
291 std::vector
<std::unique_ptr
<AInfo
>> aAInfoList
;
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
));
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
);
325 if (!pView
->isPause())
326 bGlobalPause
= false;
328 pView
->setMarked(false);
334 bGlobalPause
= false;
336 if (maViewList
.empty())
338 else if (bGlobalPause
)
339 ImplRestartTimer(10);
342 AnimationBitmap
* pStepBmp
= (++mnPos
< maList
.size()) ? maList
[mnPos
].get() : nullptr;
349 mbLoopTerminated
= true;
350 mnPos
= nAnimCount
- 1;
351 maBitmapEx
= maList
[mnPos
]->maBitmapEx
;
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();
373 if (pView
->isMarked())
375 maViewList
.erase(maViewList
.begin() + i
);
381 // stop or restart timer
382 if (maViewList
.empty())
385 ImplRestartTimer(pStepBmp
->mnWait
);
392 bool Animation::Insert(const AnimationBitmap
& rStepBmp
)
396 if (!IsInAnimation())
398 tools::Rectangle
aGlobalRect(Point(), maGlobalSize
);
401 = aGlobalRect
.Union(tools::Rectangle(rStepBmp
.maPositionPixel
, rStepBmp
.maSizePixel
))
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
;
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
;
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");
454 if (!IsInAnimation() && !maList
.empty())
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");
471 if (!IsInAnimation() && !maList
.empty())
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
));
491 bool Animation::Invert()
493 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
497 if (!IsInAnimation() && !maList
.empty())
501 for (size_t i
= 0, n
= maList
.size(); (i
< n
) && bRet
; ++i
)
502 bRet
= maList
[i
]->maBitmapEx
.Invert();
512 void Animation::Mirror(BmpMirrorFlags nMirrorFlags
)
514 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
518 if (!IsInAnimation() && !maList
.empty())
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
);
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");
554 if (!IsInAnimation() && !maList
.empty())
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();
576 const sal_uInt32 nDummy32
= 0;
578 // If no BitmapEx was set we write the first Bitmap of
580 if (!rAnimation
.GetBitmapEx().GetBitmap())
581 WriteDIBBitmapEx(rAnimation
.Get(0).maBitmapEx
, rOStm
);
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
)
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
616 SvStream
& ReadAnimation(SvStream
& rIStm
, Animation
& rAnimation
)
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
);
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)
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;
647 // Read AnimationBitmaps
650 AnimationBitmap aAnimationBitmap
;
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
);
694 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */