LibreOffice Module vcl (master)  1
gifread.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <sal/log.hxx>
21 #include <tools/stream.hxx>
22 #include "decode.hxx"
23 #include "gifread.hxx"
24 #include <memory>
25 #include <bitmapwriteaccess.hxx>
27 
28 #define NO_PENDING( rStm ) ( ( rStm ).GetError() != ERRCODE_IO_PENDING )
29 
30 namespace {
31 
33 {
34  GLOBAL_HEADER_READING,
35  MARKER_READING,
36  EXTENSION_READING,
37  LOCAL_HEADER_READING,
38  FIRST_BLOCK_READING,
39  NEXT_BLOCK_READING,
40  ABORT_READING,
41  END_READING
42 };
43 
45 {
46  GIFREAD_OK,
47  GIFREAD_ERROR,
48  GIFREAD_NEED_MORE
49 };
50 
51 }
52 
53 class GIFLZWDecompressor;
54 
55 class SvStream;
56 
57 namespace {
58 
59 class GIFReader : public GraphicReader
60 {
61  Animation aAnimation;
62  sal_uInt64 nAnimationByteSize;
63  sal_uInt64 nAnimationMinFileData;
64  Bitmap aBmp8;
65  Bitmap aBmp1;
66  BitmapPalette aGPalette;
67  BitmapPalette aLPalette;
68  SvStream& rIStm;
69  std::vector<sal_uInt8> aSrcBuf;
70  std::unique_ptr<GIFLZWDecompressor> pDecomp;
73  long nYAcc;
74  long nLastPos;
75  sal_uInt64 nMaxStreamData;
76  sal_uInt32 nLogWidth100;
77  sal_uInt32 nLogHeight100;
78  sal_uInt16 nTimer;
79  sal_uInt16 nGlobalWidth; // maximum imagewidth from header
80  sal_uInt16 nGlobalHeight; // maximum imageheight from header
81  sal_uInt16 nImageWidth; // maximum screenwidth from header
82  sal_uInt16 nImageHeight; // maximum screenheight from header
83  sal_uInt16 nImagePosX;
84  sal_uInt16 nImagePosY;
85  sal_uInt16 nImageX; // maximum screenwidth from header
86  sal_uInt16 nImageY; // maximum screenheight from header
87  sal_uInt16 nLastImageY;
88  sal_uInt16 nLastInterCount;
89  sal_uInt16 nLoops;
90  GIFAction eActAction;
91  bool bStatus;
92  bool bGCTransparent; // is the image transparent, if yes:
93  bool bInterlaced;
94  bool bOverreadBlock;
95  bool bImGraphicReady;
96  bool bGlobalPalette;
97  sal_uInt8 nBackgroundColor; // backgroundcolour
98  sal_uInt8 nGCTransparentIndex; // pixels of this index are transparent
99  sal_uInt8 nGCDisposalMethod; // 'Disposal Method' (see GIF docs)
100  sal_uInt8 cTransIndex1;
101  sal_uInt8 cNonTransIndex1;
102 
103  void ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount );
104  void ClearImageExtensions();
105  void CreateBitmaps( long nWidth, long nHeight, BitmapPalette* pPal, bool bWatchForBackgroundColor );
106  bool ReadGlobalHeader();
107  bool ReadExtension();
108  bool ReadLocalHeader();
109  sal_uLong ReadNextBlock();
110  void FillImages( const sal_uInt8* pBytes, sal_uLong nCount );
111  void CreateNewBitmaps();
112  bool ProcessGIF();
113 
114 public:
115 
116  ReadState ReadGIF( Graphic& rGraphic );
117  bool ReadIsAnimated();
118  Graphic GetIntermediateGraphic();
119 
120  explicit GIFReader( SvStream& rStm );
121 };
122 
123 }
124 
125 GIFReader::GIFReader( SvStream& rStm )
126  : nAnimationByteSize(0)
127  , nAnimationMinFileData(0)
128  , aGPalette ( 256 )
129  , aLPalette ( 256 )
130  , rIStm ( rStm )
131  , nYAcc ( 0 )
132  , nLastPos ( rStm.Tell() )
133  , nMaxStreamData( rStm.remainingSize() )
134  , nLogWidth100 ( 0 )
135  , nLogHeight100 ( 0 )
136  , nGlobalWidth ( 0 )
137  , nGlobalHeight ( 0 )
138  , nImageWidth ( 0 )
139  , nImageHeight ( 0 )
140  , nImagePosX ( 0 )
141  , nImagePosY ( 0 )
142  , nImageX ( 0 )
143  , nImageY ( 0 )
144  , nLastImageY ( 0 )
145  , nLastInterCount ( 0 )
146  , nLoops ( 1 )
147  , eActAction ( GLOBAL_HEADER_READING )
148  , bStatus ( false )
149  , bGCTransparent ( false )
150  , bInterlaced ( false)
151  , bOverreadBlock ( false )
152  , bImGraphicReady ( false )
153  , bGlobalPalette ( false )
154  , nBackgroundColor ( 0 )
155  , nGCTransparentIndex ( 0 )
156  , cTransIndex1 ( 0 )
157  , cNonTransIndex1 ( 0 )
158 {
159  maUpperName = "SVIGIF";
160  aSrcBuf.resize(256); // Memory buffer for ReadNextBlock
161  ClearImageExtensions();
162 }
163 
164 void GIFReader::ClearImageExtensions()
165 {
166  nGCDisposalMethod = 0;
167  bGCTransparent = false;
168  nTimer = 0;
169 }
170 
171 void GIFReader::CreateBitmaps(long nWidth, long nHeight, BitmapPalette* pPal,
172  bool bWatchForBackgroundColor)
173 {
174  const Size aSize(nWidth, nHeight);
175 
176  sal_uInt64 nCombinedPixSize = nWidth * nHeight;
177  if (bGCTransparent)
178  nCombinedPixSize += (nCombinedPixSize/8);
179 
180  // "Overall data compression asymptotically approaches 3839 × 8 / 12 = 2559 1/3"
181  // so assume compression of 1:2560 is possible
182  // (http://cloudinary.com/blog/a_one_color_image_is_worth_two_thousand_words suggests
183  // 1:1472.88 [184.11 x 8] is more realistic)
184 
185  sal_uInt64 nMinFileData = nWidth * nHeight / 2560;
186 
187  nMinFileData += nAnimationMinFileData;
188  nCombinedPixSize += nAnimationByteSize;
189 
190  if (nMaxStreamData < nMinFileData)
191  {
192  //there is nowhere near enough data in this stream to fill the claimed dimensions
193  SAL_WARN("vcl.filter", "in gif frame index " << aAnimation.Count() << " gif claims dimensions " << nWidth << " x " << nHeight <<
194  " but filesize of " << nMaxStreamData << " is surely insufficiently large to fill all frame images");
195  bStatus = false;
196  return;
197  }
198 
199  // Don't bother allocating a bitmap of a size that would fail on a
200  // 32-bit system. We have at least one unit tests that is expected
201  // to fail (loading a 65535*65535 size GIF
202  // svtools/qa/cppunit/data/gif/fail/CVE-2008-5937-1.gif), but
203  // which doesn't fail on 64-bit macOS at least. Why the loading
204  // fails on 64-bit Linux, no idea.
205  if (nCombinedPixSize >= SAL_MAX_INT32/3*2)
206  {
207  bStatus = false;
208  return;
209  }
210 
211  if (!aSize.Width() || !aSize.Height())
212  {
213  bStatus = false;
214  return;
215  }
216 
217  if (bGCTransparent)
218  {
219  const Color aWhite(COL_WHITE);
220 
221  aBmp1 = Bitmap(aSize, 1);
222 
223  if (!aAnimation.Count())
224  aBmp1.Erase(aWhite);
225 
226  pAcc1 = BitmapScopedWriteAccess(aBmp1);
227 
228  if (pAcc1)
229  {
230  cTransIndex1 = static_cast<sal_uInt8>(pAcc1->GetBestPaletteIndex(aWhite));
231  cNonTransIndex1 = cTransIndex1 ? 0 : 1;
232  }
233  else
234  {
235  bStatus = false;
236  }
237  }
238 
239  if (bStatus)
240  {
241  aBmp8 = Bitmap(aSize, 8, pPal);
242 
243  if (!!aBmp8 && bWatchForBackgroundColor && aAnimation.Count())
244  aBmp8.Erase((*pPal)[nBackgroundColor]);
245  else
246  aBmp8.Erase(COL_WHITE);
247 
248  pAcc8 = BitmapScopedWriteAccess(aBmp8);
249  bStatus = bool(pAcc8);
250  }
251 }
252 
253 bool GIFReader::ReadGlobalHeader()
254 {
255  char pBuf[ 7 ];
256  sal_uInt8 nRF;
257  sal_uInt8 nAspect;
258  bool bRet = false;
259 
260  rIStm.ReadBytes( pBuf, 6 );
261  if( NO_PENDING( rIStm ) )
262  {
263  pBuf[ 6 ] = 0;
264  if( !strcmp( pBuf, "GIF87a" ) || !strcmp( pBuf, "GIF89a" ) )
265  {
266  rIStm.ReadBytes( pBuf, 7 );
267  if( NO_PENDING( rIStm ) )
268  {
269  SvMemoryStream aMemStm;
270 
271  aMemStm.SetBuffer( pBuf, 7, 7 );
272  aMemStm.ReadUInt16( nGlobalWidth );
273  aMemStm.ReadUInt16( nGlobalHeight );
274  aMemStm.ReadUChar( nRF );
275  aMemStm.ReadUChar( nBackgroundColor );
276  aMemStm.ReadUChar( nAspect );
277 
278  bGlobalPalette = ( nRF & 0x80 );
279 
280  if( bGlobalPalette )
281  ReadPaletteEntries( &aGPalette, sal_uLong(1) << ( ( nRF & 7 ) + 1 ) );
282  else
283  nBackgroundColor = 0;
284 
285  if( NO_PENDING( rIStm ) )
286  bRet = true;
287  }
288  }
289  else
290  bStatus = false;
291  }
292 
293  return bRet;
294 }
295 
296 void GIFReader::ReadPaletteEntries( BitmapPalette* pPal, sal_uLong nCount )
297 {
298  sal_uLong nLen = 3 * nCount;
299  const sal_uInt64 nMaxPossible = rIStm.remainingSize();
300  if (nLen > nMaxPossible)
301  nLen = nMaxPossible;
302  std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[ nLen ]);
303  std::size_t nRead = rIStm.ReadBytes(pBuf.get(), nLen);
304  nCount = nRead/3UL;
305  if( !(NO_PENDING( rIStm )) )
306  return;
307 
308  sal_uInt8* pTmp = pBuf.get();
309 
310  for (sal_uLong i = 0; i < nCount; ++i)
311  {
312  BitmapColor& rColor = (*pPal)[i];
313 
314  rColor.SetRed( *pTmp++ );
315  rColor.SetGreen( *pTmp++ );
316  rColor.SetBlue( *pTmp++ );
317  }
318 
319  // if possible accommodate some standard colours
320  if( nCount < 256 )
321  {
322  (*pPal)[ 255UL ] = COL_WHITE;
323 
324  if( nCount < 255 )
325  (*pPal)[ 254UL ] = COL_BLACK;
326  }
327 }
328 
329 bool GIFReader::ReadExtension()
330 {
331  bool bRet = false;
332 
333  // Extension-Label
334  sal_uInt8 cFunction(0);
335  rIStm.ReadUChar( cFunction );
336  if( NO_PENDING( rIStm ) )
337  {
338  bool bOverreadDataBlocks = false;
339  sal_uInt8 cSize(0);
340  // Block length
341  rIStm.ReadUChar( cSize );
342  switch( cFunction )
343  {
344  // 'Graphic Control Extension'
345  case 0xf9 :
346  {
347  sal_uInt8 cFlags(0);
348  rIStm.ReadUChar(cFlags);
349  rIStm.ReadUInt16(nTimer);
350  rIStm.ReadUChar(nGCTransparentIndex);
351  sal_uInt8 cByte(0);
352  rIStm.ReadUChar(cByte);
353 
354  if ( NO_PENDING( rIStm ) )
355  {
356  nGCDisposalMethod = ( cFlags >> 2) & 7;
357  bGCTransparent = ( cFlags & 1 );
358  bStatus = ( cSize == 4 ) && ( cByte == 0 );
359  bRet = true;
360  }
361  }
362  break;
363 
364  // Application extension
365  case 0xff :
366  {
367  if ( NO_PENDING( rIStm ) )
368  {
369  // by default overread this extension
370  bOverreadDataBlocks = true;
371 
372  // Appl. extension has length 11
373  if ( cSize == 0x0b )
374  {
375  OString aAppId = read_uInt8s_ToOString(rIStm, 8);
376  OString aAppCode = read_uInt8s_ToOString(rIStm, 3);
377  rIStm.ReadUChar( cSize );
378 
379  // NetScape-Extension
380  if( aAppId == "NETSCAPE" && aAppCode == "2.0" && cSize == 3 )
381  {
382  sal_uInt8 cByte(0);
383  rIStm.ReadUChar( cByte );
384 
385  // Loop-Extension
386  if ( cByte == 0x01 )
387  {
388  rIStm.ReadUChar( cByte );
389  nLoops = cByte;
390  rIStm.ReadUChar( cByte );
391  nLoops |= ( static_cast<sal_uInt16>(cByte) << 8 );
392  rIStm.ReadUChar( cByte );
393 
394  bStatus = ( cByte == 0 );
395  bRet = NO_PENDING( rIStm );
396  bOverreadDataBlocks = false;
397 
398  // Netscape interprets the loop count
399  // as pure number of _repeats_;
400  // here it is the total number of loops
401  if( nLoops )
402  nLoops++;
403  }
404  else
405  rIStm.SeekRel( -1 );
406  }
407  else if ( aAppId == "STARDIV " && aAppCode == "5.0" && cSize == 9 )
408  {
409  sal_uInt8 cByte(0);
410  rIStm.ReadUChar( cByte );
411 
412  // Loop extension
413  if ( cByte == 0x01 )
414  {
415  rIStm.ReadUInt32( nLogWidth100 ).ReadUInt32( nLogHeight100 );
416  rIStm.ReadUChar( cByte );
417  bStatus = ( cByte == 0 );
418  bRet = NO_PENDING( rIStm );
419  bOverreadDataBlocks = false;
420  }
421  else
422  rIStm.SeekRel( -1 );
423  }
424 
425  }
426  }
427  }
428  break;
429 
430  // overread everything else
431  default:
432  bOverreadDataBlocks = true;
433  break;
434  }
435 
436  // overread sub-blocks
437  if ( bOverreadDataBlocks )
438  {
439  bRet = true;
440  while( cSize && bStatus && !rIStm.eof() )
441  {
442  sal_uInt16 nCount = static_cast<sal_uInt16>(cSize) + 1;
443  const sal_uInt64 nMaxPossible = rIStm.remainingSize();
444  if (nCount > nMaxPossible)
445  nCount = nMaxPossible;
446 
447  if (nCount)
448  rIStm.SeekRel( nCount - 1 ); // Skip subblock data
449 
450  bRet = false;
451  std::size_t nRead = rIStm.ReadBytes(&cSize, 1);
452  if (NO_PENDING(rIStm) && nRead == 1)
453  {
454  bRet = true;
455  }
456  else
457  cSize = 0;
458  }
459  }
460  }
461 
462  return bRet;
463 }
464 
465 bool GIFReader::ReadLocalHeader()
466 {
467  sal_uInt8 pBuf[ 9 ];
468  bool bRet = false;
469 
470  std::size_t nRead = rIStm.ReadBytes(pBuf, 9);
471  if (NO_PENDING(rIStm) && nRead == 9)
472  {
473  SvMemoryStream aMemStm;
474  BitmapPalette* pPal;
475 
476  aMemStm.SetBuffer( pBuf, 9, 9 );
477  aMemStm.ReadUInt16( nImagePosX );
478  aMemStm.ReadUInt16( nImagePosY );
479  aMemStm.ReadUInt16( nImageWidth );
480  aMemStm.ReadUInt16( nImageHeight );
481  sal_uInt8 nFlags(0);
482  aMemStm.ReadUChar(nFlags);
483 
484  // if interlaced, first define startvalue
485  bInterlaced = ( ( nFlags & 0x40 ) == 0x40 );
486  nLastInterCount = 7;
487  nLastImageY = 0;
488 
489  if( nFlags & 0x80 )
490  {
491  pPal = &aLPalette;
492  ReadPaletteEntries( pPal, sal_uLong(1) << ( (nFlags & 7 ) + 1 ) );
493  }
494  else
495  pPal = &aGPalette;
496 
497  // if we could read everything, we will create the local image;
498  // if the global colour table is valid for the image, we will
499  // consider the BackGroundColorIndex.
500  if( NO_PENDING( rIStm ) )
501  {
502  CreateBitmaps( nImageWidth, nImageHeight, pPal, bGlobalPalette && ( pPal == &aGPalette ) );
503  bRet = true;
504  }
505  }
506 
507  return bRet;
508 }
509 
510 sal_uLong GIFReader::ReadNextBlock()
511 {
512  sal_uLong nRet = 0;
513  sal_uLong nRead;
514  sal_uInt8 cBlockSize;
515 
516  rIStm.ReadUChar( cBlockSize );
517 
518  if ( rIStm.eof() )
519  nRet = 4;
520  else if ( NO_PENDING( rIStm ) )
521  {
522  if ( cBlockSize == 0 )
523  nRet = 2;
524  else
525  {
526  rIStm.ReadBytes( aSrcBuf.data(), cBlockSize );
527 
528  if( NO_PENDING( rIStm ) )
529  {
530  if( bOverreadBlock )
531  nRet = 3;
532  else
533  {
534  bool bEOI;
535  sal_uInt8* pTarget = pDecomp->DecompressBlock( aSrcBuf.data(), cBlockSize, nRead, bEOI );
536 
537  nRet = ( bEOI ? 3 : 1 );
538 
539  if( nRead && !bOverreadBlock )
540  FillImages( pTarget, nRead );
541 
542  std::free( pTarget );
543  }
544  }
545  }
546  }
547 
548  return nRet;
549 }
550 
551 void GIFReader::FillImages( const sal_uInt8* pBytes, sal_uLong nCount )
552 {
553  for( sal_uLong i = 0; i < nCount; i++ )
554  {
555  if( nImageX >= nImageWidth )
556  {
557  if( bInterlaced )
558  {
559  long nT1;
560 
561  // lines will be copied if interlaced
562  if( nLastInterCount )
563  {
564  long nMinY = std::min( static_cast<long>(nLastImageY) + 1, static_cast<long>(nImageHeight) - 1 );
565  long nMaxY = std::min( static_cast<long>(nLastImageY) + nLastInterCount, static_cast<long>(nImageHeight) - 1 );
566 
567  // copy last line read, if lines do not coincide
568  // ( happens at the end of the image )
569  if( ( nMinY > nLastImageY ) && ( nLastImageY < ( nImageHeight - 1 ) ) )
570  {
571  sal_uInt8* pScanline8 = pAcc8->GetScanline( nYAcc );
572  sal_uInt32 nSize8 = pAcc8->GetScanlineSize();
573  sal_uInt8* pScanline1 = nullptr;
574  sal_uInt32 nSize1 = 0;
575 
576  if( bGCTransparent )
577  {
578  pScanline1 = pAcc1->GetScanline( nYAcc );
579  nSize1 = pAcc1->GetScanlineSize();
580  }
581 
582  for( long j = nMinY; j <= nMaxY; j++ )
583  {
584  memcpy( pAcc8->GetScanline( j ), pScanline8, nSize8 );
585 
586  if( bGCTransparent )
587  memcpy( pAcc1->GetScanline( j ), pScanline1, nSize1 );
588  }
589  }
590  }
591 
592  nT1 = ( ++nImageY ) << 3;
593  nLastInterCount = 7;
594 
595  if( nT1 >= nImageHeight )
596  {
597  long nT2 = nImageY - ( ( nImageHeight + 7 ) >> 3 );
598  nT1 = ( nT2 << 3 ) + 4;
599  nLastInterCount = 3;
600 
601  if( nT1 >= nImageHeight )
602  {
603  nT2 -= ( nImageHeight + 3 ) >> 3;
604  nT1 = ( nT2 << 2 ) + 2;
605  nLastInterCount = 1;
606 
607  if( nT1 >= nImageHeight )
608  {
609  nT2 -= ( nImageHeight + 1 ) >> 2;
610  nT1 = ( nT2 << 1 ) + 1;
611  nLastInterCount = 0;
612  }
613  }
614  }
615 
616  nLastImageY = static_cast<sal_uInt16>(nT1);
617  nYAcc = nT1;
618  }
619  else
620  {
621  nLastImageY = ++nImageY;
622  nYAcc = nImageY;
623  }
624 
625  // line starts from the beginning
626  nImageX = 0;
627  }
628 
629  if( nImageY < nImageHeight )
630  {
631  const sal_uInt8 cTmp = pBytes[ i ];
632 
633  if( bGCTransparent )
634  {
635  if( cTmp == nGCTransparentIndex )
636  pAcc1->SetPixelIndex( nYAcc, nImageX++, cTransIndex1 );
637  else
638  {
639  pAcc8->SetPixelIndex( nYAcc, nImageX, cTmp );
640  pAcc1->SetPixelIndex( nYAcc, nImageX++, cNonTransIndex1 );
641  }
642  }
643  else
644  pAcc8->SetPixelIndex( nYAcc, nImageX++, cTmp );
645  }
646  else
647  {
648  bOverreadBlock = true;
649  break;
650  }
651  }
652 }
653 
654 void GIFReader::CreateNewBitmaps()
655 {
656  AnimationBitmap aAnimationBitmap;
657 
658  pAcc8.reset();
659 
660  if( bGCTransparent )
661  {
662  pAcc1.reset();
663  aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8, aBmp1 );
664  }
665  else
666  aAnimationBitmap.maBitmapEx = BitmapEx( aBmp8 );
667 
668  aAnimationBitmap.maPositionPixel = Point( nImagePosX, nImagePosY );
669  aAnimationBitmap.maSizePixel = Size( nImageWidth, nImageHeight );
670  aAnimationBitmap.mnWait = ( nTimer != 65535 ) ? nTimer : ANIMATION_TIMEOUT_ON_CLICK;
671  aAnimationBitmap.mbUserInput = false;
672 
673  // tdf#104121 . Internet Explorer, Firefox, Chrome and Safari all set a minimum default playback speed.
674  // IE10 Consumer Preview sets default of 100ms for rates less that 20ms. We do the same
675  if (aAnimationBitmap.mnWait < 2) // 20ms, specified in 100's of a second
676  aAnimationBitmap.mnWait = 10;
677 
678  if( nGCDisposalMethod == 2 )
679  aAnimationBitmap.meDisposal = Disposal::Back;
680  else if( nGCDisposalMethod == 3 )
681  aAnimationBitmap.meDisposal = Disposal::Previous;
682  else
683  aAnimationBitmap.meDisposal = Disposal::Not;
684 
685  nAnimationByteSize += aAnimationBitmap.maBitmapEx.GetSizeBytes();
686  nAnimationMinFileData += static_cast<sal_uInt64>(nImageWidth) * nImageHeight / 2560;
687  aAnimation.Insert(aAnimationBitmap);
688 
689  if( aAnimation.Count() == 1 )
690  {
691  aAnimation.SetDisplaySizePixel( Size( nGlobalWidth, nGlobalHeight ) );
692  aAnimation.SetLoopCount( nLoops );
693  }
694 }
695 
696 Graphic GIFReader::GetIntermediateGraphic()
697 {
698  Graphic aImGraphic;
699 
700  // only create intermediate graphic, if data is available
701  // but graphic still not completely read
702  if ( bImGraphicReady && !aAnimation.Count() )
703  {
704  pAcc8.reset();
705 
706  if ( bGCTransparent )
707  {
708  pAcc1.reset();
709  aImGraphic = BitmapEx( aBmp8, aBmp1 );
710 
711  pAcc1 = BitmapScopedWriteAccess(aBmp1);
712  bStatus = bStatus && pAcc1;
713  }
714  else
715  aImGraphic = BitmapEx(aBmp8);
716 
717  pAcc8 = BitmapScopedWriteAccess(aBmp8);
718  bStatus = bStatus && pAcc8;
719  }
720 
721  return aImGraphic;
722 }
723 
724 bool GIFReader::ProcessGIF()
725 {
726  bool bRead = false;
727  bool bEnd = false;
728 
729  if ( !bStatus )
730  eActAction = ABORT_READING;
731 
732  // set stream to right position
733  rIStm.Seek( nLastPos );
734 
735  switch( eActAction )
736  {
737  // read next marker
738  case MARKER_READING:
739  {
740  sal_uInt8 cByte;
741 
742  rIStm.ReadUChar( cByte );
743 
744  if( rIStm.eof() )
745  eActAction = END_READING;
746  else if( NO_PENDING( rIStm ) )
747  {
748  bRead = true;
749 
750  if( cByte == '!' )
751  eActAction = EXTENSION_READING;
752  else if( cByte == ',' )
753  eActAction = LOCAL_HEADER_READING;
754  else if( cByte == ';' )
755  eActAction = END_READING;
756  else
757  eActAction = ABORT_READING;
758  }
759  }
760  break;
761 
762  // read ScreenDescriptor
763  case GLOBAL_HEADER_READING:
764  {
765  bRead = ReadGlobalHeader();
766  if( bRead )
767  {
768  ClearImageExtensions();
769  eActAction = MARKER_READING;
770  }
771  }
772  break;
773 
774  // read extension
775  case EXTENSION_READING:
776  {
777  bRead = ReadExtension();
778  if( bRead )
779  eActAction = MARKER_READING;
780  }
781  break;
782 
783  // read Image-Descriptor
784  case LOCAL_HEADER_READING:
785  {
786  bRead = ReadLocalHeader();
787  if( bRead )
788  {
789  nYAcc = nImageX = nImageY = 0;
790  eActAction = FIRST_BLOCK_READING;
791  }
792  }
793  break;
794 
795  // read first data block
796  case FIRST_BLOCK_READING:
797  {
798  sal_uInt8 cDataSize;
799 
800  rIStm.ReadUChar( cDataSize );
801 
802  if( rIStm.eof() )
803  eActAction = ABORT_READING;
804  else if( cDataSize > 12 )
805  bStatus = false;
806  else if( NO_PENDING( rIStm ) )
807  {
808  bRead = true;
809  pDecomp = std::make_unique<GIFLZWDecompressor>( cDataSize );
810  eActAction = NEXT_BLOCK_READING;
811  bOverreadBlock = false;
812  }
813  else
814  eActAction = FIRST_BLOCK_READING;
815  }
816  break;
817 
818  // read next data block
819  case NEXT_BLOCK_READING:
820  {
821  sal_uInt16 nLastX = nImageX;
822  sal_uInt16 nLastY = nImageY;
823  sal_uLong nRet = ReadNextBlock();
824 
825  // Return: 0:Pending / 1:OK; / 2:OK and last block: / 3:EOI / 4:HardAbort
826  if( nRet )
827  {
828  bRead = true;
829 
830  if ( nRet == 1 )
831  {
832  bImGraphicReady = true;
833  eActAction = NEXT_BLOCK_READING;
834  bOverreadBlock = false;
835  }
836  else
837  {
838  if( nRet == 2 )
839  {
840  pDecomp.reset();
841  CreateNewBitmaps();
842  eActAction = MARKER_READING;
843  ClearImageExtensions();
844  }
845  else if( nRet == 3 )
846  {
847  eActAction = NEXT_BLOCK_READING;
848  bOverreadBlock = true;
849  }
850  else
851  {
852  pDecomp.reset();
853  CreateNewBitmaps();
854  eActAction = ABORT_READING;
855  ClearImageExtensions();
856  }
857  }
858  }
859  else
860  {
861  nImageX = nLastX;
862  nImageY = nLastY;
863  }
864  }
865  break;
866 
867  // an error occurred
868  case ABORT_READING:
869  {
870  bEnd = true;
871  eActAction = END_READING;
872  }
873  break;
874 
875  default:
876  break;
877  }
878 
879  // set stream to right position,
880  // if data could be read put it at the old
881  // position otherwise at the actual one
882  if( bRead || bEnd )
883  nLastPos = rIStm.Tell();
884 
885  return bRead;
886 }
887 
888 bool GIFReader::ReadIsAnimated()
889 {
890  ReadState eReadState;
891 
892  bStatus = true;
893 
894  while( ProcessGIF() && ( eActAction != END_READING ) ) {}
895 
896  if( !bStatus )
897  eReadState = GIFREAD_ERROR;
898  else if( eActAction == END_READING )
899  eReadState = GIFREAD_OK;
900  else
901  {
902  if ( rIStm.GetError() == ERRCODE_IO_PENDING )
903  rIStm.ResetError();
904 
905  eReadState = GIFREAD_NEED_MORE;
906  }
907 
908  if (eReadState == GIFREAD_OK)
909  return aAnimation.Count() > 1;
910  return false;
911 }
912 
913 ReadState GIFReader::ReadGIF( Graphic& rGraphic )
914 {
915  ReadState eReadState;
916 
917  bStatus = true;
918 
919  while( ProcessGIF() && ( eActAction != END_READING ) ) {}
920 
921  if( !bStatus )
922  eReadState = GIFREAD_ERROR;
923  else if( eActAction == END_READING )
924  eReadState = GIFREAD_OK;
925  else
926  {
927  if ( rIStm.GetError() == ERRCODE_IO_PENDING )
928  rIStm.ResetError();
929 
930  eReadState = GIFREAD_NEED_MORE;
931  }
932 
933  if( aAnimation.Count() == 1 )
934  {
935  rGraphic = aAnimation.Get(0).maBitmapEx;
936 
937  if( nLogWidth100 && nLogHeight100 )
938  {
939  rGraphic.SetPrefSize( Size( nLogWidth100, nLogHeight100 ) );
940  rGraphic.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
941  }
942  }
943  else
944  rGraphic = aAnimation;
945 
946  return eReadState;
947 }
948 
950 {
951  GIFReader aReader(rStm);
952 
953  SvStreamEndian nOldFormat = rStm.GetEndian();
954  rStm.SetEndian(SvStreamEndian::LITTLE);
955  bool bResult = aReader.ReadIsAnimated();
956  rStm.SetEndian(nOldFormat);
957 
958  return bResult;
959 }
960 
961 VCL_DLLPUBLIC bool ImportGIF( SvStream & rStm, Graphic& rGraphic )
962 {
963  std::shared_ptr<GraphicReader> pContext = rGraphic.GetReaderContext();
964  rGraphic.SetReaderContext(nullptr);
965  GIFReader* pGIFReader = dynamic_cast<GIFReader*>( pContext.get() );
966  if (!pGIFReader)
967  {
968  pContext = std::make_shared<GIFReader>( rStm );
969  pGIFReader = static_cast<GIFReader*>( pContext.get() );
970  }
971 
972  SvStreamEndian nOldFormat = rStm.GetEndian();
973  rStm.SetEndian( SvStreamEndian::LITTLE );
974 
975  bool bRet = true;
976 
977  ReadState eReadState = pGIFReader->ReadGIF(rGraphic);
978 
979  if (eReadState == GIFREAD_ERROR)
980  {
981  bRet = false;
982  }
983  else if (eReadState == GIFREAD_NEED_MORE)
984  {
985  rGraphic = pGIFReader->GetIntermediateGraphic();
986  rGraphic.SetReaderContext(pContext);
987  }
988 
989  rStm.SetEndian(nOldFormat);
990 
991  return bRet;
992 }
993 
994 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void SetBlue(sal_uInt8 nBlue)
SvStream & ReadUInt16(sal_uInt16 &rUInt16)
#define NO_PENDING(rStm)
Definition: gifread.cxx:28
#define VCL_DLLPUBLIC
Definition: dllapi.h:29
sal_uIntPtr sal_uLong
void SetPrefMapMode(const MapMode &rPrefMapMode)
Definition: graph.cxx:375
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap,&Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
ReadState
Definition: gifread.cxx:44
This template handles BitmapAccess the RAII way.
void SetReaderContext(const std::shared_ptr< GraphicReader > &pReader)
Definition: graph.cxx:477
VCL_DLLPUBLIC bool ImportGIF(SvStream &rStm, Graphic &rGraphic)
Definition: gifread.cxx:961
int i
sal_uLong GetSizeBytes() const
Definition: bitmapex.cxx:278
void SetRed(sal_uInt8 nRed)
SvStream & ReadUChar(unsigned char &rChar)
#define ANIMATION_TIMEOUT_ON_CLICK
Definition: Animation.hxx:27
SvStreamEndian GetEndian() const
unsigned char sal_uInt8
void SetGreen(sal_uInt8 nGreen)
void SetEndian(SvStreamEndian SvStreamEndian)
const ::std::vector< Color > ImpSvNumberformatScan::StandardColor COL_WHITE
std::shared_ptr< GraphicReader > & GetReaderContext()
Definition: graph.cxx:472
const ::std::vector< Color > ImpSvNumberformatScan::StandardColor COL_BLACK
void SetPrefSize(const Size &rPrefSize)
Definition: graph.cxx:364
#define ERRCODE_IO_PENDING
Definition: errcode.hxx:227
GIFAction
Definition: gifread.cxx:32
#define SAL_WARN(area, stream)
SvStreamEndian
void SetBuffer(void *pBuf, std::size_t nSize, std::size_t nEOF)
OString read_uInt8s_ToOString(SvStream &rStrm, std::size_t nLen)
bool IsGIFAnimated(SvStream &rStm)
Definition: gifread.cxx:949