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 .
24 #include "gifread.hxx"
30 #define NO_PENDING( rStm ) ( ( rStm ).GetError() != ERRCODE_IO_PENDING )
36 GIFReader::GIFReader( SvStream
& rStm
) :
42 nLastPos ( rStm
.Tell() ),
44 nLogHeight100 ( 0UL ),
50 eActAction ( GLOBAL_HEADER_READING
),
51 bGCTransparent ( sal_False
),
52 bImGraphicReady ( sal_False
)
54 maUpperName
= rtl::OUString("SVIGIF");
55 pSrcBuf
= new sal_uInt8
[ 256 ];
56 ClearImageExtensions();
59 // ------------------------------------------------------------------------
61 GIFReader::~GIFReader()
63 aImGraphic
.SetContext( NULL
);
66 aBmp1
.ReleaseAccess( pAcc1
);
69 aBmp8
.ReleaseAccess( pAcc8
);
74 // ------------------------------------------------------------------------
76 void GIFReader::ClearImageExtensions()
78 nGCDisposalMethod
= 0;
79 bGCTransparent
= sal_False
;
83 // ------------------------------------------------------------------------
85 sal_Bool
GIFReader::CreateBitmaps( long nWidth
, long nHeight
, BitmapPalette
* pPal
,
86 sal_Bool bWatchForBackgroundColor
)
88 const Size
aSize( nWidth
, nHeight
);
91 // Don't bother allocating a bitmap of a size that would fail on a
92 // 32-bit system. We have at least one unit tests that is expected
93 // to fail (loading a 65535*65535 size GIF
94 // svtools/qa/cppunit/data/gif/fail/CVE-2008-5937-1.gif), but
95 // which doesn't fail on 64-bit Mac OS X at least. Why the loading
96 // fails on 64-bit Linux, no idea.
97 if (nWidth
>= 64000 && nHeight
>= 64000)
106 const Color
aWhite( COL_WHITE
);
108 aBmp1
= Bitmap( aSize
, 1 );
110 if( !aAnimation
.Count() )
111 aBmp1
.Erase( aWhite
);
113 pAcc1
= aBmp1
.AcquireWriteAccess();
117 cTransIndex1
= (sal_uInt8
) pAcc1
->GetBestPaletteIndex( aWhite
);
118 cNonTransIndex1
= cTransIndex1
? 0 : 1;
126 aBmp8
= Bitmap( aSize
, 8, pPal
);
128 if( !!aBmp8
&& bWatchForBackgroundColor
&& aAnimation
.Count() )
129 aBmp8
.Erase( (*pPal
)[ nBackgroundColor
] );
131 aBmp8
.Erase( Color( COL_WHITE
) );
133 pAcc8
= aBmp8
.AcquireWriteAccess();
134 bStatus
= ( pAcc8
!= NULL
);
140 // ------------------------------------------------------------------------
142 sal_Bool
GIFReader::ReadGlobalHeader()
147 sal_Bool bRet
= sal_False
;
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, sal_False
, 7 );
161 aMemStm
>> nGlobalWidth
;
162 aMemStm
>> nGlobalHeight
;
164 aMemStm
>> nBackgroundColor
;
167 bGlobalPalette
= (sal_Bool
) ( nRF
& 0x80 );
170 ReadPaletteEntries( &aGPalette
, 1 << ( ( nRF
& 7 ) + 1 ) );
172 nBackgroundColor
= 0;
174 if( NO_PENDING( rIStm
) )
185 // ------------------------------------------------------------------------
187 void GIFReader::ReadPaletteEntries( BitmapPalette
* pPal
, sal_uLong nCount
)
189 const sal_uLong nLen
= 3UL * nCount
;
190 sal_uInt8
* pBuf
= new sal_uInt8
[ nLen
];
192 rIStm
.Read( pBuf
, nLen
);
193 if( NO_PENDING( rIStm
) )
195 sal_uInt8
* pTmp
= pBuf
;
197 for( sal_uLong i
= 0UL; i
< nCount
; )
199 BitmapColor
& rColor
= (*pPal
)[ (sal_uInt16
) i
++ ];
201 rColor
.SetRed( *pTmp
++ );
202 rColor
.SetGreen( *pTmp
++ );
203 rColor
.SetBlue( *pTmp
++ );
206 // nach Moeglichkeit noch einige Standardfarben unterbringen
209 (*pPal
)[ 255UL ] = Color( COL_WHITE
);
212 (*pPal
)[ 254UL ] = Color( COL_BLACK
);
219 // ------------------------------------------------------------------------
221 sal_Bool
GIFReader::ReadExtension()
226 sal_Bool bRet
= sal_False
;
227 sal_Bool bOverreadDataBlocks
= sal_False
;
231 if( NO_PENDING( rIStm
) )
238 // 'Graphic Control Extension'
245 rIStm
>> nGCTransparentIndex
;
248 if ( NO_PENDING( rIStm
) )
250 nGCDisposalMethod
= ( cFlags
>> 2) & 7;
251 bGCTransparent
= ( cFlags
& 1 ) ? sal_True
: sal_False
;
252 bStatus
= ( cSize
== 4 ) && ( cByte
== 0 );
258 // Application-Extension
261 if ( NO_PENDING( rIStm
) )
263 // default diese Extension ueberlesen
264 bOverreadDataBlocks
= sal_True
;
266 // Appl.-Extension hat Laenge 11
269 rtl::OString aAppId
= read_uInt8s_ToOString(rIStm
, 8);
270 rtl::OString aAppCode
= read_uInt8s_ToOString(rIStm
, 3);
273 // NetScape-Extension
274 if( aAppId
.equalsL(RTL_CONSTASCII_STRINGPARAM("NETSCAPE")) && aAppCode
.equalsL(RTL_CONSTASCII_STRINGPARAM("2.0")) && cSize
== 3 )
284 nLoops
|= ( (sal_uInt16
) cByte
<< 8 );
287 bStatus
= ( cByte
== 0 );
288 bRet
= NO_PENDING( rIStm
);
289 bOverreadDataBlocks
= sal_False
;
291 // Netscape interpretiert den LoopCount
292 // als reine Anzahl der _Wiederholungen_;
293 // bei uns ist es die Gesamtanzahl der
301 else if ( aAppId
.equalsL(RTL_CONSTASCII_STRINGPARAM("STARDIV ")) && aAppCode
.equalsL(RTL_CONSTASCII_STRINGPARAM("5.0")) && cSize
== 9 )
308 rIStm
>> nLogWidth100
>> nLogHeight100
;
310 bStatus
= ( cByte
== 0 );
311 bRet
= NO_PENDING( rIStm
);
312 bOverreadDataBlocks
= sal_False
;
323 // alles andere ueberlesen
325 bOverreadDataBlocks
= sal_True
;
329 // Sub-Blocks ueberlesen
330 if ( bOverreadDataBlocks
)
333 while( cSize
&& bStatus
&& !rIStm
.IsEof() )
335 sal_uInt16 nCount
= (sal_uInt16
) cSize
+ 1;
336 char* pBuffer
= new char[ nCount
];
339 rIStm
.Read( pBuffer
, nCount
);
340 if( NO_PENDING( rIStm
) )
342 cSize
= (sal_uInt8
) pBuffer
[ cSize
];
356 // ------------------------------------------------------------------------
358 sal_Bool
GIFReader::ReadLocalHeader()
361 sal_Bool bRet
= sal_False
;
363 rIStm
.Read( pBuf
, 9 );
364 if( NO_PENDING( rIStm
) )
366 SvMemoryStream aMemStm
;
370 aMemStm
.SetBuffer( (char*) pBuf
, 9, sal_False
, 9 );
371 aMemStm
>> nImagePosX
;
372 aMemStm
>> nImagePosY
;
373 aMemStm
>> nImageWidth
;
374 aMemStm
>> nImageHeight
;
377 // Falls Interlaced, ersten Startwert vorgeben
378 bInterlaced
= ( ( nFlags
& 0x40 ) == 0x40 );
385 ReadPaletteEntries( pPal
, 1 << ( (nFlags
& 7 ) + 1 ) );
390 // Falls alles soweit eingelesen werden konnte, kann
391 // nun das lokale Bild angelegt werden;
392 // es wird uebergeben, ob der BackgroundColorIndex evtl.
393 // beruecksichtigt werden soll ( wenn Globale Farbtab. und
394 // diese auch fuer dieses Bild gilt )
395 if( NO_PENDING( rIStm
) )
397 CreateBitmaps( nImageWidth
, nImageHeight
, pPal
, bGlobalPalette
&& ( pPal
== &aGPalette
) );
405 // ------------------------------------------------------------------------
407 sal_uLong
GIFReader::ReadNextBlock()
409 sal_uLong nRet
= 0UL;
411 sal_uInt8 cBlockSize
;
417 else if ( NO_PENDING( rIStm
) )
419 if ( cBlockSize
== 0 )
423 rIStm
.Read( pSrcBuf
, cBlockSize
);
425 if( NO_PENDING( rIStm
) )
432 HPBYTE pTarget
= pDecomp
->DecompressBlock( pSrcBuf
, cBlockSize
, nRead
, bEOI
);
434 nRet
= ( bEOI
? 3 : 1 );
436 if( nRead
&& !bOverreadBlock
)
437 FillImages( pTarget
, nRead
);
439 rtl_freeMemory( pTarget
);
448 // ------------------------------------------------------------------------
450 void GIFReader::FillImages( HPBYTE pBytes
, sal_uLong nCount
)
452 for( sal_uLong i
= 0UL; i
< nCount
; i
++ )
454 if( nImageX
>= nImageWidth
)
460 // falls Interlaced, werden die Zeilen kopiert
461 if( nLastInterCount
)
463 long nMinY
= Min( (long) nLastImageY
+ 1, (long) nImageHeight
- 1 );
464 long nMaxY
= Min( (long) nLastImageY
+ nLastInterCount
, (long) nImageHeight
- 1 );
466 // letzte gelesene Zeile kopieren, wenn Zeilen
467 // nicht zusanmmenfallen ( kommt vorm wenn wir am Ende des Bildes sind )
468 if( ( nMinY
> nLastImageY
) && ( nLastImageY
< ( nImageHeight
- 1 ) ) )
470 HPBYTE pScanline8
= pAcc8
->GetScanline( nYAcc
);
471 sal_uLong nSize8
= pAcc8
->GetScanlineSize();
472 HPBYTE pScanline1
= 0;
473 sal_uLong nSize1
= 0;
477 pScanline1
= pAcc1
->GetScanline( nYAcc
);
478 nSize1
= pAcc1
->GetScanlineSize();
481 for( long j
= nMinY
; j
<= nMaxY
; j
++ )
483 memcpy( pAcc8
->GetScanline( j
), pScanline8
, nSize8
);
486 memcpy( pAcc1
->GetScanline( j
), pScanline1
, nSize1
);
491 nT1
= ( ++nImageY
) << 3;
494 if( nT1
>= nImageHeight
)
496 nT2
= nImageY
- ( ( nImageHeight
+ 7 ) >> 3 );
497 nT1
= ( nT2
<< 3 ) + 4;
500 if( nT1
>= nImageHeight
)
502 nT2
-= ( nImageHeight
+ 3 ) >> 3;
503 nT1
= ( nT2
<< 2 ) + 2;
506 if( nT1
>= nImageHeight
)
508 nT2
-= ( nImageHeight
+ 1 ) >> 2;
509 nT1
= ( nT2
<< 1 ) + 1;
515 nLastImageY
= (sal_uInt16
) nT1
;
520 nLastImageY
= ++nImageY
;
524 // Zeile faengt von vorne an
528 if( nImageY
< nImageHeight
)
530 const sal_uInt8 cTmp
= pBytes
[ i
];
534 if( cTmp
== nGCTransparentIndex
)
535 pAcc1
->SetPixel( nYAcc
, nImageX
++, cTransIndex1
);
538 pAcc8
->SetPixel( nYAcc
, nImageX
, cTmp
);
539 pAcc1
->SetPixel( nYAcc
, nImageX
++, cNonTransIndex1
);
543 pAcc8
->SetPixel( nYAcc
, nImageX
++, cTmp
);
547 bOverreadBlock
= sal_True
;
553 // ------------------------------------------------------------------------
555 void GIFReader::CreateNewBitmaps()
557 AnimationBitmap aAnimBmp
;
559 aBmp8
.ReleaseAccess( pAcc8
);
564 aBmp1
.ReleaseAccess( pAcc1
);
566 aAnimBmp
.aBmpEx
= BitmapEx( aBmp8
, aBmp1
);
569 aAnimBmp
.aBmpEx
= BitmapEx( aBmp8
);
571 aAnimBmp
.aPosPix
= Point( nImagePosX
, nImagePosY
);
572 aAnimBmp
.aSizePix
= Size( nImageWidth
, nImageHeight
);
573 aAnimBmp
.nWait
= ( nTimer
!= 65535 ) ? nTimer
: ANIMATION_TIMEOUT_ON_CLICK
;
574 aAnimBmp
.bUserInput
= sal_False
;
576 if( nGCDisposalMethod
== 2 )
577 aAnimBmp
.eDisposal
= DISPOSE_BACK
;
578 else if( nGCDisposalMethod
== 3 )
579 aAnimBmp
.eDisposal
= DISPOSE_PREVIOUS
;
581 aAnimBmp
.eDisposal
= DISPOSE_NOT
;
583 aAnimation
.Insert( aAnimBmp
);
585 if( aAnimation
.Count() == 1 )
587 aAnimation
.SetDisplaySizePixel( Size( nGlobalWidth
, nGlobalHeight
) );
588 aAnimation
.SetLoopCount( nLoops
);
592 // ------------------------------------------------------------------------
594 const Graphic
& GIFReader::GetIntermediateGraphic()
596 // Intermediate-Graphic nur erzeugen, wenn schon
597 // Daten vorliegen, aber die Graphic noch nicht
598 // vollstaendig eingelesen wurde
599 if ( bImGraphicReady
&& !aAnimation
.Count() )
603 aBmp8
.ReleaseAccess( pAcc8
);
605 if ( bGCTransparent
)
607 aBmp1
.ReleaseAccess( pAcc1
);
608 aImGraphic
= BitmapEx( aBmp8
, aBmp1
);
610 pAcc1
= aBmp1
.AcquireWriteAccess();
611 bStatus
= bStatus
&& ( pAcc1
!= NULL
);
616 pAcc8
= aBmp8
.AcquireWriteAccess();
617 bStatus
= bStatus
&& ( pAcc8
!= NULL
);
623 // ------------------------------------------------------------------------
625 sal_Bool
GIFReader::ProcessGIF()
627 sal_Bool bRead
= sal_False
;
628 sal_Bool bEnd
= sal_False
;
631 eActAction
= ABORT_READING
;
633 // Stream an die richtige Stelle bringen
634 rIStm
.Seek( nLastPos
);
638 // naechsten Marker lesen
639 case( MARKER_READING
):
646 eActAction
= END_READING
;
647 else if( NO_PENDING( rIStm
) )
652 eActAction
= EXTENSION_READING
;
653 else if( cByte
== ',' )
654 eActAction
= LOCAL_HEADER_READING
;
655 else if( cByte
== ';' )
656 eActAction
= END_READING
;
658 eActAction
= ABORT_READING
;
663 // ScreenDescriptor lesen
664 case( GLOBAL_HEADER_READING
):
666 if( ( bRead
= ReadGlobalHeader() ) == sal_True
)
668 ClearImageExtensions();
669 eActAction
= MARKER_READING
;
676 case( EXTENSION_READING
):
678 if( ( bRead
= ReadExtension() ) == sal_True
)
679 eActAction
= MARKER_READING
;
684 // Image-Descriptor lesen
685 case( LOCAL_HEADER_READING
):
687 if( ( bRead
= ReadLocalHeader() ) == sal_True
)
689 nYAcc
= nImageX
= nImageY
= 0;
690 eActAction
= FIRST_BLOCK_READING
;
696 // ersten Datenblock lesen
697 case( FIRST_BLOCK_READING
):
704 eActAction
= ABORT_READING
;
705 else if( cDataSize
> 12 )
707 else if( NO_PENDING( rIStm
) )
710 pDecomp
= new GIFLZWDecompressor( cDataSize
);
711 eActAction
= NEXT_BLOCK_READING
;
712 bOverreadBlock
= sal_False
;
715 eActAction
= FIRST_BLOCK_READING
;
719 // naechsten Datenblock lesen
720 case( NEXT_BLOCK_READING
):
722 sal_uInt16 nLastX
= nImageX
;
723 sal_uInt16 nLastY
= nImageY
;
724 sal_uLong nRet
= ReadNextBlock();
726 // Return: 0:Pending / 1:OK; / 2:OK und letzter Block: / 3:EOI / 4:HardAbort
733 bImGraphicReady
= sal_True
;
734 eActAction
= NEXT_BLOCK_READING
;
735 bOverreadBlock
= sal_False
;
743 eActAction
= MARKER_READING
;
744 ClearImageExtensions();
746 else if( nRet
== 3UL )
748 eActAction
= NEXT_BLOCK_READING
;
749 bOverreadBlock
= sal_True
;
755 eActAction
= ABORT_READING
;
756 ClearImageExtensions();
768 // ein Fehler trat auf
769 case( ABORT_READING
):
772 eActAction
= END_READING
;
780 // Stream an die richtige Stelle bringen,
781 // falls Daten gelesen werden konnten
782 // entweder alte Position oder aktuelle Position
784 nLastPos
= rIStm
.Tell();
789 // ------------------------------------------------------------------------
791 ReadState
GIFReader::ReadGIF( Graphic
& rGraphic
)
793 ReadState eReadState
;
797 while( ProcessGIF() && ( eActAction
!= END_READING
) ) {}
800 eReadState
= GIFREAD_ERROR
;
801 else if( eActAction
== END_READING
)
802 eReadState
= GIFREAD_OK
;
805 if ( rIStm
.GetError() == ERRCODE_IO_PENDING
)
808 eReadState
= GIFREAD_NEED_MORE
;
811 if( aAnimation
.Count() == 1 )
813 rGraphic
= aAnimation
.Get( 0 ).aBmpEx
;
815 if( nLogWidth100
&& nLogHeight100
)
817 rGraphic
.SetPrefSize( Size( nLogWidth100
, nLogHeight100
) );
818 rGraphic
.SetPrefMapMode( MAP_100TH_MM
);
822 rGraphic
= aAnimation
;
832 sal_Bool
ImportGIF( SvStream
& rStm
, Graphic
& rGraphic
)
834 GIFReader
* pGIFReader
= (GIFReader
*) rGraphic
.GetContext();
835 sal_uInt16 nOldFormat
= rStm
.GetNumberFormatInt();
836 ReadState eReadState
;
837 sal_Bool bRet
= sal_True
;
839 rStm
.SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN
);
842 pGIFReader
= new GIFReader( rStm
);
844 rGraphic
.SetContext( NULL
);
845 eReadState
= pGIFReader
->ReadGIF( rGraphic
);
847 if( eReadState
== GIFREAD_ERROR
)
852 else if( eReadState
== GIFREAD_OK
)
856 rGraphic
= pGIFReader
->GetIntermediateGraphic();
857 rGraphic
.SetContext( pGIFReader
);
860 rStm
.SetNumberFormatInt( nOldFormat
);
865 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */