blob: f509757eb14728a9754ab68a8fae504efac8c5da [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_svtools.hxx"
#define _GIFPRIVATE
#include "decode.hxx"
#include "gifread.hxx"
// -----------
// - Defines -
// -----------
#define NO_PENDING( rStm ) ( ( rStm ).GetError() != ERRCODE_IO_PENDING )
// -------------
// - GIFReader -
// -------------
GIFReader::GIFReader( SvStream& rStm ) :
aGPalette ( 256 ),
aLPalette ( 256 ),
rIStm ( rStm ),
pAcc8 ( NULL ),
pAcc1 ( NULL ),
nLastPos ( rStm.Tell() ),
nLogWidth100 ( 0UL ),
nLogHeight100 ( 0UL ),
nLoops ( 1 ),
eActAction ( GLOBAL_HEADER_READING ),
bGCTransparent ( sal_False ),
bImGraphicReady ( sal_False )
{
maUpperName = UniString::CreateFromAscii( "SVIGIF", 6 );
pSrcBuf = new sal_uInt8[ 256 ];
ClearImageExtensions();
}
// ------------------------------------------------------------------------
GIFReader::~GIFReader()
{
aImGraphic.SetContext( NULL );
if( pAcc1 )
aBmp1.ReleaseAccess( pAcc1 );
if( pAcc8 )
aBmp8.ReleaseAccess( pAcc8 );
delete[] pSrcBuf;
}
// ------------------------------------------------------------------------
void GIFReader::ClearImageExtensions()
{
nGCDisposalMethod = 0;
bGCTransparent = sal_False;
nTimer = 0;
}
// ------------------------------------------------------------------------
sal_Bool GIFReader::CreateBitmaps( long nWidth, long nHeight, BitmapPalette* pPal,
sal_Bool bWatchForBackgroundColor )
{
const Size aSize( nWidth, nHeight );
if( bGCTransparent )
{
const Color aWhite( COL_WHITE );
aBmp1 = Bitmap( aSize, 1 );
if( !aAnimation.Count() )
aBmp1.Erase( aWhite );
pAcc1 = aBmp1.AcquireWriteAccess();
if( pAcc1 )
{
cTransIndex1 = (sal_uInt8) pAcc1->GetBestPaletteIndex( aWhite );
cNonTransIndex1 = cTransIndex1 ? 0 : 1;
}
else
bStatus = sal_False;
}
if( bStatus )
{
aBmp8 = Bitmap( aSize, 8, pPal );
if( !!aBmp8 && bWatchForBackgroundColor && aAnimation.Count() )
aBmp8.Erase( (*pPal)[ nBackgroundColor ] );
else
aBmp8.Erase( Color( COL_WHITE ) );
pAcc8 = aBmp8.AcquireWriteAccess();
bStatus = ( pAcc8 != NULL );
}
return bStatus;
}
// ------------------------------------------------------------------------
sal_Bool GIFReader::ReadGlobalHeader()
{
char pBuf[ 7 ];
sal_uInt8 nRF;
sal_uInt8 nAspect;
sal_Bool bRet = sal_False;
rIStm.Read( pBuf, 6 );
if( NO_PENDING( rIStm ) )
{
pBuf[ 6 ] = 0;
if( !strcmp( pBuf, "GIF87a" ) || !strcmp( pBuf, "GIF89a" ) )
{
rIStm.Read( pBuf, 7 );
if( NO_PENDING( rIStm ) )
{
SvMemoryStream aMemStm;
aMemStm.SetBuffer( pBuf, 7, sal_False, 7 );
aMemStm >> nGlobalWidth;
aMemStm >> nGlobalHeight;
aMemStm >> nRF;
aMemStm >> nBackgroundColor;
aMemStm >> nAspect;
bGlobalPalette = (sal_Bool) ( nRF & 0x80 );
if( bGlobalPalette )
ReadPaletteEntries( &aGPalette, 1 << ( ( nRF & 7 ) + 1 ) );
else
nBackgroundColor = 0;
if( NO_PENDING( rIStm ) )
bRet = sal_True;
}
}
else
bStatus = sal_False;
}
return bRet;
}
// ------------------------------------------------------------------------
void GIFReader::ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount )
{
const sal_uLong nLen = 3UL * nCount;
sal_uInt8* pBuf = new sal_uInt8[ nLen ];
rIStm.Read( pBuf, nLen );
if( NO_PENDING( rIStm ) )
{
sal_uInt8* pTmp = pBuf;
for( sal_uLong i = 0UL; i < nCount; )
{
BitmapColor& rColor = (*pPal)[ (sal_uInt16) i++ ];
rColor.SetRed( *pTmp++ );
rColor.SetGreen( *pTmp++ );
rColor.SetBlue( *pTmp++ );
}
// nach Moeglichkeit noch einige Standardfarben unterbringen
if( nCount < 256UL )
{
(*pPal)[ 255UL ] = Color( COL_WHITE );
if( nCount < 255UL )
(*pPal)[ 254UL ] = Color( COL_BLACK );
}
}
delete[] pBuf;
}
// ------------------------------------------------------------------------
sal_Bool GIFReader::ReadExtension()
{
sal_uInt8 cFunction;
sal_uInt8 cSize;
sal_uInt8 cByte;
sal_Bool bRet = sal_False;
sal_Bool bOverreadDataBlocks = sal_False;
// Extension-Label
rIStm >> cFunction;
if( NO_PENDING( rIStm ) )
{
// Block-Laenge
rIStm >> cSize;
switch( cFunction )
{
// 'Graphic Control Extension'
case( 0xf9 ) :
{
sal_uInt8 cFlags;
rIStm >> cFlags;
rIStm >> nTimer;
rIStm >> nGCTransparentIndex;
rIStm >> cByte;
if ( NO_PENDING( rIStm ) )
{
nGCDisposalMethod = ( cFlags >> 2) & 7;
bGCTransparent = ( cFlags & 1 ) ? sal_True : sal_False;
bStatus = ( cSize == 4 ) && ( cByte == 0 );
bRet = sal_True;
}
}
break;
// Application-Extension
case ( 0xff ) :
{
if ( NO_PENDING( rIStm ) )
{
// default diese Extension ueberlesen
bOverreadDataBlocks = sal_True;
// Appl.-Extension hat Laenge 11
if ( cSize == 0x0b )
{
ByteString aAppId;
ByteString aAppCode;
rIStm.Read( aAppId.AllocBuffer( 8 ), 8 );
rIStm.Read( aAppCode.AllocBuffer( 3 ), 3 );
rIStm >> cSize;
// NetScape-Extension
if( aAppId == "NETSCAPE" && aAppCode == "2.0" && cSize == 3 )
{
rIStm >> cByte;
// Loop-Extension
if ( cByte == 0x01 )
{
rIStm >> cByte;
nLoops = cByte;
rIStm >> cByte;
nLoops |= ( (sal_uInt16) cByte << 8 );
rIStm >> cByte;
bStatus = ( cByte == 0 );
bRet = NO_PENDING( rIStm );
bOverreadDataBlocks = sal_False;
// Netscape interpretiert den LoopCount
// als reine Anzahl der _Wiederholungen_;
// bei uns ist es die Gesamtanzahl der
// Durchlaeufe
if( nLoops )
nLoops++;
}
else
rIStm.SeekRel( -1 );
}
else if ( aAppId == "STARDIV " && aAppCode == "5.0" && cSize == 9 )
{
rIStm >> cByte;
// Loop-Extension
if ( cByte == 0x01 )
{
rIStm >> nLogWidth100 >> nLogHeight100;
rIStm >> cByte;
bStatus = ( cByte == 0 );
bRet = NO_PENDING( rIStm );
bOverreadDataBlocks = sal_False;
}
else
rIStm.SeekRel( -1 );
}
}
}
}
break;
// alles andere ueberlesen
default:
bOverreadDataBlocks = sal_True;
break;
}
// Sub-Blocks ueberlesen
if ( bOverreadDataBlocks )
{
bRet = sal_True;
while( cSize && bStatus && !rIStm.IsEof() )
{
sal_uInt16 nCount = (sal_uInt16) cSize + 1;
char* pBuffer = new char[ nCount ];
bRet = sal_False;
rIStm.Read( pBuffer, nCount );
if( NO_PENDING( rIStm ) )
{
cSize = (sal_uInt8) pBuffer[ cSize ];
bRet = sal_True;
}
else
cSize = 0;
delete[] pBuffer;
}
}
}
return bRet;
}
// ------------------------------------------------------------------------
sal_Bool GIFReader::ReadLocalHeader()
{
sal_uInt8 pBuf[ 9 ];
sal_Bool bRet = sal_False;
rIStm.Read( pBuf, 9 );
if( NO_PENDING( rIStm ) )
{
SvMemoryStream aMemStm;
BitmapPalette* pPal;
sal_uInt8 nFlags;
aMemStm.SetBuffer( (char*) pBuf, 9, sal_False, 9 );
aMemStm >> nImagePosX;
aMemStm >> nImagePosY;
aMemStm >> nImageWidth;
aMemStm >> nImageHeight;
aMemStm >> nFlags;
// Falls Interlaced, ersten Startwert vorgeben
bInterlaced = ( ( nFlags & 0x40 ) == 0x40 );
nLastInterCount = 7;
nLastImageY = 0;
if( nFlags & 0x80 )
{
pPal = &aLPalette;
ReadPaletteEntries( pPal, 1 << ( (nFlags & 7 ) + 1 ) );
}
else
pPal = &aGPalette;
// Falls alles soweit eingelesen werden konnte, kann
// nun das lokale Bild angelegt werden;
// es wird uebergeben, ob der BackgroundColorIndex evtl.
// beruecksichtigt werden soll ( wenn Globale Farbtab. und
// diese auch fuer dieses Bild gilt )
if( NO_PENDING( rIStm ) )
{
CreateBitmaps( nImageWidth, nImageHeight, pPal, bGlobalPalette && ( pPal == &aGPalette ) );
bRet = sal_True;
}
}
return bRet;
}
// ------------------------------------------------------------------------
sal_uLong GIFReader::ReadNextBlock()
{
sal_uLong nRet = 0UL;
sal_uLong nRead;
sal_uInt8 cBlockSize;
rIStm >> cBlockSize;
if ( rIStm.IsEof() )
nRet = 4UL;
else if ( NO_PENDING( rIStm ) )
{
if ( cBlockSize == 0 )
nRet = 2UL;
else
{
rIStm.Read( pSrcBuf, cBlockSize );
if( NO_PENDING( rIStm ) )
{
if( bOverreadBlock )
nRet = 3UL;
else
{
sal_Bool bEOI;
HPBYTE pTarget = pDecomp->DecompressBlock( pSrcBuf, cBlockSize, nRead, bEOI );
nRet = ( bEOI ? 3 : 1 );
if( nRead && !bOverreadBlock )
FillImages( pTarget, nRead );
rtl_freeMemory( pTarget );
}
}
}
}
return nRet;
}
// ------------------------------------------------------------------------
void GIFReader::FillImages( HPBYTE pBytes, sal_uLong nCount )
{
for( sal_uLong i = 0UL; i < nCount; i++ )
{
if( nImageX >= nImageWidth )
{
if( bInterlaced )
{
long nT1, nT2;
// falls Interlaced, werden die Zeilen kopiert
if( nLastInterCount )
{
long nMinY = Min( (long) nLastImageY + 1, (long) nImageHeight - 1 );
long nMaxY = Min( (long) nLastImageY + nLastInterCount, (long) nImageHeight - 1 );
// letzte gelesene Zeile kopieren, wenn Zeilen
// nicht zusanmmenfallen ( kommt vorm wenn wir am Ende des Bildes sind )
if( ( nMinY > nLastImageY ) && ( nLastImageY < ( nImageHeight - 1 ) ) )
{
HPBYTE pScanline8 = pAcc8->GetScanline( nYAcc );
sal_uLong nSize8 = pAcc8->GetScanlineSize();
HPBYTE pScanline1 = 0;
sal_uLong nSize1 = 0;
if( bGCTransparent )
{
pScanline1 = pAcc1->GetScanline( nYAcc );
nSize1 = pAcc1->GetScanlineSize();
}
for( 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 )
{
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 = (sal_uInt16) nT1;
nYAcc = nT1;
}
else
{
nLastImageY = ++nImageY;
nYAcc = nImageY;
}
// Zeile faengt von vorne an
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 = sal_True;
break;
}
}
}
// ------------------------------------------------------------------------
void GIFReader::CreateNewBitmaps()
{
AnimationBitmap aAnimBmp;
aBmp8.ReleaseAccess( pAcc8 );
pAcc8 = NULL;
if( bGCTransparent )
{
aBmp1.ReleaseAccess( pAcc1 );
pAcc1 = NULL;
aAnimBmp.aBmpEx = BitmapEx( aBmp8, aBmp1 );
}
else
aAnimBmp.aBmpEx = BitmapEx( aBmp8 );
aAnimBmp.aPosPix = Point( nImagePosX, nImagePosY );
aAnimBmp.aSizePix = Size( nImageWidth, nImageHeight );
aAnimBmp.nWait = ( nTimer != 65535 ) ? nTimer : ANIMATION_TIMEOUT_ON_CLICK;
aAnimBmp.bUserInput = sal_False;
if( nGCDisposalMethod == 2 )
aAnimBmp.eDisposal = DISPOSE_BACK;
else if( nGCDisposalMethod == 3 )
aAnimBmp.eDisposal = DISPOSE_PREVIOUS;
else
aAnimBmp.eDisposal = DISPOSE_NOT;
aAnimation.Insert( aAnimBmp );
if( aAnimation.Count() == 1 )
{
aAnimation.SetDisplaySizePixel( Size( nGlobalWidth, nGlobalHeight ) );
aAnimation.SetLoopCount( nLoops );
}
}
// ------------------------------------------------------------------------
const Graphic& GIFReader::GetIntermediateGraphic()
{
// Intermediate-Graphic nur erzeugen, wenn schon
// Daten vorliegen, aber die Graphic noch nicht
// vollstaendig eingelesen wurde
if ( bImGraphicReady && !aAnimation.Count() )
{
Bitmap aBmp;
aBmp8.ReleaseAccess( pAcc8 );
if ( bGCTransparent )
{
aBmp1.ReleaseAccess( pAcc1 );
aImGraphic = BitmapEx( aBmp8, aBmp1 );
pAcc1 = aBmp1.AcquireWriteAccess();
bStatus = bStatus && ( pAcc1 != NULL );
}
else
aImGraphic = aBmp8;
pAcc8 = aBmp8.AcquireWriteAccess();
bStatus = bStatus && ( pAcc8 != NULL );
}
return aImGraphic;
}
// ------------------------------------------------------------------------
sal_Bool GIFReader::ProcessGIF()
{
sal_Bool bRead = sal_False;
sal_Bool bEnd = sal_False;
if ( !bStatus )
eActAction = ABORT_READING;
// Stream an die richtige Stelle bringen
rIStm.Seek( nLastPos );
switch( eActAction )
{
// naechsten Marker lesen
case( MARKER_READING ):
{
sal_uInt8 cByte;
rIStm >> cByte;
if( rIStm.IsEof() )
eActAction = END_READING;
else if( NO_PENDING( rIStm ) )
{
bRead = sal_True;
if( cByte == '!' )
eActAction = EXTENSION_READING;
else if( cByte == ',' )
eActAction = LOCAL_HEADER_READING;
else if( cByte == ';' )
eActAction = END_READING;
else
eActAction = ABORT_READING;
}
}
break;
// ScreenDescriptor lesen
case( GLOBAL_HEADER_READING ):
{
if( ( bRead = ReadGlobalHeader() ) == sal_True )
{
ClearImageExtensions();
eActAction = MARKER_READING;
}
}
break;
// Extension lesen
case( EXTENSION_READING ):
{
if( ( bRead = ReadExtension() ) == sal_True )
eActAction = MARKER_READING;
}
break;
// Image-Descriptor lesen
case( LOCAL_HEADER_READING ):
{
if( ( bRead = ReadLocalHeader() ) == sal_True )
{
nYAcc = nImageX = nImageY = 0;
eActAction = FIRST_BLOCK_READING;
}
}
break;
// ersten Datenblock lesen
case( FIRST_BLOCK_READING ):
{
sal_uInt8 cDataSize;
rIStm >> cDataSize;
if( rIStm.IsEof() )
eActAction = ABORT_READING;
else if( cDataSize > 12 )
bStatus = sal_False;
else if( NO_PENDING( rIStm ) )
{
bRead = sal_True;
pDecomp = new GIFLZWDecompressor( cDataSize );
eActAction = NEXT_BLOCK_READING;
bOverreadBlock = sal_False;
}
else
eActAction = FIRST_BLOCK_READING;
}
break;
// naechsten Datenblock lesen
case( NEXT_BLOCK_READING ):
{
sal_uInt16 nLastX = nImageX;
sal_uInt16 nLastY = nImageY;
sal_uLong nRet = ReadNextBlock();
// Return: 0:Pending / 1:OK; / 2:OK und letzter Block: / 3:EOI / 4:HardAbort
if( nRet )
{
bRead = sal_True;
if ( nRet == 1UL )
{
bImGraphicReady = sal_True;
eActAction = NEXT_BLOCK_READING;
bOverreadBlock = sal_False;
}
else
{
if( nRet == 2UL )
{
delete pDecomp;
CreateNewBitmaps();
eActAction = MARKER_READING;
ClearImageExtensions();
}
else if( nRet == 3UL )
{
eActAction = NEXT_BLOCK_READING;
bOverreadBlock = sal_True;
}
else
{
delete pDecomp;
CreateNewBitmaps();
eActAction = ABORT_READING;
ClearImageExtensions();
}
}
}
else
{
nImageX = nLastX;
nImageY = nLastY;
}
}
break;
// ein Fehler trat auf
case( ABORT_READING ):
{
bEnd = sal_True;
eActAction = END_READING;
}
break;
default:
break;
}
// Stream an die richtige Stelle bringen,
// falls Daten gelesen werden konnten
// entweder alte Position oder aktuelle Position
if( bRead || bEnd )
nLastPos = rIStm.Tell();
return bRead;
}
// ------------------------------------------------------------------------
ReadState GIFReader::ReadGIF( Graphic& rGraphic )
{
ReadState eReadState;
bStatus = sal_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 ).aBmpEx;
if( nLogWidth100 && nLogHeight100 )
{
rGraphic.SetPrefSize( Size( nLogWidth100, nLogHeight100 ) );
rGraphic.SetPrefMapMode( MAP_100TH_MM );
}
}
else
rGraphic = aAnimation;
return eReadState;
}
// -------------
// - ImportGIF -
// -------------
sal_Bool ImportGIF( SvStream & rStm, Graphic& rGraphic )
{
GIFReader* pGIFReader = (GIFReader*) rGraphic.GetContext();
sal_uInt16 nOldFormat = rStm.GetNumberFormatInt();
ReadState eReadState;
sal_Bool bRet = sal_True;
rStm.SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN );
if( !pGIFReader )
pGIFReader = new GIFReader( rStm );
rGraphic.SetContext( NULL );
eReadState = pGIFReader->ReadGIF( rGraphic );
if( eReadState == GIFREAD_ERROR )
{
bRet = sal_False;
delete pGIFReader;
}
else if( eReadState == GIFREAD_OK )
delete pGIFReader;
else
{
rGraphic = pGIFReader->GetIntermediateGraphic();
rGraphic.SetContext( pGIFReader );
}
rStm.SetNumberFormatInt( nOldFormat );
return bRet;
}