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 "gifread.hxx"
23 #include <o3tl/make_unique.hxx>
25 #define NO_PENDING( rStm ) ( ( rStm ).GetError() != ERRCODE_IO_PENDING )
29 GLOBAL_HEADER_READING
,
46 class GIFLZWDecompressor
;
50 class GIFReader
: public GraphicReader
56 BitmapPalette aGPalette
;
57 BitmapPalette aLPalette
;
59 std::vector
<sal_uInt8
> aSrcBuf
;
60 std::unique_ptr
<GIFLZWDecompressor
> pDecomp
;
61 Bitmap::ScopedWriteAccess pAcc8
;
62 Bitmap::ScopedWriteAccess pAcc1
;
65 sal_uInt32 nLogWidth100
;
66 sal_uInt32 nLogHeight100
;
68 sal_uInt16 nGlobalWidth
; // maximum imagewidth from header
69 sal_uInt16 nGlobalHeight
; // maximum imageheight from header
70 sal_uInt16 nImageWidth
; // maximum screenwidth from header
71 sal_uInt16 nImageHeight
; // maximum screenheight from header
72 sal_uInt16 nImagePosX
;
73 sal_uInt16 nImagePosY
;
74 sal_uInt16 nImageX
; // maximum screenwidth from header
75 sal_uInt16 nImageY
; // maximum screenheight from header
76 sal_uInt16 nLastImageY
;
77 sal_uInt16 nLastInterCount
;
81 bool bGCTransparent
; // is the image transparent, if yes:
86 sal_uInt8 nBackgroundColor
; // backgroundcolour
87 sal_uInt8 nGCTransparentIndex
; // pixels of this index are transparant
88 sal_uInt8 nGCDisposalMethod
; // 'Disposal Method' (see GIF docs)
89 sal_uInt8 cTransIndex1
;
90 sal_uInt8 cNonTransIndex1
;
92 void ReadPaletteEntries( BitmapPalette
* pPal
, sal_uLong nCount
);
93 void ClearImageExtensions();
94 void CreateBitmaps( long nWidth
, long nHeight
, BitmapPalette
* pPal
, bool bWatchForBackgroundColor
);
95 bool ReadGlobalHeader();
97 bool ReadLocalHeader();
98 sal_uLong
ReadNextBlock();
99 void FillImages( sal_uInt8
* pBytes
, sal_uLong nCount
);
100 void CreateNewBitmaps();
105 ReadState
ReadGIF( Graphic
& rGraphic
);
106 const Graphic
& GetIntermediateGraphic();
108 explicit GIFReader( SvStream
& rStm
);
109 virtual ~GIFReader() override
;
112 GIFReader::GIFReader( SvStream
& rStm
)
117 , nLastPos ( rStm
.Tell() )
118 , nLogWidth100 ( 0UL )
119 , nLogHeight100 ( 0UL )
121 , nGlobalHeight ( 0 )
129 , nLastInterCount ( 0 )
131 , eActAction ( GLOBAL_HEADER_READING
)
133 , bGCTransparent ( false )
134 , bInterlaced ( false)
135 , bOverreadBlock ( false )
136 , bImGraphicReady ( false )
137 , bGlobalPalette ( false )
138 , nBackgroundColor ( 0 )
139 , nGCTransparentIndex ( 0 )
141 , cNonTransIndex1 ( 0 )
143 maUpperName
= "SVIGIF";
144 aSrcBuf
.resize(256); // Memory buffer for ReadNextBlock
145 ClearImageExtensions();
148 GIFReader::~GIFReader()
150 aImGraphic
.SetContext( nullptr );
153 void GIFReader::ClearImageExtensions()
155 nGCDisposalMethod
= 0;
156 bGCTransparent
= false;
160 void GIFReader::CreateBitmaps( long nWidth
, long nHeight
, BitmapPalette
* pPal
,
161 bool bWatchForBackgroundColor
)
163 const Size
aSize( nWidth
, nHeight
);
165 #if SAL_TYPES_SIZEOFPOINTER == 8
166 // Don't bother allocating a bitmap of a size that would fail on a
167 // 32-bit system. We have at least one unit tests that is expected
168 // to fail (loading a 65535*65535 size GIF
169 // svtools/qa/cppunit/data/gif/fail/CVE-2008-5937-1.gif), but
170 // which doesn't fail on 64-bit Mac OS X at least. Why the loading
171 // fails on 64-bit Linux, no idea.
172 if (nWidth
>= 64000 && nHeight
>= 64000)
181 const Color
aWhite( COL_WHITE
);
183 aBmp1
= Bitmap( aSize
, 1 );
185 if( !aAnimation
.Count() )
186 aBmp1
.Erase( aWhite
);
188 pAcc1
= Bitmap::ScopedWriteAccess(aBmp1
);
192 cTransIndex1
= (sal_uInt8
) pAcc1
->GetBestPaletteIndex( aWhite
);
193 cNonTransIndex1
= cTransIndex1
? 0 : 1;
201 aBmp8
= Bitmap( aSize
, 8, pPal
);
203 if( !!aBmp8
&& bWatchForBackgroundColor
&& aAnimation
.Count() )
204 aBmp8
.Erase( (*pPal
)[ nBackgroundColor
] );
206 aBmp8
.Erase( Color( COL_WHITE
) );
208 pAcc8
= Bitmap::ScopedWriteAccess(aBmp8
);
209 bStatus
= ( pAcc8
!= nullptr );
213 bool GIFReader::ReadGlobalHeader()
220 rIStm
.ReadBytes( pBuf
, 6 );
221 if( NO_PENDING( rIStm
) )
224 if( !strcmp( pBuf
, "GIF87a" ) || !strcmp( pBuf
, "GIF89a" ) )
226 rIStm
.ReadBytes( pBuf
, 7 );
227 if( NO_PENDING( rIStm
) )
229 SvMemoryStream aMemStm
;
231 aMemStm
.SetBuffer( pBuf
, 7, 7 );
232 aMemStm
.ReadUInt16( nGlobalWidth
);
233 aMemStm
.ReadUInt16( nGlobalHeight
);
234 aMemStm
.ReadUChar( nRF
);
235 aMemStm
.ReadUChar( nBackgroundColor
);
236 aMemStm
.ReadUChar( nAspect
);
238 bGlobalPalette
= ( nRF
& 0x80 );
241 ReadPaletteEntries( &aGPalette
, sal_uLong(1) << ( ( nRF
& 7 ) + 1 ) );
243 nBackgroundColor
= 0;
245 if( NO_PENDING( rIStm
) )
256 void GIFReader::ReadPaletteEntries( BitmapPalette
* pPal
, sal_uLong nCount
)
258 sal_uLong nLen
= 3 * nCount
;
259 const sal_uInt64 nMaxPossible
= rIStm
.remainingSize();
260 if (nLen
> nMaxPossible
)
262 std::unique_ptr
<sal_uInt8
[]> pBuf(new sal_uInt8
[ nLen
]);
263 std::size_t nRead
= rIStm
.ReadBytes(pBuf
.get(), nLen
);
265 if( NO_PENDING( rIStm
) )
267 sal_uInt8
* pTmp
= pBuf
.get();
269 for (sal_uLong i
= 0; i
< nCount
; ++i
)
271 BitmapColor
& rColor
= (*pPal
)[i
];
273 rColor
.SetRed( *pTmp
++ );
274 rColor
.SetGreen( *pTmp
++ );
275 rColor
.SetBlue( *pTmp
++ );
278 // if possible accommodate some standard colours
281 (*pPal
)[ 255UL ] = Color( COL_WHITE
);
284 (*pPal
)[ 254UL ] = Color( COL_BLACK
);
289 bool GIFReader::ReadExtension()
294 sal_uInt8
cFunction(0);
295 rIStm
.ReadUChar( cFunction
);
296 if( NO_PENDING( rIStm
) )
298 bool bOverreadDataBlocks
= false;
301 rIStm
.ReadUChar( cSize
);
304 // 'Graphic Control Extension'
308 rIStm
.ReadUChar(cFlags
);
309 rIStm
.ReadUInt16(nTimer
);
310 rIStm
.ReadUChar(nGCTransparentIndex
);
312 rIStm
.ReadUChar(cByte
);
314 if ( NO_PENDING( rIStm
) )
316 nGCDisposalMethod
= ( cFlags
>> 2) & 7;
317 bGCTransparent
= ( cFlags
& 1 );
318 bStatus
= ( cSize
== 4 ) && ( cByte
== 0 );
324 // Application extension
327 if ( NO_PENDING( rIStm
) )
329 // by default overread this extension
330 bOverreadDataBlocks
= true;
332 // Appl. extension has length 11
335 OString aAppId
= read_uInt8s_ToOString(rIStm
, 8);
336 OString aAppCode
= read_uInt8s_ToOString(rIStm
, 3);
337 rIStm
.ReadUChar( cSize
);
339 // NetScape-Extension
340 if( aAppId
== "NETSCAPE" && aAppCode
== "2.0" && cSize
== 3 )
343 rIStm
.ReadUChar( cByte
);
348 rIStm
.ReadUChar( cByte
);
350 rIStm
.ReadUChar( cByte
);
351 nLoops
|= ( (sal_uInt16
) cByte
<< 8 );
352 rIStm
.ReadUChar( cByte
);
354 bStatus
= ( cByte
== 0 );
355 bRet
= NO_PENDING( rIStm
);
356 bOverreadDataBlocks
= false;
358 // Netscape interpretes the loop count
359 // as pure number of _repeats_;
360 // here it is the total number of loops
367 else if ( aAppId
== "STARDIV " && aAppCode
== "5.0" && cSize
== 9 )
370 rIStm
.ReadUChar( cByte
);
375 rIStm
.ReadUInt32( nLogWidth100
).ReadUInt32( nLogHeight100
);
376 rIStm
.ReadUChar( cByte
);
377 bStatus
= ( cByte
== 0 );
378 bRet
= NO_PENDING( rIStm
);
379 bOverreadDataBlocks
= false;
390 // overread everything else
392 bOverreadDataBlocks
= true;
396 // overread sub-blocks
397 if ( bOverreadDataBlocks
)
400 while( cSize
&& bStatus
&& !rIStm
.IsEof() )
402 sal_uInt16 nCount
= (sal_uInt16
) cSize
+ 1;
403 const sal_uInt64 nMaxPossible
= rIStm
.remainingSize();
404 if (nCount
> nMaxPossible
)
405 nCount
= nMaxPossible
;
408 rIStm
.SeekRel( nCount
- 1 ); // Skip subblock data
411 std::size_t nRead
= rIStm
.ReadBytes(&cSize
, 1);
412 if (NO_PENDING(rIStm
) && nRead
== 1)
425 bool GIFReader::ReadLocalHeader()
430 std::size_t nRead
= rIStm
.ReadBytes(pBuf
, 9);
431 if (NO_PENDING(rIStm
) && nRead
== 9)
433 SvMemoryStream aMemStm
;
436 aMemStm
.SetBuffer( pBuf
, 9, 9 );
437 aMemStm
.ReadUInt16( nImagePosX
);
438 aMemStm
.ReadUInt16( nImagePosY
);
439 aMemStm
.ReadUInt16( nImageWidth
);
440 aMemStm
.ReadUInt16( nImageHeight
);
442 aMemStm
.ReadUChar(nFlags
);
444 // if interlaced, first define startvalue
445 bInterlaced
= ( ( nFlags
& 0x40 ) == 0x40 );
452 ReadPaletteEntries( pPal
, sal_uLong(1) << ( (nFlags
& 7 ) + 1 ) );
457 // if we could read everything, we will create the local image;
458 // if the global colour table is valid for the image, we will
459 // consider the BackGroudColorIndex.
460 if( NO_PENDING( rIStm
) )
462 CreateBitmaps( nImageWidth
, nImageHeight
, pPal
, bGlobalPalette
&& ( pPal
== &aGPalette
) );
470 sal_uLong
GIFReader::ReadNextBlock()
474 sal_uInt8 cBlockSize
;
476 rIStm
.ReadUChar( cBlockSize
);
480 else if ( NO_PENDING( rIStm
) )
482 if ( cBlockSize
== 0 )
486 rIStm
.ReadBytes( aSrcBuf
.data(), cBlockSize
);
488 if( NO_PENDING( rIStm
) )
495 sal_uInt8
* pTarget
= pDecomp
->DecompressBlock( aSrcBuf
.data(), cBlockSize
, nRead
, bEOI
);
497 nRet
= ( bEOI
? 3 : 1 );
499 if( nRead
&& !bOverreadBlock
)
500 FillImages( pTarget
, nRead
);
502 rtl_freeMemory( pTarget
);
511 void GIFReader::FillImages( sal_uInt8
* pBytes
, sal_uLong nCount
)
513 for( sal_uLong i
= 0; i
< nCount
; i
++ )
515 if( nImageX
>= nImageWidth
)
521 // lines will be copied if interlaced
522 if( nLastInterCount
)
524 long nMinY
= std::min( (long) nLastImageY
+ 1, (long) nImageHeight
- 1 );
525 long nMaxY
= std::min( (long) nLastImageY
+ nLastInterCount
, (long) nImageHeight
- 1 );
527 // copy last line read, if lines do not coincide
528 // ( happens at the end of the image )
529 if( ( nMinY
> nLastImageY
) && ( nLastImageY
< ( nImageHeight
- 1 ) ) )
531 sal_uInt8
* pScanline8
= pAcc8
->GetScanline( nYAcc
);
532 sal_uLong nSize8
= pAcc8
->GetScanlineSize();
533 sal_uInt8
* pScanline1
= nullptr;
534 sal_uLong nSize1
= 0;
538 pScanline1
= pAcc1
->GetScanline( nYAcc
);
539 nSize1
= pAcc1
->GetScanlineSize();
542 for( long j
= nMinY
; j
<= nMaxY
; j
++ )
544 memcpy( pAcc8
->GetScanline( j
), pScanline8
, nSize8
);
547 memcpy( pAcc1
->GetScanline( j
), pScanline1
, nSize1
);
552 nT1
= ( ++nImageY
) << 3;
555 if( nT1
>= nImageHeight
)
557 long nT2
= nImageY
- ( ( nImageHeight
+ 7 ) >> 3 );
558 nT1
= ( nT2
<< 3 ) + 4;
561 if( nT1
>= nImageHeight
)
563 nT2
-= ( nImageHeight
+ 3 ) >> 3;
564 nT1
= ( nT2
<< 2 ) + 2;
567 if( nT1
>= nImageHeight
)
569 nT2
-= ( nImageHeight
+ 1 ) >> 2;
570 nT1
= ( nT2
<< 1 ) + 1;
576 nLastImageY
= (sal_uInt16
) nT1
;
581 nLastImageY
= ++nImageY
;
585 // line starts from the beginning
589 if( nImageY
< nImageHeight
)
591 const sal_uInt8 cTmp
= pBytes
[ i
];
595 if( cTmp
== nGCTransparentIndex
)
596 pAcc1
->SetPixelIndex( nYAcc
, nImageX
++, cTransIndex1
);
599 pAcc8
->SetPixelIndex( nYAcc
, nImageX
, cTmp
);
600 pAcc1
->SetPixelIndex( nYAcc
, nImageX
++, cNonTransIndex1
);
604 pAcc8
->SetPixelIndex( nYAcc
, nImageX
++, cTmp
);
608 bOverreadBlock
= true;
614 void GIFReader::CreateNewBitmaps()
616 AnimationBitmap aAnimBmp
;
623 aAnimBmp
.aBmpEx
= BitmapEx( aBmp8
, aBmp1
);
626 aAnimBmp
.aBmpEx
= BitmapEx( aBmp8
);
628 aAnimBmp
.aPosPix
= Point( nImagePosX
, nImagePosY
);
629 aAnimBmp
.aSizePix
= Size( nImageWidth
, nImageHeight
);
630 aAnimBmp
.nWait
= ( nTimer
!= 65535 ) ? nTimer
: ANIMATION_TIMEOUT_ON_CLICK
;
631 aAnimBmp
.bUserInput
= false;
633 if( nGCDisposalMethod
== 2 )
634 aAnimBmp
.eDisposal
= Disposal::Back
;
635 else if( nGCDisposalMethod
== 3 )
636 aAnimBmp
.eDisposal
= Disposal::Previous
;
638 aAnimBmp
.eDisposal
= Disposal::Not
;
640 aAnimation
.Insert( aAnimBmp
);
642 if( aAnimation
.Count() == 1 )
644 aAnimation
.SetDisplaySizePixel( Size( nGlobalWidth
, nGlobalHeight
) );
645 aAnimation
.SetLoopCount( nLoops
);
649 const Graphic
& GIFReader::GetIntermediateGraphic()
651 // only create intermediate graphic, if data is available
652 // but graphic still not completely read
653 if ( bImGraphicReady
&& !aAnimation
.Count() )
659 if ( bGCTransparent
)
662 aImGraphic
= BitmapEx( aBmp8
, aBmp1
);
664 pAcc1
= Bitmap::ScopedWriteAccess(aBmp1
);
665 bStatus
= bStatus
&& ( pAcc1
!= nullptr );
670 pAcc8
= Bitmap::ScopedWriteAccess(aBmp8
);
671 bStatus
= bStatus
&& ( pAcc8
!= nullptr );
677 bool GIFReader::ProcessGIF()
683 eActAction
= ABORT_READING
;
685 // set stream to right position
686 rIStm
.Seek( nLastPos
);
695 rIStm
.ReadUChar( cByte
);
698 eActAction
= END_READING
;
699 else if( NO_PENDING( rIStm
) )
704 eActAction
= EXTENSION_READING
;
705 else if( cByte
== ',' )
706 eActAction
= LOCAL_HEADER_READING
;
707 else if( cByte
== ';' )
708 eActAction
= END_READING
;
710 eActAction
= ABORT_READING
;
715 // read ScreenDescriptor
716 case GLOBAL_HEADER_READING
:
718 if( ( bRead
= ReadGlobalHeader() ) )
720 ClearImageExtensions();
721 eActAction
= MARKER_READING
;
727 case EXTENSION_READING
:
729 if( ( bRead
= ReadExtension() ) )
730 eActAction
= MARKER_READING
;
734 // read Image-Descriptor
735 case LOCAL_HEADER_READING
:
737 if( ( bRead
= ReadLocalHeader() ) )
739 nYAcc
= nImageX
= nImageY
= 0;
740 eActAction
= FIRST_BLOCK_READING
;
745 // read first data block
746 case FIRST_BLOCK_READING
:
750 rIStm
.ReadUChar( cDataSize
);
753 eActAction
= ABORT_READING
;
754 else if( cDataSize
> 12 )
756 else if( NO_PENDING( rIStm
) )
759 pDecomp
= o3tl::make_unique
<GIFLZWDecompressor
>( cDataSize
);
760 eActAction
= NEXT_BLOCK_READING
;
761 bOverreadBlock
= false;
764 eActAction
= FIRST_BLOCK_READING
;
768 // read next data block
769 case NEXT_BLOCK_READING
:
771 sal_uInt16 nLastX
= nImageX
;
772 sal_uInt16 nLastY
= nImageY
;
773 sal_uLong nRet
= ReadNextBlock();
775 // Return: 0:Pending / 1:OK; / 2:OK and last block: / 3:EOI / 4:HardAbort
782 bImGraphicReady
= true;
783 eActAction
= NEXT_BLOCK_READING
;
784 bOverreadBlock
= false;
792 eActAction
= MARKER_READING
;
793 ClearImageExtensions();
797 eActAction
= NEXT_BLOCK_READING
;
798 bOverreadBlock
= true;
804 eActAction
= ABORT_READING
;
805 ClearImageExtensions();
821 eActAction
= END_READING
;
829 // set stream to right position,
830 // if data could be read put it a the old
831 // position otherwise at the actual one
833 nLastPos
= rIStm
.Tell();
838 ReadState
GIFReader::ReadGIF( Graphic
& rGraphic
)
840 ReadState eReadState
;
844 while( ProcessGIF() && ( eActAction
!= END_READING
) ) {}
847 eReadState
= GIFREAD_ERROR
;
848 else if( eActAction
== END_READING
)
849 eReadState
= GIFREAD_OK
;
852 if ( rIStm
.GetError() == ERRCODE_IO_PENDING
)
855 eReadState
= GIFREAD_NEED_MORE
;
858 if( aAnimation
.Count() == 1 )
860 rGraphic
= aAnimation
.Get( 0 ).aBmpEx
;
862 if( nLogWidth100
&& nLogHeight100
)
864 rGraphic
.SetPrefSize( Size( nLogWidth100
, nLogHeight100
) );
865 rGraphic
.SetPrefMapMode( MapUnit::Map100thMM
);
869 rGraphic
= aAnimation
;
874 VCL_DLLPUBLIC
bool ImportGIF( SvStream
& rStm
, Graphic
& rGraphic
)
876 std::shared_ptr
<GraphicReader
> pContext
= rGraphic
.GetContext();
877 rGraphic
.SetContext(nullptr);
878 GIFReader
* pGIFReader
= dynamic_cast<GIFReader
*>( pContext
.get() );
881 pContext
= std::make_shared
<GIFReader
>( rStm
);
882 pGIFReader
= static_cast<GIFReader
*>( pContext
.get() );
885 SvStreamEndian nOldFormat
= rStm
.GetEndian();
886 rStm
.SetEndian( SvStreamEndian::LITTLE
);
890 ReadState eReadState
= pGIFReader
->ReadGIF(rGraphic
);
892 if (eReadState
== GIFREAD_ERROR
)
896 else if (eReadState
== GIFREAD_NEED_MORE
)
898 rGraphic
= pGIFReader
->GetIntermediateGraphic();
899 rGraphic
.SetContext(pContext
);
902 rStm
.SetEndian(nOldFormat
);
907 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */