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 .
20 #include <sal/log.hxx>
21 #include <tools/stream.hxx>
23 #include "gifread.hxx"
25 #include <vcl/BitmapWriteAccess.hxx>
31 GLOBAL_HEADER_READING
,
49 class GIFLZWDecompressor
;
58 sal_uInt64 nAnimationByteSize
;
59 sal_uInt64 nAnimationMinFileData
;
62 BitmapPalette aGPalette
;
63 BitmapPalette aLPalette
;
65 std::vector
<sal_uInt8
> aSrcBuf
;
66 std::unique_ptr
<GIFLZWDecompressor
> pDecomp
;
67 BitmapScopedWriteAccess pAcc8
;
68 BitmapScopedWriteAccess pAcc1
;
71 sal_uInt64 nMaxStreamData
;
72 sal_uInt32 nLogWidth100
;
73 sal_uInt32 nLogHeight100
;
75 sal_uInt16 nGlobalWidth
; // maximum imagewidth from header
76 sal_uInt16 nGlobalHeight
; // maximum imageheight from header
77 sal_uInt16 nImageWidth
; // maximum screenwidth from header
78 sal_uInt16 nImageHeight
; // maximum screenheight from header
79 sal_uInt16 nImagePosX
;
80 sal_uInt16 nImagePosY
;
81 sal_uInt16 nImageX
; // maximum screenwidth from header
82 sal_uInt16 nImageY
; // maximum screenheight from header
83 sal_uInt16 nLastImageY
;
84 sal_uInt16 nLastInterCount
;
88 bool bGCTransparent
; // is the image transparent, if yes:
92 sal_uInt8 nBackgroundColor
; // backgroundcolour
93 sal_uInt8 nGCTransparentIndex
; // pixels of this index are transparent
94 sal_uInt8 nGCDisposalMethod
; // 'Disposal Method' (see GIF docs)
95 sal_uInt8 cTransIndex1
;
96 sal_uInt8 cNonTransIndex1
;
97 sal_uLong nPaletteSize
;
99 void ReadPaletteEntries( BitmapPalette
* pPal
, sal_uLong nCount
);
100 void ClearImageExtensions();
101 void CreateBitmaps( tools::Long nWidth
, tools::Long nHeight
, BitmapPalette
* pPal
, bool bWatchForBackgroundColor
);
102 bool ReadGlobalHeader();
103 bool ReadExtension();
104 bool ReadLocalHeader();
105 sal_uLong
ReadNextBlock();
106 void FillImages( const sal_uInt8
* pBytes
, sal_uLong nCount
);
107 void CreateNewBitmaps();
112 ReadState
ReadGIF( Graphic
& rGraphic
);
113 bool ReadIsAnimated();
114 void GetLogicSize(Size
& rLogicSize
);
116 explicit GIFReader( SvStream
& rStm
);
121 GIFReader::GIFReader( SvStream
& rStm
)
122 : nAnimationByteSize(0)
123 , nAnimationMinFileData(0)
128 , nLastPos ( rStm
.Tell() )
129 , nMaxStreamData( rStm
.remainingSize() )
131 , nLogHeight100 ( 0 )
133 , nGlobalHeight ( 0 )
141 , nLastInterCount ( 0 )
143 , eActAction ( GLOBAL_HEADER_READING
)
145 , bGCTransparent ( false )
146 , bInterlaced ( false)
147 , bOverreadBlock ( false )
148 , bGlobalPalette ( false )
149 , nBackgroundColor ( 0 )
150 , nGCTransparentIndex ( 0 )
152 , cNonTransIndex1 ( 0 )
155 aSrcBuf
.resize(256); // Memory buffer for ReadNextBlock
156 ClearImageExtensions();
159 void GIFReader::ClearImageExtensions()
161 nGCDisposalMethod
= 0;
162 bGCTransparent
= false;
166 void GIFReader::CreateBitmaps(tools::Long nWidth
, tools::Long nHeight
, BitmapPalette
* pPal
,
167 bool bWatchForBackgroundColor
)
169 const Size
aSize(nWidth
, nHeight
);
171 sal_uInt64 nCombinedPixSize
= nWidth
* nHeight
;
173 nCombinedPixSize
+= (nCombinedPixSize
/8);
175 // "Overall data compression asymptotically approaches 3839 × 8 / 12 = 2559 1/3"
176 // so assume compression of 1:2560 is possible
177 // (http://cloudinary.com/blog/a_one_color_image_is_worth_two_thousand_words suggests
178 // 1:1472.88 [184.11 x 8] is more realistic)
180 sal_uInt64 nMinFileData
= nWidth
* nHeight
/ 2560;
182 nMinFileData
+= nAnimationMinFileData
;
183 nCombinedPixSize
+= nAnimationByteSize
;
185 if (nMaxStreamData
< nMinFileData
)
187 //there is nowhere near enough data in this stream to fill the claimed dimensions
188 SAL_WARN("vcl.filter", "in gif frame index " << aAnimation
.Count() << " gif claims dimensions " << nWidth
<< " x " << nHeight
<<
189 " but filesize of " << nMaxStreamData
<< " is surely insufficiently large to fill all frame images");
194 // Don't bother allocating a bitmap of a size that would fail on a
195 // 32-bit system. We have at least one unit tests that is expected
196 // to fail (loading a 65535*65535 size GIF
197 // svtools/qa/cppunit/data/gif/fail/CVE-2008-5937-1.gif), but
198 // which doesn't fail on 64-bit macOS at least. Why the loading
199 // fails on 64-bit Linux, no idea.
200 if (nCombinedPixSize
>= SAL_MAX_INT32
/3*2)
206 if (!aSize
.Width() || !aSize
.Height())
214 const Color
aWhite(COL_WHITE
);
215 const Color
aBlack(COL_BLACK
);
217 aBmp1
= Bitmap(aSize
, vcl::PixelFormat::N8_BPP
, &Bitmap::GetGreyPalette(256));
219 if (!aAnimation
.Count())
226 // We have to make an AlphaMask from it, that needs to be inverted from transparency.
227 // It is faster to invert it here.
228 // So Non-Transparent color should be 0xff , and Transparent should be 0.
229 cNonTransIndex1
= static_cast<sal_uInt8
>(pAcc1
->GetBestPaletteIndex(aWhite
));
230 cTransIndex1
= static_cast<sal_uInt8
>(pAcc1
->GetBestPaletteIndex(aBlack
));
240 aBmp8
= Bitmap(aSize
, vcl::PixelFormat::N8_BPP
, pPal
);
242 if (!aBmp8
.IsEmpty() && bWatchForBackgroundColor
&& aAnimation
.Count())
243 aBmp8
.Erase((*pPal
)[nBackgroundColor
]);
245 aBmp8
.Erase(COL_WHITE
);
248 bStatus
= bool(pAcc8
);
252 bool GIFReader::ReadGlobalHeader()
257 auto nRead
= rIStm
.ReadBytes(pBuf
, 6);
258 if (nRead
== 6 && rIStm
.good())
261 if( !strcmp( pBuf
, "GIF87a" ) || !strcmp( pBuf
, "GIF89a" ) )
263 nRead
= rIStm
.ReadBytes(pBuf
, 7);
264 if (nRead
== 7 && rIStm
.good())
268 SvMemoryStream aMemStm
;
270 aMemStm
.SetBuffer( pBuf
, 7, 7 );
271 aMemStm
.ReadUInt16( nGlobalWidth
);
272 aMemStm
.ReadUInt16( nGlobalHeight
);
273 aMemStm
.ReadUChar( nRF
);
274 aMemStm
.ReadUChar( nBackgroundColor
);
275 aMemStm
.ReadUChar( nAspect
);
277 bGlobalPalette
= ( nRF
& 0x80 );
280 ReadPaletteEntries( &aGPalette
, sal_uLong(1) << ( ( nRF
& 7 ) + 1 ) );
282 nBackgroundColor
= 0;
295 void GIFReader::ReadPaletteEntries( BitmapPalette
* pPal
, sal_uLong nCount
)
297 sal_uLong nLen
= 3 * nCount
;
298 const sal_uInt64 nMaxPossible
= rIStm
.remainingSize();
299 if (nLen
> nMaxPossible
)
301 std::unique_ptr
<sal_uInt8
[]> pBuf(new sal_uInt8
[ nLen
]);
302 std::size_t nRead
= rIStm
.ReadBytes(pBuf
.get(), nLen
);
307 sal_uInt8
* pTmp
= pBuf
.get();
309 for (sal_uLong i
= 0; i
< nCount
; ++i
)
311 BitmapColor
& rColor
= (*pPal
)[i
];
313 rColor
.SetRed( *pTmp
++ );
314 rColor
.SetGreen( *pTmp
++ );
315 rColor
.SetBlue( *pTmp
++ );
318 // if possible accommodate some standard colours
321 (*pPal
)[ 255UL ] = COL_WHITE
;
324 (*pPal
)[ 254UL ] = COL_BLACK
;
327 nPaletteSize
= nCount
;
330 bool GIFReader::ReadExtension()
335 sal_uInt8
cFunction(0);
336 rIStm
.ReadUChar( cFunction
);
339 bool bOverreadDataBlocks
= false;
342 rIStm
.ReadUChar( cSize
);
345 // 'Graphic Control Extension'
349 rIStm
.ReadUChar(cFlags
);
350 rIStm
.ReadUInt16(nTimer
);
351 rIStm
.ReadUChar(nGCTransparentIndex
);
353 rIStm
.ReadUChar(cByte
);
357 nGCDisposalMethod
= ( cFlags
>> 2) & 7;
358 bGCTransparent
= ( cFlags
& 1 );
359 bStatus
= ( cSize
== 4 ) && ( cByte
== 0 );
365 // Application extension
370 // by default overread this extension
371 bOverreadDataBlocks
= true;
373 // Appl. extension has length 11
376 OString aAppId
= read_uInt8s_ToOString(rIStm
, 8);
377 OString aAppCode
= read_uInt8s_ToOString(rIStm
, 3);
378 rIStm
.ReadUChar( cSize
);
380 // NetScape-Extension
381 if( aAppId
== "NETSCAPE" && aAppCode
== "2.0" && cSize
== 3 )
384 rIStm
.ReadUChar( cByte
);
389 rIStm
.ReadUChar( cByte
);
391 rIStm
.ReadUChar( cByte
);
392 nLoops
|= ( static_cast<sal_uInt16
>(cByte
) << 8 );
393 rIStm
.ReadUChar( cByte
);
395 bStatus
= ( cByte
== 0 );
397 bOverreadDataBlocks
= false;
399 // Netscape interprets the loop count
400 // as pure number of _repeats_;
401 // here it is the total number of loops
408 else if ( aAppId
== "STARDIV " && aAppCode
== "5.0" && cSize
== 9 )
411 rIStm
.ReadUChar( cByte
);
416 rIStm
.ReadUInt32( nLogWidth100
).ReadUInt32( nLogHeight100
);
417 rIStm
.ReadUChar( cByte
);
418 bStatus
= ( cByte
== 0 );
420 bOverreadDataBlocks
= false;
431 // overread everything else
433 bOverreadDataBlocks
= true;
437 // overread sub-blocks
438 if ( bOverreadDataBlocks
)
441 while( cSize
&& bStatus
&& !rIStm
.eof() )
443 sal_uInt16 nCount
= static_cast<sal_uInt16
>(cSize
) + 1;
444 const sal_uInt64 nMaxPossible
= rIStm
.remainingSize();
445 if (nCount
> nMaxPossible
)
446 nCount
= nMaxPossible
;
449 rIStm
.SeekRel( nCount
- 1 ); // Skip subblock data
452 std::size_t nRead
= rIStm
.ReadBytes(&cSize
, 1);
453 if (rIStm
.good() && nRead
== 1)
466 bool GIFReader::ReadLocalHeader()
471 std::size_t nRead
= rIStm
.ReadBytes(pBuf
, 9);
472 if (rIStm
.good() && nRead
== 9)
474 SvMemoryStream aMemStm
;
477 aMemStm
.SetBuffer( pBuf
, 9, 9 );
478 aMemStm
.ReadUInt16( nImagePosX
);
479 aMemStm
.ReadUInt16( nImagePosY
);
480 aMemStm
.ReadUInt16( nImageWidth
);
481 aMemStm
.ReadUInt16( nImageHeight
);
483 aMemStm
.ReadUChar(nFlags
);
485 // if interlaced, first define startvalue
486 bInterlaced
= ( ( nFlags
& 0x40 ) == 0x40 );
493 ReadPaletteEntries( pPal
, sal_uLong(1) << ( (nFlags
& 7 ) + 1 ) );
498 // if we could read everything, we will create the local image;
499 // if the global colour table is valid for the image, we will
500 // consider the BackGroundColorIndex.
503 CreateBitmaps( nImageWidth
, nImageHeight
, pPal
, bGlobalPalette
&& ( pPal
== &aGPalette
) );
511 sal_uLong
GIFReader::ReadNextBlock()
514 sal_uInt8 cBlockSize
;
516 rIStm
.ReadUChar( cBlockSize
);
520 else if (rIStm
.good())
522 if ( cBlockSize
== 0 )
526 rIStm
.ReadBytes( aSrcBuf
.data(), cBlockSize
);
536 sal_uInt8
* pTarget
= pDecomp
->DecompressBlock( aSrcBuf
.data(), cBlockSize
, nRead
, bEOI
);
538 nRet
= ( bEOI
? 3 : 1 );
540 if( nRead
&& !bOverreadBlock
)
541 FillImages( pTarget
, nRead
);
543 std::free( pTarget
);
552 void GIFReader::FillImages( const sal_uInt8
* pBytes
, sal_uLong nCount
)
554 for( sal_uLong i
= 0; i
< nCount
; i
++ )
556 if( nImageX
>= nImageWidth
)
562 // lines will be copied if interlaced
563 if( nLastInterCount
)
565 tools::Long nMinY
= std::min( static_cast<tools::Long
>(nLastImageY
) + 1, static_cast<tools::Long
>(nImageHeight
) - 1 );
566 tools::Long nMaxY
= std::min( static_cast<tools::Long
>(nLastImageY
) + nLastInterCount
, static_cast<tools::Long
>(nImageHeight
) - 1 );
568 // copy last line read, if lines do not coincide
569 // ( happens at the end of the image )
570 if( ( nMinY
> nLastImageY
) && ( nLastImageY
< ( nImageHeight
- 1 ) ) )
572 sal_uInt8
* pScanline8
= pAcc8
->GetScanline( nYAcc
);
573 sal_uInt32 nSize8
= pAcc8
->GetScanlineSize();
574 sal_uInt8
* pScanline1
= nullptr;
575 sal_uInt32 nSize1
= 0;
579 pScanline1
= pAcc1
->GetScanline( nYAcc
);
580 nSize1
= pAcc1
->GetScanlineSize();
583 for( tools::Long j
= nMinY
; j
<= nMaxY
; j
++ )
585 memcpy( pAcc8
->GetScanline( j
), pScanline8
, nSize8
);
588 memcpy( pAcc1
->GetScanline( j
), pScanline1
, nSize1
);
593 nT1
= ( ++nImageY
) << 3;
596 if( nT1
>= nImageHeight
)
598 tools::Long nT2
= nImageY
- ( ( nImageHeight
+ 7 ) >> 3 );
599 nT1
= ( nT2
<< 3 ) + 4;
602 if( nT1
>= nImageHeight
)
604 nT2
-= ( nImageHeight
+ 3 ) >> 3;
605 nT1
= ( nT2
<< 2 ) + 2;
608 if( nT1
>= nImageHeight
)
610 nT2
-= ( nImageHeight
+ 1 ) >> 2;
611 nT1
= ( nT2
<< 1 ) + 1;
617 nLastImageY
= static_cast<sal_uInt16
>(nT1
);
622 nLastImageY
= ++nImageY
;
626 // line starts from the beginning
630 if( nImageY
< nImageHeight
)
632 const sal_uInt8 cTmp
= pBytes
[ i
];
636 if( cTmp
== nGCTransparentIndex
)
637 pAcc1
->SetPixelIndex( nYAcc
, nImageX
++, cTransIndex1
);
640 pAcc8
->SetPixelIndex( nYAcc
, nImageX
, cTmp
);
641 pAcc1
->SetPixelIndex( nYAcc
, nImageX
++, cNonTransIndex1
);
645 pAcc8
->SetPixelIndex( nYAcc
, nImageX
++, cTmp
);
649 bOverreadBlock
= true;
655 void GIFReader::CreateNewBitmaps()
657 AnimationFrame aAnimationFrame
;
664 AlphaMask
aAlphaMask(aBmp1
);
665 // No need to convert from transparency to alpha
666 // aBmp1 is already inverted
667 aAnimationFrame
.maBitmapEx
= BitmapEx( aBmp8
, aAlphaMask
);
669 else if( nPaletteSize
> 2 )
671 // tdf#160690 set an opaque alpha mask for non-transparent frames
672 // Due to the switch from transparency to alpha in commit
673 // 81994cb2b8b32453a92bcb011830fcb884f22ff3, an empty alpha mask
674 // is treated as a completely transparent bitmap. So revert all
675 // of the previous commits for tdf#157576, tdf#157635, and tdf#157793
676 // and create a completely opaque bitmap instead.
677 // Note: this fix also fixes tdf#157576, tdf#157635, and tdf#157793.
678 AlphaMask
aAlphaMask(aBmp8
.GetSizePixel());
679 aAnimationFrame
.maBitmapEx
= BitmapEx( aBmp8
, aAlphaMask
);
683 // Don't apply the fix for tdf#160690 as it will cause 1 bit bitmaps
684 // in Word documents like the following test document to fail to be
686 // sw/qa/extras/tiledrendering/data/tdf159626_yellowPatternFill.docx
687 aAnimationFrame
.maBitmapEx
= BitmapEx( aBmp8
);
690 aAnimationFrame
.maPositionPixel
= Point( nImagePosX
, nImagePosY
);
691 aAnimationFrame
.maSizePixel
= Size( nImageWidth
, nImageHeight
);
692 aAnimationFrame
.mnWait
= ( nTimer
!= 65535 ) ? nTimer
: ANIMATION_TIMEOUT_ON_CLICK
;
693 aAnimationFrame
.mbUserInput
= false;
695 // tdf#104121 . Internet Explorer, Firefox, Chrome and Safari all set a minimum default playback speed.
696 // IE10 Consumer Preview sets default of 100ms for rates less that 20ms. We do the same
697 if (aAnimationFrame
.mnWait
< 2) // 20ms, specified in 100's of a second
698 aAnimationFrame
.mnWait
= 10;
700 if( nGCDisposalMethod
== 2 )
701 aAnimationFrame
.meDisposal
= Disposal::Back
;
702 else if( nGCDisposalMethod
== 3 )
703 aAnimationFrame
.meDisposal
= Disposal::Previous
;
705 aAnimationFrame
.meDisposal
= Disposal::Not
;
707 nAnimationByteSize
+= aAnimationFrame
.maBitmapEx
.GetSizeBytes();
708 nAnimationMinFileData
+= static_cast<sal_uInt64
>(nImageWidth
) * nImageHeight
/ 2560;
709 aAnimation
.Insert(aAnimationFrame
);
711 if( aAnimation
.Count() == 1 )
713 aAnimation
.SetDisplaySizePixel( Size( nGlobalWidth
, nGlobalHeight
) );
714 aAnimation
.SetLoopCount( nLoops
);
718 bool GIFReader::ProcessGIF()
724 eActAction
= ABORT_READING
;
726 // set stream to right position
727 rIStm
.Seek( nLastPos
);
736 rIStm
.ReadUChar( cByte
);
739 eActAction
= END_READING
;
740 else if (rIStm
.good())
745 eActAction
= EXTENSION_READING
;
746 else if( cByte
== ',' )
747 eActAction
= LOCAL_HEADER_READING
;
748 else if( cByte
== ';' )
749 eActAction
= END_READING
;
751 eActAction
= ABORT_READING
;
756 // read ScreenDescriptor
757 case GLOBAL_HEADER_READING
:
759 bRead
= ReadGlobalHeader();
762 ClearImageExtensions();
763 eActAction
= MARKER_READING
;
769 case EXTENSION_READING
:
771 bRead
= ReadExtension();
773 eActAction
= MARKER_READING
;
777 // read Image-Descriptor
778 case LOCAL_HEADER_READING
:
780 bRead
= ReadLocalHeader();
783 nYAcc
= nImageX
= nImageY
= 0;
784 eActAction
= FIRST_BLOCK_READING
;
789 // read first data block
790 case FIRST_BLOCK_READING
:
794 rIStm
.ReadUChar( cDataSize
);
797 eActAction
= ABORT_READING
;
798 else if( cDataSize
> 12 )
800 else if (rIStm
.good())
803 pDecomp
= std::make_unique
<GIFLZWDecompressor
>( cDataSize
);
804 eActAction
= NEXT_BLOCK_READING
;
805 bOverreadBlock
= false;
808 eActAction
= FIRST_BLOCK_READING
;
812 // read next data block
813 case NEXT_BLOCK_READING
:
815 sal_uInt16 nLastX
= nImageX
;
816 sal_uInt16 nLastY
= nImageY
;
817 sal_uLong nRet
= ReadNextBlock();
819 // Return: 0:Pending / 1:OK; / 2:OK and last block: / 3:EOI / 4:HardAbort
826 eActAction
= NEXT_BLOCK_READING
;
827 bOverreadBlock
= false;
835 eActAction
= MARKER_READING
;
836 ClearImageExtensions();
840 eActAction
= NEXT_BLOCK_READING
;
841 bOverreadBlock
= true;
847 eActAction
= ABORT_READING
;
848 ClearImageExtensions();
864 eActAction
= END_READING
;
872 // set stream to right position,
873 // if data could be read put it at the old
874 // position otherwise at the actual one
876 nLastPos
= rIStm
.Tell();
881 bool GIFReader::ReadIsAnimated()
884 while (ProcessGIF() && eActAction
!= END_READING
)
887 ReadState eReadState
= GIFREAD_ERROR
;
890 eReadState
= GIFREAD_ERROR
;
891 else if (eActAction
== END_READING
)
892 eReadState
= GIFREAD_OK
;
894 if (eReadState
== GIFREAD_OK
)
895 return aAnimation
.Count() > 1;
899 void GIFReader::GetLogicSize(Size
& rLogicSize
)
901 rLogicSize
.setWidth(nLogWidth100
);
902 rLogicSize
.setHeight(nLogHeight100
);
905 ReadState
GIFReader::ReadGIF(Graphic
& rGraphic
)
909 while (ProcessGIF() && eActAction
!= END_READING
)
912 ReadState eReadState
= GIFREAD_ERROR
;
915 eReadState
= GIFREAD_ERROR
;
916 else if (eActAction
== END_READING
)
917 eReadState
= GIFREAD_OK
;
919 if (aAnimation
.Count() == 1)
921 rGraphic
= aAnimation
.Get(0).maBitmapEx
;
923 if( nLogWidth100
&& nLogHeight100
)
925 rGraphic
.SetPrefSize( Size( nLogWidth100
, nLogHeight100
) );
926 rGraphic
.SetPrefMapMode(MapMode(MapUnit::Map100thMM
));
930 rGraphic
= aAnimation
;
935 bool IsGIFAnimated(SvStream
& rStream
, Size
& rLogicSize
)
937 GIFReader
aReader(rStream
);
939 SvStreamEndian nOldFormat
= rStream
.GetEndian();
940 rStream
.SetEndian(SvStreamEndian::LITTLE
);
941 bool bResult
= aReader
.ReadIsAnimated();
942 aReader
.GetLogicSize(rLogicSize
);
943 rStream
.SetEndian(nOldFormat
);
948 VCL_DLLPUBLIC
bool ImportGIF(SvStream
& rStream
, Graphic
& rGraphic
)
950 bool bReturn
= false;
951 GIFReader
aGIFReader(rStream
);
953 SvStreamEndian nOldFormat
= rStream
.GetEndian();
954 rStream
.SetEndian(SvStreamEndian::LITTLE
);
956 ReadState eReadState
= aGIFReader
.ReadGIF(rGraphic
);
958 if (eReadState
== GIFREAD_OK
)
961 rStream
.SetEndian(nOldFormat
);
966 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */