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 .
23 #include "gifread.hxx"
24 #include <boost/scoped_array.hpp>
26 #define NO_PENDING( rStm ) ( ( rStm ).GetError() != ERRCODE_IO_PENDING )
28 GIFReader::GIFReader( SvStream
& rStm
)
36 , nLastPos ( rStm
.Tell() )
37 , nLogWidth100 ( 0UL )
38 , nLogHeight100 ( 0UL )
48 , nLastInterCount ( 0 )
50 , eActAction ( GLOBAL_HEADER_READING
)
52 , bGCTransparent ( false )
53 , bInterlaced ( false)
54 , bOverreadBlock ( false )
55 , bImGraphicReady ( false )
56 , bGlobalPalette ( false )
57 , nBackgroundColor ( 0 )
58 , nGCTransparentIndex ( 0 )
60 , cNonTransIndex1 ( 0 )
62 maUpperName
= "SVIGIF";
63 pSrcBuf
= new sal_uInt8
[ 256 ];
64 ClearImageExtensions();
67 GIFReader::~GIFReader()
69 aImGraphic
.SetContext( NULL
);
72 Bitmap::ReleaseAccess( pAcc1
);
75 Bitmap::ReleaseAccess( pAcc8
);
80 void GIFReader::ClearImageExtensions()
82 nGCDisposalMethod
= 0;
83 bGCTransparent
= false;
87 bool GIFReader::CreateBitmaps( long nWidth
, long nHeight
, BitmapPalette
* pPal
,
88 bool bWatchForBackgroundColor
)
90 const Size
aSize( nWidth
, nHeight
);
93 // Don't bother allocating a bitmap of a size that would fail on a
94 // 32-bit system. We have at least one unit tests that is expected
95 // to fail (loading a 65535*65535 size GIF
96 // svtools/qa/cppunit/data/gif/fail/CVE-2008-5937-1.gif), but
97 // which doesn't fail on 64-bit Mac OS X at least. Why the loading
98 // fails on 64-bit Linux, no idea.
99 if (nWidth
>= 64000 && nHeight
>= 64000)
108 const Color
aWhite( COL_WHITE
);
110 aBmp1
= Bitmap( aSize
, 1 );
112 if( !aAnimation
.Count() )
113 aBmp1
.Erase( aWhite
);
115 pAcc1
= aBmp1
.AcquireWriteAccess();
119 cTransIndex1
= (sal_uInt8
) pAcc1
->GetBestPaletteIndex( aWhite
);
120 cNonTransIndex1
= cTransIndex1
? 0 : 1;
128 aBmp8
= Bitmap( aSize
, 8, pPal
);
130 if( !!aBmp8
&& bWatchForBackgroundColor
&& aAnimation
.Count() )
131 aBmp8
.Erase( (*pPal
)[ nBackgroundColor
] );
133 aBmp8
.Erase( Color( COL_WHITE
) );
135 pAcc8
= aBmp8
.AcquireWriteAccess();
136 bStatus
= ( pAcc8
!= NULL
);
142 bool GIFReader::ReadGlobalHeader()
149 rIStm
.Read( pBuf
, 6 );
150 if( NO_PENDING( rIStm
) )
153 if( !strcmp( pBuf
, "GIF87a" ) || !strcmp( pBuf
, "GIF89a" ) )
155 rIStm
.Read( pBuf
, 7 );
156 if( NO_PENDING( rIStm
) )
158 SvMemoryStream aMemStm
;
160 aMemStm
.SetBuffer( pBuf
, 7, false, 7 );
161 aMemStm
.ReadUInt16( nGlobalWidth
);
162 aMemStm
.ReadUInt16( nGlobalHeight
);
163 aMemStm
.ReadUChar( nRF
);
164 aMemStm
.ReadUChar( nBackgroundColor
);
165 aMemStm
.ReadUChar( nAspect
);
167 bGlobalPalette
= ( nRF
& 0x80 );
170 ReadPaletteEntries( &aGPalette
, 1 << ( ( nRF
& 7 ) + 1 ) );
172 nBackgroundColor
= 0;
174 if( NO_PENDING( rIStm
) )
185 void GIFReader::ReadPaletteEntries( BitmapPalette
* pPal
, sal_uLong nCount
)
187 sal_uLong nLen
= 3UL * nCount
;
188 const sal_uInt64 nMaxPossible
= rIStm
.remainingSize();
189 if (nLen
> nMaxPossible
)
191 boost::scoped_array
<sal_uInt8
> pBuf(new sal_uInt8
[ nLen
]);
192 sal_Size nRead
= rIStm
.Read(pBuf
.get(), nLen
);
194 if( NO_PENDING( rIStm
) )
196 sal_uInt8
* pTmp
= pBuf
.get();
198 for (sal_uLong i
= 0UL; i
< nCount
; ++i
)
200 BitmapColor
& rColor
= (*pPal
)[i
];
202 rColor
.SetRed( *pTmp
++ );
203 rColor
.SetGreen( *pTmp
++ );
204 rColor
.SetBlue( *pTmp
++ );
207 // if possible accommodate some standard colours
210 (*pPal
)[ 255UL ] = Color( COL_WHITE
);
213 (*pPal
)[ 254UL ] = Color( COL_BLACK
);
218 bool GIFReader::ReadExtension()
221 bool bOverreadDataBlocks
= false;
224 sal_uInt8
cFunction(0);
225 rIStm
.ReadUChar( cFunction
);
226 if( NO_PENDING( rIStm
) )
230 rIStm
.ReadUChar( cSize
);
234 // 'Graphic Control Extension'
238 rIStm
.ReadUChar(cFlags
);
239 rIStm
.ReadUInt16(nTimer
);
240 rIStm
.ReadUChar(nGCTransparentIndex
);
242 rIStm
.ReadUChar(cByte
);
244 if ( NO_PENDING( rIStm
) )
246 nGCDisposalMethod
= ( cFlags
>> 2) & 7;
247 bGCTransparent
= ( cFlags
& 1 );
248 bStatus
= ( cSize
== 4 ) && ( cByte
== 0 );
254 // Application extension
257 if ( NO_PENDING( rIStm
) )
259 // by default overread this extension
260 bOverreadDataBlocks
= true;
262 // Appl. extension has length 11
265 OString aAppId
= read_uInt8s_ToOString(rIStm
, 8);
266 OString aAppCode
= read_uInt8s_ToOString(rIStm
, 3);
267 rIStm
.ReadUChar( cSize
);
269 // NetScape-Extension
270 if( aAppId
== "NETSCAPE" && aAppCode
== "2.0" && cSize
== 3 )
273 rIStm
.ReadUChar( cByte
);
278 rIStm
.ReadUChar( cByte
);
280 rIStm
.ReadUChar( cByte
);
281 nLoops
|= ( (sal_uInt16
) cByte
<< 8 );
282 rIStm
.ReadUChar( cByte
);
284 bStatus
= ( cByte
== 0 );
285 bRet
= NO_PENDING( rIStm
);
286 bOverreadDataBlocks
= false;
288 // Netscape interpretes the loop count
289 // as pure number of _repeats_;
290 // here it is the total number of loops
297 else if ( aAppId
== "STARDIV " && aAppCode
== "5.0" && cSize
== 9 )
300 rIStm
.ReadUChar( cByte
);
305 rIStm
.ReadUInt32( nLogWidth100
).ReadUInt32( nLogHeight100
);
306 rIStm
.ReadUChar( cByte
);
307 bStatus
= ( cByte
== 0 );
308 bRet
= NO_PENDING( rIStm
);
309 bOverreadDataBlocks
= false;
320 // overread everything else
322 bOverreadDataBlocks
= true;
326 // overread sub-blocks
327 if ( bOverreadDataBlocks
)
330 while( cSize
&& bStatus
&& !rIStm
.IsEof() )
332 sal_uInt16 nCount
= (sal_uInt16
) cSize
+ 1;
333 const sal_uInt64 nMaxPossible
= rIStm
.remainingSize();
334 if (nCount
> nMaxPossible
)
335 nCount
= nMaxPossible
;
336 boost::scoped_array
<sal_uInt8
> pBuffer(new sal_uInt8
[nCount
]);
339 sal_Size nRead
= rIStm
.Read(pBuffer
.get(), nCount
);
340 if (NO_PENDING(rIStm
) && cSize
< nRead
)
342 cSize
= pBuffer
[cSize
];
354 bool GIFReader::ReadLocalHeader()
359 sal_Size nRead
= rIStm
.Read(pBuf
, 9);
360 if (NO_PENDING(rIStm
) && nRead
== 9)
362 SvMemoryStream aMemStm
;
365 aMemStm
.SetBuffer( pBuf
, 9, false, 9 );
366 aMemStm
.ReadUInt16( nImagePosX
);
367 aMemStm
.ReadUInt16( nImagePosY
);
368 aMemStm
.ReadUInt16( nImageWidth
);
369 aMemStm
.ReadUInt16( nImageHeight
);
371 aMemStm
.ReadUChar(nFlags
);
373 // if interlaced, first define startvalue
374 bInterlaced
= ( ( nFlags
& 0x40 ) == 0x40 );
381 ReadPaletteEntries( pPal
, 1 << ( (nFlags
& 7 ) + 1 ) );
386 // if we could read everything, we will create the local image;
387 // if the global colour table is valid for the image, we will
388 // consider the BackGroudColorIndex.
389 if( NO_PENDING( rIStm
) )
391 CreateBitmaps( nImageWidth
, nImageHeight
, pPal
, bGlobalPalette
&& ( pPal
== &aGPalette
) );
399 sal_uLong
GIFReader::ReadNextBlock()
401 sal_uLong nRet
= 0UL;
403 sal_uInt8 cBlockSize
;
405 rIStm
.ReadUChar( cBlockSize
);
409 else if ( NO_PENDING( rIStm
) )
411 if ( cBlockSize
== 0 )
415 rIStm
.Read( pSrcBuf
, cBlockSize
);
417 if( NO_PENDING( rIStm
) )
424 HPBYTE pTarget
= pDecomp
->DecompressBlock( pSrcBuf
, cBlockSize
, nRead
, bEOI
);
426 nRet
= ( bEOI
? 3 : 1 );
428 if( nRead
&& !bOverreadBlock
)
429 FillImages( pTarget
, nRead
);
431 rtl_freeMemory( pTarget
);
440 void GIFReader::FillImages( HPBYTE pBytes
, sal_uLong nCount
)
442 for( sal_uLong i
= 0UL; i
< nCount
; i
++ )
444 if( nImageX
>= nImageWidth
)
450 // lines will be copied if interlaced
451 if( nLastInterCount
)
453 long nMinY
= std::min( (long) nLastImageY
+ 1, (long) nImageHeight
- 1 );
454 long nMaxY
= std::min( (long) nLastImageY
+ nLastInterCount
, (long) nImageHeight
- 1 );
456 // copy last line read, if lines do not coincide
457 // ( happens at the end of the image )
458 if( ( nMinY
> nLastImageY
) && ( nLastImageY
< ( nImageHeight
- 1 ) ) )
460 HPBYTE pScanline8
= pAcc8
->GetScanline( nYAcc
);
461 sal_uLong nSize8
= pAcc8
->GetScanlineSize();
462 HPBYTE pScanline1
= 0;
463 sal_uLong nSize1
= 0;
467 pScanline1
= pAcc1
->GetScanline( nYAcc
);
468 nSize1
= pAcc1
->GetScanlineSize();
471 for( long j
= nMinY
; j
<= nMaxY
; j
++ )
473 memcpy( pAcc8
->GetScanline( j
), pScanline8
, nSize8
);
476 memcpy( pAcc1
->GetScanline( j
), pScanline1
, nSize1
);
481 nT1
= ( ++nImageY
) << 3;
484 if( nT1
>= nImageHeight
)
486 long nT2
= nImageY
- ( ( nImageHeight
+ 7 ) >> 3 );
487 nT1
= ( nT2
<< 3 ) + 4;
490 if( nT1
>= nImageHeight
)
492 nT2
-= ( nImageHeight
+ 3 ) >> 3;
493 nT1
= ( nT2
<< 2 ) + 2;
496 if( nT1
>= nImageHeight
)
498 nT2
-= ( nImageHeight
+ 1 ) >> 2;
499 nT1
= ( nT2
<< 1 ) + 1;
505 nLastImageY
= (sal_uInt16
) nT1
;
510 nLastImageY
= ++nImageY
;
514 // line starts from the beginning
518 if( nImageY
< nImageHeight
)
520 const sal_uInt8 cTmp
= pBytes
[ i
];
524 if( cTmp
== nGCTransparentIndex
)
525 pAcc1
->SetPixelIndex( nYAcc
, nImageX
++, cTransIndex1
);
528 pAcc8
->SetPixelIndex( nYAcc
, nImageX
, cTmp
);
529 pAcc1
->SetPixelIndex( nYAcc
, nImageX
++, cNonTransIndex1
);
533 pAcc8
->SetPixelIndex( nYAcc
, nImageX
++, cTmp
);
537 bOverreadBlock
= true;
543 void GIFReader::CreateNewBitmaps()
545 AnimationBitmap aAnimBmp
;
547 Bitmap::ReleaseAccess( pAcc8
);
552 Bitmap::ReleaseAccess( pAcc1
);
554 aAnimBmp
.aBmpEx
= BitmapEx( aBmp8
, aBmp1
);
557 aAnimBmp
.aBmpEx
= BitmapEx( aBmp8
);
559 aAnimBmp
.aPosPix
= Point( nImagePosX
, nImagePosY
);
560 aAnimBmp
.aSizePix
= Size( nImageWidth
, nImageHeight
);
561 aAnimBmp
.nWait
= ( nTimer
!= 65535 ) ? nTimer
: ANIMATION_TIMEOUT_ON_CLICK
;
562 aAnimBmp
.bUserInput
= false;
564 if( nGCDisposalMethod
== 2 )
565 aAnimBmp
.eDisposal
= DISPOSE_BACK
;
566 else if( nGCDisposalMethod
== 3 )
567 aAnimBmp
.eDisposal
= DISPOSE_PREVIOUS
;
569 aAnimBmp
.eDisposal
= DISPOSE_NOT
;
571 aAnimation
.Insert( aAnimBmp
);
573 if( aAnimation
.Count() == 1 )
575 aAnimation
.SetDisplaySizePixel( Size( nGlobalWidth
, nGlobalHeight
) );
576 aAnimation
.SetLoopCount( nLoops
);
580 const Graphic
& GIFReader::GetIntermediateGraphic()
582 // only create intermediate graphic, if data is available
583 // but graphic still not completely read
584 if ( bImGraphicReady
&& !aAnimation
.Count() )
588 Bitmap::ReleaseAccess( pAcc8
);
590 if ( bGCTransparent
)
592 Bitmap::ReleaseAccess( pAcc1
);
593 aImGraphic
= BitmapEx( aBmp8
, aBmp1
);
595 pAcc1
= aBmp1
.AcquireWriteAccess();
596 bStatus
= bStatus
&& ( pAcc1
!= NULL
);
601 pAcc8
= aBmp8
.AcquireWriteAccess();
602 bStatus
= bStatus
&& ( pAcc8
!= NULL
);
608 bool GIFReader::ProcessGIF()
614 eActAction
= ABORT_READING
;
616 // set stream to right position
617 rIStm
.Seek( nLastPos
);
622 case( MARKER_READING
):
626 rIStm
.ReadUChar( cByte
);
629 eActAction
= END_READING
;
630 else if( NO_PENDING( rIStm
) )
635 eActAction
= EXTENSION_READING
;
636 else if( cByte
== ',' )
637 eActAction
= LOCAL_HEADER_READING
;
638 else if( cByte
== ';' )
639 eActAction
= END_READING
;
641 eActAction
= ABORT_READING
;
646 // read ScreenDescriptor
647 case( GLOBAL_HEADER_READING
):
649 if( ( bRead
= ReadGlobalHeader() ) )
651 ClearImageExtensions();
652 eActAction
= MARKER_READING
;
658 case( EXTENSION_READING
):
660 if( ( bRead
= ReadExtension() ) )
661 eActAction
= MARKER_READING
;
665 // read Image-Descriptor
666 case( LOCAL_HEADER_READING
):
668 if( ( bRead
= ReadLocalHeader() ) )
670 nYAcc
= nImageX
= nImageY
= 0;
671 eActAction
= FIRST_BLOCK_READING
;
676 // read first data block
677 case( FIRST_BLOCK_READING
):
681 rIStm
.ReadUChar( cDataSize
);
684 eActAction
= ABORT_READING
;
685 else if( cDataSize
> 12 )
687 else if( NO_PENDING( rIStm
) )
690 pDecomp
= new GIFLZWDecompressor( cDataSize
);
691 eActAction
= NEXT_BLOCK_READING
;
692 bOverreadBlock
= false;
695 eActAction
= FIRST_BLOCK_READING
;
699 // read next data block
700 case( NEXT_BLOCK_READING
):
702 sal_uInt16 nLastX
= nImageX
;
703 sal_uInt16 nLastY
= nImageY
;
704 sal_uLong nRet
= ReadNextBlock();
706 // Return: 0:Pending / 1:OK; / 2:OK and last block: / 3:EOI / 4:HardAbort
713 bImGraphicReady
= true;
714 eActAction
= NEXT_BLOCK_READING
;
715 bOverreadBlock
= false;
723 eActAction
= MARKER_READING
;
724 ClearImageExtensions();
726 else if( nRet
== 3UL )
728 eActAction
= NEXT_BLOCK_READING
;
729 bOverreadBlock
= true;
735 eActAction
= ABORT_READING
;
736 ClearImageExtensions();
749 case( ABORT_READING
):
752 eActAction
= END_READING
;
760 // set stream to right position,
761 // if data could be read put it a the old
762 // position otherwise at the actual one
764 nLastPos
= rIStm
.Tell();
769 ReadState
GIFReader::ReadGIF( Graphic
& rGraphic
)
771 ReadState eReadState
;
775 while( ProcessGIF() && ( eActAction
!= END_READING
) ) {}
778 eReadState
= GIFREAD_ERROR
;
779 else if( eActAction
== END_READING
)
780 eReadState
= GIFREAD_OK
;
783 if ( rIStm
.GetError() == ERRCODE_IO_PENDING
)
786 eReadState
= GIFREAD_NEED_MORE
;
789 if( aAnimation
.Count() == 1 )
791 rGraphic
= aAnimation
.Get( 0 ).aBmpEx
;
793 if( nLogWidth100
&& nLogHeight100
)
795 rGraphic
.SetPrefSize( Size( nLogWidth100
, nLogHeight100
) );
796 rGraphic
.SetPrefMapMode( MAP_100TH_MM
);
800 rGraphic
= aAnimation
;
805 bool ImportGIF( SvStream
& rStm
, Graphic
& rGraphic
)
807 GIFReader
* pGIFReader
= static_cast<GIFReader
*>(rGraphic
.GetContext());
808 SvStreamEndian nOldFormat
= rStm
.GetEndian();
809 ReadState eReadState
;
812 rStm
.SetEndian( SvStreamEndian::LITTLE
);
815 pGIFReader
= new GIFReader( rStm
);
817 rGraphic
.SetContext( NULL
);
818 eReadState
= pGIFReader
->ReadGIF( rGraphic
);
820 if( eReadState
== GIFREAD_ERROR
)
825 else if( eReadState
== GIFREAD_OK
)
829 rGraphic
= pGIFReader
->GetIntermediateGraphic();
830 rGraphic
.SetContext( pGIFReader
);
833 rStm
.SetEndian( nOldFormat
);
838 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */