/* -*- 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 <tools/stream.hxx>
#include <rtl/crc.h>
#include <sal/log.hxx>
#include <vcl/animate.hxx>
#include <vcl/virdev.hxx>
#include <vcl/window.hxx>
#include <vcl/dibtools.hxx>
#include <vcl/BitmapColorQuantizationFilter.hxx>
#include <impanmvw.hxx>
#define MIN_TIMEOUT 2
sal_uLong Animation::mnAnimCount = 0;
BitmapChecksum AnimationBitmap::GetChecksum() const
{
BitmapChecksum nCrc = aBmpEx.GetChecksum();
SVBT32 aBT32;
UInt32ToSVBT32( aPosPix.X(), aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
UInt32ToSVBT32( aPosPix.Y(), aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
UInt32ToSVBT32( aSizePix.Width(), aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
UInt32ToSVBT32( aSizePix.Height(), aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
UInt32ToSVBT32( nWait, aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
UInt32ToSVBT32( static_cast<long>(eDisposal), aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
UInt32ToSVBT32( static_cast<long>(bUserInput), aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
return nCrc;
}
Animation::Animation() :
mnLoopCount ( 0 ),
mnLoops ( 0 ),
mnPos ( 0 ),
mbIsInAnimation ( false ),
mbLoopTerminated ( false )
{
maTimer.SetInvokeHandler( LINK( this, Animation, ImplTimeoutHdl ) );
}
Animation::Animation( const Animation& rAnimation ) :
maBitmapEx ( rAnimation.maBitmapEx ),
maGlobalSize ( rAnimation.maGlobalSize ),
mnLoopCount ( rAnimation.mnLoopCount ),
mnPos ( rAnimation.mnPos ),
mbIsInAnimation ( false ),
mbLoopTerminated ( rAnimation.mbLoopTerminated )
{
for(auto const & i : rAnimation.maList)
maList.emplace_back( new AnimationBitmap( *i ) );
maTimer.SetInvokeHandler( LINK( this, Animation, ImplTimeoutHdl ) );
mnLoops = mbLoopTerminated ? 0 : mnLoopCount;
}
Animation::~Animation()
{
if( mbIsInAnimation )
Stop();
}
Animation& Animation::operator=( const Animation& rAnimation )
{
Clear();
for(auto const & i : rAnimation.maList)
maList.emplace_back( new AnimationBitmap( *i ) );
maGlobalSize = rAnimation.maGlobalSize;
maBitmapEx = rAnimation.maBitmapEx;
mnLoopCount = rAnimation.mnLoopCount;
mnPos = rAnimation.mnPos;
mbLoopTerminated = rAnimation.mbLoopTerminated;
mnLoops = mbLoopTerminated ? 0 : mnLoopCount;
return *this;
}
bool Animation::operator==( const Animation& rAnimation ) const
{
const size_t nCount = maList.size();
bool bRet = false;
if( rAnimation.maList.size() == nCount
&& rAnimation.maBitmapEx == maBitmapEx
&& rAnimation.maGlobalSize == maGlobalSize
)
{
bRet = true;
for( size_t n = 0; n < nCount; n++ )
{
if( ( *maList[ n ] ) != ( *rAnimation.maList[ n ] ) )
{
bRet = false;
break;
}
}
}
return bRet;
}
void Animation::Clear()
{
maTimer.Stop();
mbIsInAnimation = false;
maGlobalSize = Size();
maBitmapEx.SetEmpty();
maList.clear();
maViewList.clear();
}
bool Animation::IsTransparent() const
{
tools::Rectangle aRect( Point(), maGlobalSize );
bool bRet = false;
// 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.
for(auto const & pAnimBmp : maList)
{
if( Disposal::Back == pAnimBmp->eDisposal
&& tools::Rectangle( pAnimBmp->aPosPix, pAnimBmp->aSizePix ) != aRect
)
{
bRet = true;
break;
}
}
if( !bRet )
bRet = maBitmapEx.IsTransparent();
return bRet;
}
sal_uLong Animation::GetSizeBytes() const
{
sal_uLong nSizeBytes = GetBitmapEx().GetSizeBytes();
for(auto const & pAnimBmp : maList)
{
nSizeBytes += pAnimBmp->aBmpEx.GetSizeBytes();
}
return nSizeBytes;
}
BitmapChecksum Animation::GetChecksum() const
{
SVBT32 aBT32;
BitmapChecksumOctetArray aBCOA;
BitmapChecksum nCrc = GetBitmapEx().GetChecksum();
UInt32ToSVBT32( maList.size(), aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
UInt32ToSVBT32( maGlobalSize.Width(), aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
UInt32ToSVBT32( maGlobalSize.Height(), aBT32 );
nCrc = vcl_get_checksum( nCrc, aBT32, 4 );
for(auto const & i : maList)
{
BCToBCOA( i->GetChecksum(), aBCOA );
nCrc = vcl_get_checksum( nCrc, aBCOA, BITMAP_CHECKSUM_SIZE );
}
return nCrc;
}
bool Animation::Start( OutputDevice* pOut, const Point& rDestPt, const Size& rDestSz, long nExtraData,
OutputDevice* pFirstFrameOutDev )
{
bool bRet = false;
if( !maList.empty() )
{
if( ( pOut->GetOutDevType() == OUTDEV_WINDOW )
&& !mbLoopTerminated
&& ( ANIMATION_TIMEOUT_ON_CLICK != maList[ mnPos ]->nWait )
)
{
ImplAnimView* pView;
ImplAnimView* pMatch = nullptr;
for( size_t i = 0; i < maViewList.size(); ++i )
{
pView = maViewList[ i ].get();
if( pView->matches( pOut, nExtraData ) )
{
if( pView->getOutPos() == rDestPt &&
pView->getOutSizePix() == pOut->LogicToPixel( rDestSz ) )
{
pView->repaint();
pMatch = pView;
}
else
{
maViewList.erase( maViewList.begin() + i );
pView = nullptr;
}
break;
}
}
if( maViewList.empty() )
{
maTimer.Stop();
mbIsInAnimation = false;
mnPos = 0;
}
if( !pMatch )
maViewList.emplace_back( new ImplAnimView( this, pOut, rDestPt, rDestSz, nExtraData, pFirstFrameOutDev ) );
if( !mbIsInAnimation )
{
ImplRestartTimer( maList[ mnPos ]->nWait );
mbIsInAnimation = true;
}
}
else
Draw( pOut, rDestPt, rDestSz );
bRet = true;
}
return bRet;
}
void Animation::Stop( OutputDevice* pOut, long nExtraData )
{
for( size_t i = 0; i < maViewList.size(); )
{
ImplAnimView* pView = maViewList[ i ].get();
if( pView->matches( pOut, nExtraData ) )
{
maViewList.erase( maViewList.begin() + i );
}
else
i++;
}
if( maViewList.empty() )
{
maTimer.Stop();
mbIsInAnimation = false;
}
}
void Animation::Draw( OutputDevice* pOut, const Point& rDestPt ) const
{
Draw( pOut, rDestPt, pOut->PixelToLogic( maGlobalSize ) );
}
void Animation::Draw( OutputDevice* pOut, const Point& rDestPt, const Size& rDestSz ) const
{
const size_t nCount = maList.size();
if( nCount )
{
AnimationBitmap* pObj = maList[ std::min( mnPos, nCount - 1 ) ].get();
if( pOut->GetConnectMetaFile()
|| ( pOut->GetOutDevType() == OUTDEV_PRINTER )
)
maList[ 0 ]->aBmpEx.Draw( pOut, rDestPt, rDestSz );
else if( ANIMATION_TIMEOUT_ON_CLICK == pObj->nWait )
pObj->aBmpEx.Draw( pOut, rDestPt, rDestSz );
else
{
const size_t nOldPos = mnPos;
const_cast<Animation*>(this)->mnPos = mbLoopTerminated ? ( nCount - 1 ) : mnPos;
delete new ImplAnimView( const_cast<Animation*>(this), pOut, rDestPt, rDestSz, 0 );
const_cast<Animation*>(this)->mnPos = nOldPos;
}
}
}
void Animation::ImplRestartTimer( sal_uLong nTimeout )
{
maTimer.SetTimeout( std::max( nTimeout, static_cast<sal_uLong>(MIN_TIMEOUT) ) * 10 );
maTimer.Start();
}
IMPL_LINK_NOARG(Animation, ImplTimeoutHdl, Timer *, void)
{
const size_t nAnimCount = maList.size();
if( nAnimCount )
{
ImplAnimView* pView;
bool bGlobalPause = true;
if( maNotifyLink.IsSet() )
{
std::vector< std::unique_ptr<AInfo> > aAInfoList;
// create AInfo-List
for(auto const & i : maViewList)
aAInfoList.emplace_back( i->createAInfo() );
maNotifyLink.Call( this );
// set view state from AInfo structure
for(auto& pAInfo : aAInfoList)
{
if( !pAInfo->pViewData )
{
pView = new ImplAnimView( this, pAInfo->pOutDev,
pAInfo->aStartOrg, pAInfo->aStartSize, pAInfo->nExtraData );
maViewList.push_back( std::unique_ptr<ImplAnimView>(pView) );
}
else
pView = static_cast<ImplAnimView*>(pAInfo->pViewData);
pView->pause( pAInfo->bPause );
pView->setMarked( true );
}
// delete all unmarked views and reset marked state
for( size_t i = 0; i < maViewList.size(); )
{
pView = maViewList[ i ].get();
if( !pView->isMarked() )
{
maViewList.erase( maViewList.begin() + i );
}
else
{
if( !pView->isPause() )
bGlobalPause = false;
pView->setMarked( false );
i++;
}
}
}
else
bGlobalPause = false;
if( maViewList.empty() )
Stop();
else if( bGlobalPause )
ImplRestartTimer( 10 );
else
{
AnimationBitmap* pStepBmp = (++mnPos < maList.size()) ? maList[ mnPos ].get() : nullptr;
if( !pStepBmp )
{
if( mnLoops == 1 )
{
Stop();
mbLoopTerminated = true;
mnPos = nAnimCount - 1;
maBitmapEx = maList[ mnPos ]->aBmpEx;
return;
}
else
{
if( mnLoops )
mnLoops--;
mnPos = 0;
pStepBmp = maList[ mnPos ].get();
}
}
// Paint all views; after painting check, if view is
// marked; in this case remove view, because area of output
// lies out of display area of window; mark state is
// set from view itself
for( size_t i = 0; i < maViewList.size(); )
{
pView = maViewList[ i ].get();
pView->draw( mnPos );
if( pView->isMarked() )
{
maViewList.erase( maViewList.begin() + i );
}
else
i++;
}
// stop or restart timer
if( maViewList.empty() )
Stop();
else
ImplRestartTimer( pStepBmp->nWait );
}
}
else
Stop();
}
bool Animation::Insert( const AnimationBitmap& rStepBmp )
{
bool bRet = false;
if( !IsInAnimation() )
{
tools::Rectangle aGlobalRect( Point(), maGlobalSize );
maGlobalSize = aGlobalRect.Union( tools::Rectangle( rStepBmp.aPosPix, rStepBmp.aSizePix ) ).GetSize();
maList.emplace_back( new AnimationBitmap( rStepBmp ) );
// As a start, we make the first BitmapEx the replacement BitmapEx
if( maList.size() == 1 )
maBitmapEx = rStepBmp.aBmpEx;
bRet = true;
}
return bRet;
}
const AnimationBitmap& Animation::Get( sal_uInt16 nAnimation ) const
{
SAL_WARN_IF( ( nAnimation >= maList.size() ), "vcl", "No object at this position" );
return *maList[ nAnimation ];
}
void Animation::Replace( const AnimationBitmap& rNewAnimationBitmap, sal_uInt16 nAnimation )
{
SAL_WARN_IF( ( nAnimation >= maList.size() ), "vcl", "No object at this position" );
maList[ nAnimation ].reset( new AnimationBitmap( rNewAnimationBitmap ) );
// If we insert at first position we also need to
// update the replacement BitmapEx
if ( ( !nAnimation
&& ( !mbLoopTerminated
|| ( maList.size() == 1 )
)
)
||
( ( nAnimation == maList.size() - 1 )
&& mbLoopTerminated
)
)
{
maBitmapEx = rNewAnimationBitmap.aBmpEx;
}
}
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() && !maList.empty() )
{
bRet = true;
for( size_t i = 0, n = maList.size(); ( i < n ) && bRet; ++i )
bRet = maList[ i ]->aBmpEx.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() && !maList.empty() )
{
bRet = true;
for (size_t i = 0, n = maList.size(); (i < n) && bRet; ++i)
{
bRet = BitmapFilter::Filter(maList[i]->aBmpEx, 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() && !maList.empty() )
{
bRet = true;
for( size_t i = 0, n = maList.size(); ( i < n ) && bRet; ++i )
bRet = maList[ i ]->aBmpEx.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() && !maList.empty() )
{
bRet = true;
if( nMirrorFlags != BmpMirrorFlags::NONE )
{
for( size_t i = 0, n = maList.size(); ( i < n ) && bRet; ++i )
{
AnimationBitmap* pStepBmp = maList[ i ].get();
bRet = pStepBmp->aBmpEx.Mirror( nMirrorFlags );
if( bRet )
{
if( nMirrorFlags & BmpMirrorFlags::Horizontal )
pStepBmp->aPosPix.setX( maGlobalSize.Width() - pStepBmp->aPosPix.X() - pStepBmp->aSizePix.Width() );
if( nMirrorFlags & BmpMirrorFlags::Vertical )
pStepBmp->aPosPix.setY( maGlobalSize.Height() - pStepBmp->aPosPix.Y() - pStepBmp->aSizePix.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() && !maList.empty() )
{
bRet = true;
for( size_t i = 0, n = maList.size(); ( i < n ) && bRet; ++i )
{
bRet = maList[ i ]->aBmpEx.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() )
WriteDIBBitmapEx(rAnimation.Get( 0 ).aBmpEx, rOStm);
else
WriteDIBBitmapEx(rAnimation.GetBitmapEx(), rOStm);
// Write identifier ( SDANIMA1 )
rOStm.WriteUInt32( 0x5344414e ).WriteUInt32( 0x494d4931 );
for( sal_uInt16 i = 0; i < nCount; i++ )
{
const AnimationBitmap& rAnimBmp = rAnimation.Get( i );
const sal_uInt16 nRest = nCount - i - 1;
// Write AnimationBitmap
WriteDIBBitmapEx(rAnimBmp.aBmpEx, rOStm);
WritePair( rOStm, rAnimBmp.aPosPix );
WritePair( rOStm, rAnimBmp.aSizePix );
WritePair( rOStm, rAnimation.maGlobalSize );
rOStm.WriteUInt16( ( ANIMATION_TIMEOUT_ON_CLICK == rAnimBmp.nWait ) ? 65535 : rAnimBmp.nWait );
rOStm.WriteUInt16( static_cast<sal_uInt16>(rAnimBmp.eDisposal) );
rOStm.WriteBool( rAnimBmp.bUserInput );
rOStm.WriteUInt32( rAnimation.mnLoopCount );
rOStm.WriteUInt32( nDummy32 ); // Unused
rOStm.WriteUInt32( nDummy32 ); // Unused
rOStm.WriteUInt32( nDummy32 ); // Unused
write_uInt16_lenPrefixed_uInt8s_FromOString(rOStm, OString()); // 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 AnimationBitmaps 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 AnimationBitmaps
if( bReadAnimations )
{
AnimationBitmap aAnimBmp;
sal_uInt32 nTmp32;
sal_uInt16 nTmp16;
bool cTmp;
do
{
ReadDIBBitmapEx(aAnimBmp.aBmpEx, rIStm);
ReadPair( rIStm, aAnimBmp.aPosPix );
ReadPair( rIStm, aAnimBmp.aSizePix );
ReadPair( rIStm, rAnimation.maGlobalSize );
rIStm.ReadUInt16( nTmp16 ); aAnimBmp.nWait = ( ( 65535 == nTmp16 ) ? ANIMATION_TIMEOUT_ON_CLICK : nTmp16 );
rIStm.ReadUInt16( nTmp16 ); aAnimBmp.eDisposal = static_cast<Disposal>(nTmp16);
rIStm.ReadCharAsBool( cTmp ); aAnimBmp.bUserInput = 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( aAnimBmp );
}
while( nTmp16 && !rIStm.GetError() );
rAnimation.ResetLoopCount();
}
rIStm.SetEndian( nOldFormat );
return rIStm;
}
AInfo::AInfo() : pOutDev( nullptr ),
pViewData( nullptr ),
nExtraData( 0 ),
bPause( false ) {}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V1023 A pointer without owner is added to the 'maList' container by the 'emplace_back' method. A memory leak will occur in case of an exception.
↑ V1023 A pointer without owner is added to the 'maList' container by the 'emplace_back' method. A memory leak will occur in case of an exception.
↑ V794 The assignment operator should be protected from the case of 'this == &rAnimation'.
↑ V1023 A pointer without owner is added to the 'maViewList' container by the 'emplace_back' method. A memory leak will occur in case of an exception.
↑ V1023 A pointer without owner is added to the 'maList' container by the 'emplace_back' method. A memory leak will occur in case of an exception.