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