diff options
Diffstat (limited to 'vcl/source/filter/igif/gifread.cxx')
-rw-r--r-- | vcl/source/filter/igif/gifread.cxx | 1002 |
1 files changed, 1002 insertions, 0 deletions
diff --git a/vcl/source/filter/igif/gifread.cxx b/vcl/source/filter/igif/gifread.cxx new file mode 100644 index 000000000..fa1270e6a --- /dev/null +++ b/vcl/source/filter/igif/gifread.cxx @@ -0,0 +1,1002 @@ +/* -*- 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/log.hxx> +#include <tools/stream.hxx> +#include "decode.hxx" +#include "gifread.hxx" +#include <memory> +#include <bitmap/BitmapWriteAccess.hxx> +#include <graphic/GraphicReader.hxx> + +#define NO_PENDING( rStm ) ( ( rStm ).GetError() != ERRCODE_IO_PENDING ) + +namespace { + +enum GIFAction +{ + GLOBAL_HEADER_READING, + MARKER_READING, + EXTENSION_READING, + LOCAL_HEADER_READING, + FIRST_BLOCK_READING, + NEXT_BLOCK_READING, + ABORT_READING, + END_READING +}; + +enum ReadState +{ + GIFREAD_OK, + GIFREAD_ERROR, + GIFREAD_NEED_MORE +}; + +} + +class GIFLZWDecompressor; + +class SvStream; + +namespace { + +class GIFReader : public GraphicReader +{ + Animation aAnimation; + sal_uInt64 nAnimationByteSize; + sal_uInt64 nAnimationMinFileData; + Bitmap aBmp8; + Bitmap aBmp1; + BitmapPalette aGPalette; + BitmapPalette aLPalette; + SvStream& rIStm; + std::vector<sal_uInt8> aSrcBuf; + std::unique_ptr<GIFLZWDecompressor> pDecomp; + BitmapScopedWriteAccess pAcc8; + BitmapScopedWriteAccess pAcc1; + tools::Long nYAcc; + tools::Long nLastPos; + sal_uInt64 nMaxStreamData; + sal_uInt32 nLogWidth100; + sal_uInt32 nLogHeight100; + sal_uInt16 nTimer; + sal_uInt16 nGlobalWidth; // maximum imagewidth from header + sal_uInt16 nGlobalHeight; // maximum imageheight from header + sal_uInt16 nImageWidth; // maximum screenwidth from header + sal_uInt16 nImageHeight; // maximum screenheight from header + sal_uInt16 nImagePosX; + sal_uInt16 nImagePosY; + sal_uInt16 nImageX; // maximum screenwidth from header + sal_uInt16 nImageY; // maximum screenheight from header + sal_uInt16 nLastImageY; + sal_uInt16 nLastInterCount; + sal_uInt16 nLoops; + GIFAction eActAction; + bool bStatus; + bool bGCTransparent; // is the image transparent, if yes: + bool bInterlaced; + bool bOverreadBlock; + bool bImGraphicReady; + bool bGlobalPalette; + sal_uInt8 nBackgroundColor; // backgroundcolour + sal_uInt8 nGCTransparentIndex; // pixels of this index are transparent + sal_uInt8 nGCDisposalMethod; // 'Disposal Method' (see GIF docs) + sal_uInt8 cTransIndex1; + sal_uInt8 cNonTransIndex1; + + void ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount ); + void ClearImageExtensions(); + void CreateBitmaps( tools::Long nWidth, tools::Long nHeight, BitmapPalette* pPal, bool bWatchForBackgroundColor ); + bool ReadGlobalHeader(); + bool ReadExtension(); + bool ReadLocalHeader(); + sal_uLong ReadNextBlock(); + void FillImages( const sal_uInt8* pBytes, sal_uLong nCount ); + void CreateNewBitmaps(); + bool ProcessGIF(); + +public: + + ReadState ReadGIF( Graphic& rGraphic ); + bool ReadIsAnimated(); + void GetLogicSize(Size& rLogicSize); + Graphic GetIntermediateGraphic(); + + explicit GIFReader( SvStream& rStm ); +}; + +} + +GIFReader::GIFReader( SvStream& rStm ) + : nAnimationByteSize(0) + , nAnimationMinFileData(0) + , aGPalette ( 256 ) + , aLPalette ( 256 ) + , rIStm ( rStm ) + , nYAcc ( 0 ) + , nLastPos ( rStm.Tell() ) + , nMaxStreamData( rStm.remainingSize() ) + , nLogWidth100 ( 0 ) + , nLogHeight100 ( 0 ) + , nGlobalWidth ( 0 ) + , nGlobalHeight ( 0 ) + , nImageWidth ( 0 ) + , nImageHeight ( 0 ) + , nImagePosX ( 0 ) + , nImagePosY ( 0 ) + , nImageX ( 0 ) + , nImageY ( 0 ) + , nLastImageY ( 0 ) + , nLastInterCount ( 0 ) + , nLoops ( 1 ) + , eActAction ( GLOBAL_HEADER_READING ) + , bStatus ( false ) + , bGCTransparent ( false ) + , bInterlaced ( false) + , bOverreadBlock ( false ) + , bImGraphicReady ( false ) + , bGlobalPalette ( false ) + , nBackgroundColor ( 0 ) + , nGCTransparentIndex ( 0 ) + , cTransIndex1 ( 0 ) + , cNonTransIndex1 ( 0 ) +{ + maUpperName = "SVIGIF"; + aSrcBuf.resize(256); // Memory buffer for ReadNextBlock + ClearImageExtensions(); +} + +void GIFReader::ClearImageExtensions() +{ + nGCDisposalMethod = 0; + bGCTransparent = false; + nTimer = 0; +} + +void GIFReader::CreateBitmaps(tools::Long nWidth, tools::Long nHeight, BitmapPalette* pPal, + bool bWatchForBackgroundColor) +{ + const Size aSize(nWidth, nHeight); + + sal_uInt64 nCombinedPixSize = nWidth * nHeight; + if (bGCTransparent) + nCombinedPixSize += (nCombinedPixSize/8); + + // "Overall data compression asymptotically approaches 3839 × 8 / 12 = 2559 1/3" + // so assume compression of 1:2560 is possible + // (http://cloudinary.com/blog/a_one_color_image_is_worth_two_thousand_words suggests + // 1:1472.88 [184.11 x 8] is more realistic) + + sal_uInt64 nMinFileData = nWidth * nHeight / 2560; + + nMinFileData += nAnimationMinFileData; + nCombinedPixSize += nAnimationByteSize; + + if (nMaxStreamData < nMinFileData) + { + //there is nowhere near enough data in this stream to fill the claimed dimensions + SAL_WARN("vcl.filter", "in gif frame index " << aAnimation.Count() << " gif claims dimensions " << nWidth << " x " << nHeight << + " but filesize of " << nMaxStreamData << " is surely insufficiently large to fill all frame images"); + bStatus = false; + return; + } + + // Don't bother allocating a bitmap of a size that would fail on a + // 32-bit system. We have at least one unit tests that is expected + // to fail (loading a 65535*65535 size GIF + // svtools/qa/cppunit/data/gif/fail/CVE-2008-5937-1.gif), but + // which doesn't fail on 64-bit macOS at least. Why the loading + // fails on 64-bit Linux, no idea. + if (nCombinedPixSize >= SAL_MAX_INT32/3*2) + { + bStatus = false; + return; + } + + if (!aSize.Width() || !aSize.Height()) + { + bStatus = false; + return; + } + + if (bGCTransparent) + { + const Color aWhite(COL_WHITE); + + aBmp1 = Bitmap(aSize, vcl::PixelFormat::N1_BPP); + + if (!aAnimation.Count()) + aBmp1.Erase(aWhite); + + pAcc1 = BitmapScopedWriteAccess(aBmp1); + + if (pAcc1) + { + cTransIndex1 = static_cast<sal_uInt8>(pAcc1->GetBestPaletteIndex(aWhite)); + cNonTransIndex1 = cTransIndex1 ? 0 : 1; + } + else + { + bStatus = false; + } + } + + if (bStatus) + { + aBmp8 = Bitmap(aSize, vcl::PixelFormat::N8_BPP, pPal); + + if (!aBmp8.IsEmpty() && bWatchForBackgroundColor && aAnimation.Count()) + aBmp8.Erase((*pPal)[nBackgroundColor]); + else + aBmp8.Erase(COL_WHITE); + + pAcc8 = BitmapScopedWriteAccess(aBmp8); + bStatus = bool(pAcc8); + } +} + +bool GIFReader::ReadGlobalHeader() +{ + char pBuf[ 7 ]; + bool bRet = false; + + auto nRead = rIStm.ReadBytes(pBuf, 6); + if (nRead == 6 && NO_PENDING(rIStm)) + { + pBuf[ 6 ] = 0; + if( !strcmp( pBuf, "GIF87a" ) || !strcmp( pBuf, "GIF89a" ) ) + { + nRead = rIStm.ReadBytes(pBuf, 7); + if (nRead == 7 && NO_PENDING(rIStm)) + { + sal_uInt8 nAspect; + sal_uInt8 nRF; + SvMemoryStream aMemStm; + + aMemStm.SetBuffer( pBuf, 7, 7 ); + aMemStm.ReadUInt16( nGlobalWidth ); + aMemStm.ReadUInt16( nGlobalHeight ); + aMemStm.ReadUChar( nRF ); + aMemStm.ReadUChar( nBackgroundColor ); + aMemStm.ReadUChar( nAspect ); + + bGlobalPalette = ( nRF & 0x80 ); + + if( bGlobalPalette ) + ReadPaletteEntries( &aGPalette, sal_uLong(1) << ( ( nRF & 7 ) + 1 ) ); + else + nBackgroundColor = 0; + + if( NO_PENDING( rIStm ) ) + bRet = true; + } + } + else + bStatus = false; + } + + return bRet; +} + +void GIFReader::ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount ) +{ + sal_uLong nLen = 3 * nCount; + const sal_uInt64 nMaxPossible = rIStm.remainingSize(); + if (nLen > nMaxPossible) + nLen = nMaxPossible; + std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[ nLen ]); + std::size_t nRead = rIStm.ReadBytes(pBuf.get(), nLen); + nCount = nRead/3UL; + if( !(NO_PENDING( rIStm )) ) + return; + + sal_uInt8* pTmp = pBuf.get(); + + for (sal_uLong i = 0; i < nCount; ++i) + { + BitmapColor& rColor = (*pPal)[i]; + + rColor.SetRed( *pTmp++ ); + rColor.SetGreen( *pTmp++ ); + rColor.SetBlue( *pTmp++ ); + } + + // if possible accommodate some standard colours + if( nCount < 256 ) + { + (*pPal)[ 255UL ] = COL_WHITE; + + if( nCount < 255 ) + (*pPal)[ 254UL ] = COL_BLACK; + } +} + +bool GIFReader::ReadExtension() +{ + bool bRet = false; + + // Extension-Label + sal_uInt8 cFunction(0); + rIStm.ReadUChar( cFunction ); + if( NO_PENDING( rIStm ) ) + { + bool bOverreadDataBlocks = false; + sal_uInt8 cSize(0); + // Block length + rIStm.ReadUChar( cSize ); + switch( cFunction ) + { + // 'Graphic Control Extension' + case 0xf9 : + { + sal_uInt8 cFlags(0); + rIStm.ReadUChar(cFlags); + rIStm.ReadUInt16(nTimer); + rIStm.ReadUChar(nGCTransparentIndex); + sal_uInt8 cByte(0); + rIStm.ReadUChar(cByte); + + if ( NO_PENDING( rIStm ) ) + { + nGCDisposalMethod = ( cFlags >> 2) & 7; + bGCTransparent = ( cFlags & 1 ); + bStatus = ( cSize == 4 ) && ( cByte == 0 ); + bRet = true; + } + } + break; + + // Application extension + case 0xff : + { + if ( NO_PENDING( rIStm ) ) + { + // by default overread this extension + bOverreadDataBlocks = true; + + // Appl. extension has length 11 + if ( cSize == 0x0b ) + { + OString aAppId = read_uInt8s_ToOString(rIStm, 8); + OString aAppCode = read_uInt8s_ToOString(rIStm, 3); + rIStm.ReadUChar( cSize ); + + // NetScape-Extension + if( aAppId == "NETSCAPE" && aAppCode == "2.0" && cSize == 3 ) + { + sal_uInt8 cByte(0); + rIStm.ReadUChar( cByte ); + + // Loop-Extension + if ( cByte == 0x01 ) + { + rIStm.ReadUChar( cByte ); + nLoops = cByte; + rIStm.ReadUChar( cByte ); + nLoops |= ( static_cast<sal_uInt16>(cByte) << 8 ); + rIStm.ReadUChar( cByte ); + + bStatus = ( cByte == 0 ); + bRet = NO_PENDING( rIStm ); + bOverreadDataBlocks = false; + + // Netscape interprets the loop count + // as pure number of _repeats_; + // here it is the total number of loops + if( nLoops ) + nLoops++; + } + else + rIStm.SeekRel( -1 ); + } + else if ( aAppId == "STARDIV " && aAppCode == "5.0" && cSize == 9 ) + { + sal_uInt8 cByte(0); + rIStm.ReadUChar( cByte ); + + // Loop extension + if ( cByte == 0x01 ) + { + rIStm.ReadUInt32( nLogWidth100 ).ReadUInt32( nLogHeight100 ); + rIStm.ReadUChar( cByte ); + bStatus = ( cByte == 0 ); + bRet = NO_PENDING( rIStm ); + bOverreadDataBlocks = false; + } + else + rIStm.SeekRel( -1 ); + } + + } + } + } + break; + + // overread everything else + default: + bOverreadDataBlocks = true; + break; + } + + // overread sub-blocks + if ( bOverreadDataBlocks ) + { + bRet = true; + while( cSize && bStatus && !rIStm.eof() ) + { + sal_uInt16 nCount = static_cast<sal_uInt16>(cSize) + 1; + const sal_uInt64 nMaxPossible = rIStm.remainingSize(); + if (nCount > nMaxPossible) + nCount = nMaxPossible; + + if (nCount) + rIStm.SeekRel( nCount - 1 ); // Skip subblock data + + bRet = false; + std::size_t nRead = rIStm.ReadBytes(&cSize, 1); + if (NO_PENDING(rIStm) && nRead == 1) + { + bRet = true; + } + else + cSize = 0; + } + } + } + + return bRet; +} + +bool GIFReader::ReadLocalHeader() +{ + sal_uInt8 pBuf[ 9 ]; + bool bRet = false; + + std::size_t nRead = rIStm.ReadBytes(pBuf, 9); + if (NO_PENDING(rIStm) && nRead == 9) + { + SvMemoryStream aMemStm; + BitmapPalette* pPal; + + aMemStm.SetBuffer( pBuf, 9, 9 ); + aMemStm.ReadUInt16( nImagePosX ); + aMemStm.ReadUInt16( nImagePosY ); + aMemStm.ReadUInt16( nImageWidth ); + aMemStm.ReadUInt16( nImageHeight ); + sal_uInt8 nFlags(0); + aMemStm.ReadUChar(nFlags); + + // if interlaced, first define startvalue + bInterlaced = ( ( nFlags & 0x40 ) == 0x40 ); + nLastInterCount = 7; + nLastImageY = 0; + + if( nFlags & 0x80 ) + { + pPal = &aLPalette; + ReadPaletteEntries( pPal, sal_uLong(1) << ( (nFlags & 7 ) + 1 ) ); + } + else + pPal = &aGPalette; + + // if we could read everything, we will create the local image; + // if the global colour table is valid for the image, we will + // consider the BackGroundColorIndex. + if( NO_PENDING( rIStm ) ) + { + CreateBitmaps( nImageWidth, nImageHeight, pPal, bGlobalPalette && ( pPal == &aGPalette ) ); + bRet = true; + } + } + + return bRet; +} + +sal_uLong GIFReader::ReadNextBlock() +{ + sal_uLong nRet = 0; + sal_uInt8 cBlockSize; + + rIStm.ReadUChar( cBlockSize ); + + if ( rIStm.eof() ) + nRet = 4; + else if ( NO_PENDING( rIStm ) ) + { + if ( cBlockSize == 0 ) + nRet = 2; + else + { + rIStm.ReadBytes( aSrcBuf.data(), cBlockSize ); + + if( NO_PENDING( rIStm ) ) + { + if( bOverreadBlock ) + nRet = 3; + else + { + bool bEOI; + sal_uLong nRead; + sal_uInt8* pTarget = pDecomp->DecompressBlock( aSrcBuf.data(), cBlockSize, nRead, bEOI ); + + nRet = ( bEOI ? 3 : 1 ); + + if( nRead && !bOverreadBlock ) + FillImages( pTarget, nRead ); + + std::free( pTarget ); + } + } + } + } + + return nRet; +} + +void GIFReader::FillImages( const sal_uInt8* pBytes, sal_uLong nCount ) +{ + for( sal_uLong i = 0; i < nCount; i++ ) + { + if( nImageX >= nImageWidth ) + { + if( bInterlaced ) + { + tools::Long nT1; + + // lines will be copied if interlaced + if( nLastInterCount ) + { + tools::Long nMinY = std::min( static_cast<tools::Long>(nLastImageY) + 1, static_cast<tools::Long>(nImageHeight) - 1 ); + tools::Long nMaxY = std::min( static_cast<tools::Long>(nLastImageY) + nLastInterCount, static_cast<tools::Long>(nImageHeight) - 1 ); + + // copy last line read, if lines do not coincide + // ( happens at the end of the image ) + if( ( nMinY > nLastImageY ) && ( nLastImageY < ( nImageHeight - 1 ) ) ) + { + sal_uInt8* pScanline8 = pAcc8->GetScanline( nYAcc ); + sal_uInt32 nSize8 = pAcc8->GetScanlineSize(); + sal_uInt8* pScanline1 = nullptr; + sal_uInt32 nSize1 = 0; + + if( bGCTransparent ) + { + pScanline1 = pAcc1->GetScanline( nYAcc ); + nSize1 = pAcc1->GetScanlineSize(); + } + + for( tools::Long j = nMinY; j <= nMaxY; j++ ) + { + memcpy( pAcc8->GetScanline( j ), pScanline8, nSize8 ); + + if( bGCTransparent ) + memcpy( pAcc1->GetScanline( j ), pScanline1, nSize1 ); + } + } + } + + nT1 = ( ++nImageY ) << 3; + nLastInterCount = 7; + + if( nT1 >= nImageHeight ) + { + tools::Long nT2 = nImageY - ( ( nImageHeight + 7 ) >> 3 ); + nT1 = ( nT2 << 3 ) + 4; + nLastInterCount = 3; + + if( nT1 >= nImageHeight ) + { + nT2 -= ( nImageHeight + 3 ) >> 3; + nT1 = ( nT2 << 2 ) + 2; + nLastInterCount = 1; + + if( nT1 >= nImageHeight ) + { + nT2 -= ( nImageHeight + 1 ) >> 2; + nT1 = ( nT2 << 1 ) + 1; + nLastInterCount = 0; + } + } + } + + nLastImageY = static_cast<sal_uInt16>(nT1); + nYAcc = nT1; + } + else + { + nLastImageY = ++nImageY; + nYAcc = nImageY; + } + + // line starts from the beginning + nImageX = 0; + } + + if( nImageY < nImageHeight ) + { + const sal_uInt8 cTmp = pBytes[ i ]; + + if( bGCTransparent ) + { + if( cTmp == nGCTransparentIndex ) + pAcc1->SetPixelIndex( nYAcc, nImageX++, cTransIndex1 ); + else + { + pAcc8->SetPixelIndex( nYAcc, nImageX, cTmp ); + pAcc1->SetPixelIndex( nYAcc, nImageX++, cNonTransIndex1 ); + } + } + else + pAcc8->SetPixelIndex( nYAcc, nImageX++, cTmp ); + } + else + { + bOverreadBlock = true; + break; + } + } +} + +void GIFReader::CreateNewBitmaps() +{ + AnimationBitmap aAnimationBitmap; + + pAcc8.reset(); + + if( bGCTransparent ) + { + pAcc1.reset(); + aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8, aBmp1 ); + } + else + aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8 ); + + aAnimationBitmap.maPositionPixel = Point( nImagePosX, nImagePosY ); + aAnimationBitmap.maSizePixel = Size( nImageWidth, nImageHeight ); + aAnimationBitmap.mnWait = ( nTimer != 65535 ) ? nTimer : ANIMATION_TIMEOUT_ON_CLICK; + aAnimationBitmap.mbUserInput = false; + + // tdf#104121 . Internet Explorer, Firefox, Chrome and Safari all set a minimum default playback speed. + // IE10 Consumer Preview sets default of 100ms for rates less that 20ms. We do the same + if (aAnimationBitmap.mnWait < 2) // 20ms, specified in 100's of a second + aAnimationBitmap.mnWait = 10; + + if( nGCDisposalMethod == 2 ) + aAnimationBitmap.meDisposal = Disposal::Back; + else if( nGCDisposalMethod == 3 ) + aAnimationBitmap.meDisposal = Disposal::Previous; + else + aAnimationBitmap.meDisposal = Disposal::Not; + + nAnimationByteSize += aAnimationBitmap.maBitmapEx.GetSizeBytes(); + nAnimationMinFileData += static_cast<sal_uInt64>(nImageWidth) * nImageHeight / 2560; + aAnimation.Insert(aAnimationBitmap); + + if( aAnimation.Count() == 1 ) + { + aAnimation.SetDisplaySizePixel( Size( nGlobalWidth, nGlobalHeight ) ); + aAnimation.SetLoopCount( nLoops ); + } +} + +Graphic GIFReader::GetIntermediateGraphic() +{ + Graphic aImGraphic; + + // only create intermediate graphic, if data is available + // but graphic still not completely read + if ( bImGraphicReady && !aAnimation.Count() ) + { + pAcc8.reset(); + + if ( bGCTransparent ) + { + pAcc1.reset(); + aImGraphic = BitmapEx( aBmp8, aBmp1 ); + + pAcc1 = BitmapScopedWriteAccess(aBmp1); + bStatus = bStatus && pAcc1; + } + else + aImGraphic = BitmapEx(aBmp8); + + pAcc8 = BitmapScopedWriteAccess(aBmp8); + bStatus = bStatus && pAcc8; + } + + return aImGraphic; +} + +bool GIFReader::ProcessGIF() +{ + bool bRead = false; + bool bEnd = false; + + if ( !bStatus ) + eActAction = ABORT_READING; + + // set stream to right position + rIStm.Seek( nLastPos ); + + switch( eActAction ) + { + // read next marker + case MARKER_READING: + { + sal_uInt8 cByte; + + rIStm.ReadUChar( cByte ); + + if( rIStm.eof() ) + eActAction = END_READING; + else if( NO_PENDING( rIStm ) ) + { + bRead = true; + + if( cByte == '!' ) + eActAction = EXTENSION_READING; + else if( cByte == ',' ) + eActAction = LOCAL_HEADER_READING; + else if( cByte == ';' ) + eActAction = END_READING; + else + eActAction = ABORT_READING; + } + } + break; + + // read ScreenDescriptor + case GLOBAL_HEADER_READING: + { + bRead = ReadGlobalHeader(); + if( bRead ) + { + ClearImageExtensions(); + eActAction = MARKER_READING; + } + } + break; + + // read extension + case EXTENSION_READING: + { + bRead = ReadExtension(); + if( bRead ) + eActAction = MARKER_READING; + } + break; + + // read Image-Descriptor + case LOCAL_HEADER_READING: + { + bRead = ReadLocalHeader(); + if( bRead ) + { + nYAcc = nImageX = nImageY = 0; + eActAction = FIRST_BLOCK_READING; + } + } + break; + + // read first data block + case FIRST_BLOCK_READING: + { + sal_uInt8 cDataSize; + + rIStm.ReadUChar( cDataSize ); + + if( rIStm.eof() ) + eActAction = ABORT_READING; + else if( cDataSize > 12 ) + bStatus = false; + else if( NO_PENDING( rIStm ) ) + { + bRead = true; + pDecomp = std::make_unique<GIFLZWDecompressor>( cDataSize ); + eActAction = NEXT_BLOCK_READING; + bOverreadBlock = false; + } + else + eActAction = FIRST_BLOCK_READING; + } + break; + + // read next data block + case NEXT_BLOCK_READING: + { + sal_uInt16 nLastX = nImageX; + sal_uInt16 nLastY = nImageY; + sal_uLong nRet = ReadNextBlock(); + + // Return: 0:Pending / 1:OK; / 2:OK and last block: / 3:EOI / 4:HardAbort + if( nRet ) + { + bRead = true; + + if ( nRet == 1 ) + { + bImGraphicReady = true; + eActAction = NEXT_BLOCK_READING; + bOverreadBlock = false; + } + else + { + if( nRet == 2 ) + { + pDecomp.reset(); + CreateNewBitmaps(); + eActAction = MARKER_READING; + ClearImageExtensions(); + } + else if( nRet == 3 ) + { + eActAction = NEXT_BLOCK_READING; + bOverreadBlock = true; + } + else + { + pDecomp.reset(); + CreateNewBitmaps(); + eActAction = ABORT_READING; + ClearImageExtensions(); + } + } + } + else + { + nImageX = nLastX; + nImageY = nLastY; + } + } + break; + + // an error occurred + case ABORT_READING: + { + bEnd = true; + eActAction = END_READING; + } + break; + + default: + break; + } + + // set stream to right position, + // if data could be read put it at the old + // position otherwise at the actual one + if( bRead || bEnd ) + nLastPos = rIStm.Tell(); + + return bRead; +} + +bool GIFReader::ReadIsAnimated() +{ + ReadState eReadState; + + bStatus = true; + + while( ProcessGIF() && ( eActAction != END_READING ) ) {} + + if( !bStatus ) + eReadState = GIFREAD_ERROR; + else if( eActAction == END_READING ) + eReadState = GIFREAD_OK; + else + { + if ( rIStm.GetError() == ERRCODE_IO_PENDING ) + rIStm.ResetError(); + + eReadState = GIFREAD_NEED_MORE; + } + + if (eReadState == GIFREAD_OK) + return aAnimation.Count() > 1; + return false; +} + +void GIFReader::GetLogicSize(Size& rLogicSize) +{ + rLogicSize.setWidth(nLogWidth100); + rLogicSize.setHeight(nLogHeight100); +} + +ReadState GIFReader::ReadGIF( Graphic& rGraphic ) +{ + ReadState eReadState; + + bStatus = true; + + while( ProcessGIF() && ( eActAction != END_READING ) ) {} + + if( !bStatus ) + eReadState = GIFREAD_ERROR; + else if( eActAction == END_READING ) + eReadState = GIFREAD_OK; + else + { + if ( rIStm.GetError() == ERRCODE_IO_PENDING ) + rIStm.ResetError(); + + eReadState = GIFREAD_NEED_MORE; + } + + if( aAnimation.Count() == 1 ) + { + rGraphic = aAnimation.Get(0).maBitmapEx; + + if( nLogWidth100 && nLogHeight100 ) + { + rGraphic.SetPrefSize( Size( nLogWidth100, nLogHeight100 ) ); + rGraphic.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + } + } + else + rGraphic = aAnimation; + + return eReadState; +} + +bool IsGIFAnimated(SvStream & rStm, Size& rLogicSize) +{ + GIFReader aReader(rStm); + + SvStreamEndian nOldFormat = rStm.GetEndian(); + rStm.SetEndian(SvStreamEndian::LITTLE); + bool bResult = aReader.ReadIsAnimated(); + aReader.GetLogicSize(rLogicSize); + rStm.SetEndian(nOldFormat); + + return bResult; +} + +VCL_DLLPUBLIC bool ImportGIF( SvStream & rStm, Graphic& rGraphic ) +{ + std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext(); + rGraphic.SetReaderContext(nullptr); + GIFReader* pGIFReader = dynamic_cast<GIFReader*>( pContext.get() ); + if (!pGIFReader) + { + pContext = std::make_shared<GIFReader>( rStm ); + pGIFReader = static_cast<GIFReader*>( pContext.get() ); + } + + SvStreamEndian nOldFormat = rStm.GetEndian(); + rStm.SetEndian( SvStreamEndian::LITTLE ); + + bool bRet = true; + + ReadState eReadState = pGIFReader->ReadGIF(rGraphic); + + if (eReadState == GIFREAD_ERROR) + { + bRet = false; + } + else if (eReadState == GIFREAD_NEED_MORE) + { + rGraphic = pGIFReader->GetIntermediateGraphic(); + rGraphic.SetReaderContext(pContext); + } + + rStm.SetEndian(nOldFormat); + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |