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 tools::Long nExtraData
, OutputDevice
* pFirstFrameOutDev
)
169 if ((pOut
->GetOutDevType() == OUTDEV_WINDOW
) && !mbLoopTerminated
170 && (ANIMATION_TIMEOUT_ON_CLICK
!= maList
[mnPos
]->mnWait
))
174 auto itAnimView
= std::find_if(
175 maViewList
.begin(), maViewList
.end(),
176 [pOut
, nExtraData
](const std::unique_ptr
<ImplAnimView
>& pAnimView
) -> bool {
177 return pAnimView
->matches(pOut
, nExtraData
);
180 if (itAnimView
!= maViewList
.end())
182 if ((*itAnimView
)->getOutPos() == rDestPt
183 && (*itAnimView
)->getOutSizePix() == pOut
->LogicToPixel(rDestSz
))
185 (*itAnimView
)->repaint();
189 maViewList
.erase(itAnimView
);
192 if (maViewList
.empty())
195 mbIsInAnimation
= false;
200 maViewList
.emplace_back(
201 new ImplAnimView(this, pOut
, rDestPt
, rDestSz
, nExtraData
, pFirstFrameOutDev
));
203 if (!mbIsInAnimation
)
205 ImplRestartTimer(maList
[mnPos
]->mnWait
);
206 mbIsInAnimation
= true;
210 Draw(pOut
, rDestPt
, rDestSz
);
218 void Animation::Stop(const OutputDevice
* pOut
, tools::Long nExtraData
)
220 maViewList
.erase(std::remove_if(maViewList
.begin(), maViewList
.end(),
221 [=](const std::unique_ptr
<ImplAnimView
>& pAnimView
) -> bool {
222 return pAnimView
->matches(pOut
, nExtraData
);
226 if (maViewList
.empty())
229 mbIsInAnimation
= false;
233 void Animation::Draw(OutputDevice
* pOut
, const Point
& rDestPt
) const
235 Draw(pOut
, rDestPt
, pOut
->PixelToLogic(maGlobalSize
));
238 void Animation::Draw(OutputDevice
* pOut
, const Point
& rDestPt
, const Size
& rDestSz
) const
240 const size_t nCount
= maList
.size();
245 AnimationBitmap
* pObj
= maList
[std::min(mnPos
, nCount
- 1)].get();
247 if (pOut
->GetConnectMetaFile() || (pOut
->GetOutDevType() == OUTDEV_PRINTER
))
248 maList
[0]->maBitmapEx
.Draw(pOut
, rDestPt
, rDestSz
);
249 else if (ANIMATION_TIMEOUT_ON_CLICK
== pObj
->mnWait
)
250 pObj
->maBitmapEx
.Draw(pOut
, rDestPt
, rDestSz
);
253 const size_t nOldPos
= mnPos
;
254 if (mbLoopTerminated
)
255 const_cast<Animation
*>(this)->mnPos
= nCount
- 1;
258 ImplAnimView
{ const_cast<Animation
*>(this), pOut
, rDestPt
, rDestSz
, 0 };
261 const_cast<Animation
*>(this)->mnPos
= nOldPos
;
267 constexpr sal_uLong constMinTimeout
= 2;
270 void Animation::ImplRestartTimer(sal_uLong nTimeout
)
272 maTimer
.SetTimeout(std::max(nTimeout
, constMinTimeout
) * 10);
276 IMPL_LINK_NOARG(Animation
, ImplTimeoutHdl
, Timer
*, void)
278 const size_t nAnimCount
= maList
.size();
282 bool bGlobalPause
= false;
284 if (maNotifyLink
.IsSet())
286 std::vector
<std::unique_ptr
<AInfo
>> aAInfoList
;
288 for (auto const& i
: maViewList
)
289 aAInfoList
.emplace_back(i
->createAInfo());
291 maNotifyLink
.Call(this);
293 // set view state from AInfo structure
294 for (auto& pAInfo
: aAInfoList
)
296 ImplAnimView
* pView
= nullptr;
297 if (!pAInfo
->pViewData
)
299 pView
= new ImplAnimView(this, pAInfo
->pOutDev
, pAInfo
->aStartOrg
,
300 pAInfo
->aStartSize
, pAInfo
->nExtraData
);
302 maViewList
.push_back(std::unique_ptr
<ImplAnimView
>(pView
));
305 pView
= static_cast<ImplAnimView
*>(pAInfo
->pViewData
);
307 pView
->pause(pAInfo
->bPause
);
308 pView
->setMarked(true);
311 // delete all unmarked views
312 auto removeStart
= std::remove_if(maViewList
.begin(), maViewList
.end(),
313 [](const auto& pView
) { return !pView
->isMarked(); });
314 maViewList
.erase(removeStart
, maViewList
.cend());
316 // check if every remaining view is paused
317 bGlobalPause
= std::all_of(maViewList
.cbegin(), maViewList
.cend(),
318 [](const auto& pView
) { return pView
->isPause(); });
320 // reset marked state
321 std::for_each(maViewList
.cbegin(), maViewList
.cend(),
322 [](const auto& pView
) { pView
->setMarked(false); });
325 if (maViewList
.empty())
327 else if (bGlobalPause
)
328 ImplRestartTimer(10);
331 AnimationBitmap
* pStepBmp
= (++mnPos
< maList
.size()) ? maList
[mnPos
].get() : nullptr;
338 mbLoopTerminated
= true;
339 mnPos
= nAnimCount
- 1;
340 maBitmapEx
= maList
[mnPos
]->maBitmapEx
;
349 pStepBmp
= maList
[mnPos
].get();
354 std::for_each(maViewList
.cbegin(), maViewList
.cend(),
355 [this](const auto& pView
) { pView
->draw(mnPos
); });
357 * If a view is marked, remove the view, because
358 * area of output lies out of display area of window.
359 * Mark state is set from view itself.
361 auto removeStart
= std::remove_if(maViewList
.begin(), maViewList
.end(),
362 [](const auto& pView
) { return pView
->isMarked(); });
363 maViewList
.erase(removeStart
, maViewList
.cend());
365 // stop or restart timer
366 if (maViewList
.empty())
369 ImplRestartTimer(pStepBmp
->mnWait
);
376 bool Animation::Insert(const AnimationBitmap
& rStepBmp
)
380 if (!IsInAnimation())
382 tools::Rectangle
aGlobalRect(Point(), maGlobalSize
);
385 = aGlobalRect
.Union(tools::Rectangle(rStepBmp
.maPositionPixel
, rStepBmp
.maSizePixel
))
387 maList
.emplace_back(new AnimationBitmap(rStepBmp
));
389 // As a start, we make the first BitmapEx the replacement BitmapEx
390 if (maList
.size() == 1)
391 maBitmapEx
= rStepBmp
.maBitmapEx
;
399 const AnimationBitmap
& Animation::Get(sal_uInt16 nAnimation
) const
401 SAL_WARN_IF((nAnimation
>= maList
.size()), "vcl", "No object at this position");
402 return *maList
[nAnimation
];
405 void Animation::Replace(const AnimationBitmap
& rNewAnimationBitmap
, sal_uInt16 nAnimation
)
407 SAL_WARN_IF((nAnimation
>= maList
.size()), "vcl", "No object at this position");
409 maList
[nAnimation
].reset(new AnimationBitmap(rNewAnimationBitmap
));
411 // If we insert at first position we also need to
412 // update the replacement BitmapEx
413 if ((!nAnimation
&& (!mbLoopTerminated
|| (maList
.size() == 1)))
414 || ((nAnimation
== maList
.size() - 1) && mbLoopTerminated
))
416 maBitmapEx
= rNewAnimationBitmap
.maBitmapEx
;
420 void Animation::SetLoopCount(const sal_uInt32 nLoopCount
)
422 mnLoopCount
= nLoopCount
;
426 void Animation::ResetLoopCount()
428 mnLoops
= mnLoopCount
;
429 mbLoopTerminated
= false;
432 void Animation::Convert(BmpConversion eConversion
)
434 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
438 if (!IsInAnimation() && !maList
.empty())
442 for (size_t i
= 0, n
= maList
.size(); (i
< n
) && bRet
; ++i
)
443 bRet
= maList
[i
]->maBitmapEx
.Convert(eConversion
);
445 maBitmapEx
.Convert(eConversion
);
449 bool Animation::ReduceColors(sal_uInt16 nNewColorCount
)
451 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
455 if (!IsInAnimation() && !maList
.empty())
459 for (size_t i
= 0, n
= maList
.size(); (i
< n
) && bRet
; ++i
)
461 bRet
= BitmapFilter::Filter(maList
[i
]->maBitmapEx
,
462 BitmapColorQuantizationFilter(nNewColorCount
));
465 BitmapFilter::Filter(maBitmapEx
, BitmapColorQuantizationFilter(nNewColorCount
));
475 bool Animation::Invert()
477 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
481 if (!IsInAnimation() && !maList
.empty())
485 for (size_t i
= 0, n
= maList
.size(); (i
< n
) && bRet
; ++i
)
486 bRet
= maList
[i
]->maBitmapEx
.Invert();
496 void Animation::Mirror(BmpMirrorFlags nMirrorFlags
)
498 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
502 if (IsInAnimation() || maList
.empty())
507 if (nMirrorFlags
== BmpMirrorFlags::NONE
)
510 for (size_t i
= 0, n
= maList
.size(); (i
< n
) && bRet
; ++i
)
512 AnimationBitmap
* pStepBmp
= maList
[i
].get();
513 bRet
= pStepBmp
->maBitmapEx
.Mirror(nMirrorFlags
);
516 if (nMirrorFlags
& BmpMirrorFlags::Horizontal
)
517 pStepBmp
->maPositionPixel
.setX(maGlobalSize
.Width() - pStepBmp
->maPositionPixel
.X()
518 - pStepBmp
->maSizePixel
.Width());
520 if (nMirrorFlags
& BmpMirrorFlags::Vertical
)
521 pStepBmp
->maPositionPixel
.setY(maGlobalSize
.Height() - pStepBmp
->maPositionPixel
.Y()
522 - pStepBmp
->maSizePixel
.Height());
526 maBitmapEx
.Mirror(nMirrorFlags
);
529 void Animation::Adjust(short nLuminancePercent
, short nContrastPercent
, short nChannelRPercent
,
530 short nChannelGPercent
, short nChannelBPercent
, double fGamma
, bool bInvert
)
532 SAL_WARN_IF(IsInAnimation(), "vcl", "Animation modified while it is animated");
536 if (IsInAnimation() || maList
.empty())
541 for (size_t i
= 0, n
= maList
.size(); (i
< n
) && bRet
; ++i
)
543 bRet
= maList
[i
]->maBitmapEx
.Adjust(nLuminancePercent
, nContrastPercent
, nChannelRPercent
,
544 nChannelGPercent
, nChannelBPercent
, fGamma
, bInvert
);
547 maBitmapEx
.Adjust(nLuminancePercent
, nContrastPercent
, nChannelRPercent
, nChannelGPercent
,
548 nChannelBPercent
, fGamma
, bInvert
);
551 SvStream
& WriteAnimation(SvStream
& rOStm
, const Animation
& rAnimation
)
553 const sal_uInt16 nCount
= rAnimation
.Count();
557 const sal_uInt32 nDummy32
= 0;
559 // If no BitmapEx was set we write the first Bitmap of
561 if (!rAnimation
.GetBitmapEx().GetBitmap())
562 WriteDIBBitmapEx(rAnimation
.Get(0).maBitmapEx
, rOStm
);
564 WriteDIBBitmapEx(rAnimation
.GetBitmapEx(), rOStm
);
566 // Write identifier ( SDANIMA1 )
567 rOStm
.WriteUInt32(0x5344414e).WriteUInt32(0x494d4931);
569 for (sal_uInt16 i
= 0; i
< nCount
; i
++)
571 const AnimationBitmap
& rAnimationBitmap
= rAnimation
.Get(i
);
572 const sal_uInt16 nRest
= nCount
- i
- 1;
574 // Write AnimationBitmap
575 WriteDIBBitmapEx(rAnimationBitmap
.maBitmapEx
, rOStm
);
576 tools::GenericTypeSerializer
aSerializer(rOStm
);
577 aSerializer
.writePoint(rAnimationBitmap
.maPositionPixel
);
578 aSerializer
.writeSize(rAnimationBitmap
.maSizePixel
);
579 aSerializer
.writeSize(rAnimation
.maGlobalSize
);
580 rOStm
.WriteUInt16((ANIMATION_TIMEOUT_ON_CLICK
== rAnimationBitmap
.mnWait
)
582 : rAnimationBitmap
.mnWait
);
583 rOStm
.WriteUInt16(static_cast<sal_uInt16
>(rAnimationBitmap
.meDisposal
));
584 rOStm
.WriteBool(rAnimationBitmap
.mbUserInput
);
585 rOStm
.WriteUInt32(rAnimation
.mnLoopCount
);
586 rOStm
.WriteUInt32(nDummy32
); // Unused
587 rOStm
.WriteUInt32(nDummy32
); // Unused
588 rOStm
.WriteUInt32(nDummy32
); // Unused
589 write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm
, OString()); // dummy
590 rOStm
.WriteUInt16(nRest
); // Count of remaining structures
597 SvStream
& ReadAnimation(SvStream
& rIStm
, Animation
& rAnimation
)
600 sal_uInt32 nAnimMagic1
, nAnimMagic2
;
601 SvStreamEndian nOldFormat
= rIStm
.GetEndian();
602 bool bReadAnimations
= false;
604 rIStm
.SetEndian(SvStreamEndian::LITTLE
);
605 nStmPos
= rIStm
.Tell();
606 rIStm
.ReadUInt32(nAnimMagic1
).ReadUInt32(nAnimMagic2
);
610 // If the BitmapEx at the beginning have already been read (by Graphic)
611 // we can start reading the AnimationBitmaps right away
612 if ((nAnimMagic1
== 0x5344414e) && (nAnimMagic2
== 0x494d4931) && !rIStm
.GetError())
613 bReadAnimations
= true;
614 // Else, we try reading the Bitmap(-Ex)
618 ReadDIBBitmapEx(rAnimation
.maBitmapEx
, rIStm
);
619 nStmPos
= rIStm
.Tell();
620 rIStm
.ReadUInt32(nAnimMagic1
).ReadUInt32(nAnimMagic2
);
622 if ((nAnimMagic1
== 0x5344414e) && (nAnimMagic2
== 0x494d4931) && !rIStm
.GetError())
623 bReadAnimations
= true;
628 // Read AnimationBitmaps
631 AnimationBitmap aAnimationBitmap
;
638 ReadDIBBitmapEx(aAnimationBitmap
.maBitmapEx
, rIStm
);
639 tools::GenericTypeSerializer
aSerializer(rIStm
);
640 aSerializer
.readPoint(aAnimationBitmap
.maPositionPixel
);
641 aSerializer
.readSize(aAnimationBitmap
.maSizePixel
);
642 aSerializer
.readSize(rAnimation
.maGlobalSize
);
643 rIStm
.ReadUInt16(nTmp16
);
644 aAnimationBitmap
.mnWait
= ((65535 == nTmp16
) ? ANIMATION_TIMEOUT_ON_CLICK
: nTmp16
);
645 rIStm
.ReadUInt16(nTmp16
);
646 aAnimationBitmap
.meDisposal
= static_cast<Disposal
>(nTmp16
);
647 rIStm
.ReadCharAsBool(cTmp
);
648 aAnimationBitmap
.mbUserInput
= cTmp
;
649 rIStm
.ReadUInt32(rAnimation
.mnLoopCount
);
650 rIStm
.ReadUInt32(nTmp32
); // Unused
651 rIStm
.ReadUInt32(nTmp32
); // Unused
652 rIStm
.ReadUInt32(nTmp32
); // Unused
653 read_uInt16_lenPrefixed_uInt8s_ToOString(rIStm
); // Unused
654 rIStm
.ReadUInt16(nTmp16
); // The rest to read
656 rAnimation
.Insert(aAnimationBitmap
);
657 } while (nTmp16
&& !rIStm
.GetError());
659 rAnimation
.ResetLoopCount();
662 rIStm
.SetEndian(nOldFormat
);
675 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */