LibreOffice Module vcl (master)  1
egif.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 
21 #include <tools/stream.hxx>
22 #include <tools/debug.hxx>
23 #include <vcl/BitmapReadAccess.hxx>
24 #include <vcl/graph.hxx>
25 #include <vcl/outdev.hxx>
26 #include <vcl/FilterConfigItem.hxx>
27 #include <com/sun/star/task/XStatusIndicator.hpp>
28 #include "giflzwc.hxx"
29 #include <memory>
30 #include <filter/GifWriter.hxx>
31 
32 namespace {
33 
34 class GIFWriter
35 {
36  Bitmap aAccBmp;
37  SvStream& m_rGIF;
38  BitmapReadAccess* m_pAcc;
39  sal_uInt32 nMinPercent;
40  sal_uInt32 nMaxPercent;
41  sal_uInt32 nLastPercent;
42  tools::Long nActX;
43  tools::Long nActY;
44  sal_Int32 nInterlaced;
45  bool bStatus;
46  bool bTransparent;
47 
48  void MayCallback(sal_uInt32 nPercent);
49  void WriteSignature( bool bGIF89a );
50  void WriteGlobalHeader( const Size& rSize );
51  void WriteLoopExtension( const Animation& rAnimation );
52  void WriteLogSizeExtension( const Size& rSize100 );
53  void WriteImageExtension( tools::Long nTimer, Disposal eDisposal );
54  void WriteLocalHeader();
55  void WritePalette();
56  void WriteAccess();
57  void WriteTerminator();
58 
59  bool CreateAccess( const BitmapEx& rBmpEx );
60  void DestroyAccess();
61 
62  void WriteAnimation( const Animation& rAnimation );
63  void WriteBitmapEx( const BitmapEx& rBmpEx, const Point& rPoint, bool bExtended,
64  tools::Long nTimer = 0, Disposal eDisposal = Disposal::Not );
65 
66  css::uno::Reference< css::task::XStatusIndicator > xStatusIndicator;
67 
68 public:
69 
70  explicit GIFWriter(SvStream &rStream);
71 
72  bool WriteGIF( const Graphic& rGraphic, FilterConfigItem* pConfigItem );
73 };
74 
75 }
76 
77 GIFWriter::GIFWriter(SvStream &rStream)
78  : m_rGIF(rStream)
79  , m_pAcc(nullptr)
80  , nMinPercent(0)
81  , nMaxPercent(0)
82  , nLastPercent(0)
83  , nActX(0)
84  , nActY(0)
85  , nInterlaced(0)
86  , bStatus(false)
87  , bTransparent(false)
88 {
89 }
90 
91 
92 bool GIFWriter::WriteGIF(const Graphic& rGraphic, FilterConfigItem* pFilterConfigItem)
93 {
94  if ( pFilterConfigItem )
95  {
96  xStatusIndicator = pFilterConfigItem->GetStatusIndicator();
97  if ( xStatusIndicator.is() )
98  {
99  xStatusIndicator->start( OUString(), 100 );
100  }
101  }
102 
103  Size aSize100;
104  const MapMode aMap( rGraphic.GetPrefMapMode() );
105  bool bLogSize = ( aMap.GetMapUnit() != MapUnit::MapPixel );
106 
107  if( bLogSize )
108  aSize100 = OutputDevice::LogicToLogic(rGraphic.GetPrefSize(), aMap, MapMode(MapUnit::Map100thMM));
109 
110  bStatus = true;
111  nLastPercent = 0;
112  nInterlaced = 0;
113  m_pAcc = nullptr;
114 
115  if ( pFilterConfigItem )
116  nInterlaced = pFilterConfigItem->ReadInt32( "Interlaced", 0 );
117 
118  m_rGIF.SetEndian( SvStreamEndian::LITTLE );
119 
120  if( rGraphic.IsAnimated() )
121  {
122  const Animation& rAnimation = rGraphic.GetAnimation();
123 
124  WriteSignature( true );
125 
126  if ( bStatus )
127  {
128  WriteGlobalHeader( rAnimation.GetDisplaySizePixel() );
129 
130  if( bStatus )
131  {
132  WriteLoopExtension( rAnimation );
133 
134  if( bStatus )
135  WriteAnimation( rAnimation );
136  }
137  }
138  }
139  else
140  {
141  const bool bGrafTrans = rGraphic.IsTransparent();
142 
143  BitmapEx aBmpEx = rGraphic.GetBitmapEx();
144 
145  nMinPercent = 0;
146  nMaxPercent = 100;
147 
148  WriteSignature( bGrafTrans || bLogSize );
149 
150  if( bStatus )
151  {
152  WriteGlobalHeader( aBmpEx.GetSizePixel() );
153 
154  if( bStatus )
155  WriteBitmapEx( aBmpEx, Point(), bGrafTrans );
156  }
157  }
158 
159  if( bStatus )
160  {
161  if( bLogSize )
162  WriteLogSizeExtension( aSize100 );
163 
164  WriteTerminator();
165  }
166 
167  if ( xStatusIndicator.is() )
168  xStatusIndicator->end();
169 
170  return bStatus;
171 }
172 
173 
174 void GIFWriter::WriteBitmapEx( const BitmapEx& rBmpEx, const Point& rPoint,
175  bool bExtended, tools::Long nTimer, Disposal eDisposal )
176 {
177  if( !CreateAccess( rBmpEx ) )
178  return;
179 
180  nActX = rPoint.X();
181  nActY = rPoint.Y();
182 
183  if( bExtended )
184  WriteImageExtension( nTimer, eDisposal );
185 
186  if( bStatus )
187  {
188  WriteLocalHeader();
189 
190  if( bStatus )
191  {
192  WritePalette();
193 
194  if( bStatus )
195  WriteAccess();
196  }
197  }
198 
199  DestroyAccess();
200 }
201 
202 
203 void GIFWriter::WriteAnimation( const Animation& rAnimation )
204 {
205  const sal_uInt16 nCount = rAnimation.Count();
206 
207  if( !nCount )
208  return;
209 
210  const double fStep = 100. / nCount;
211 
212  nMinPercent = 0;
213  nMaxPercent = static_cast<sal_uInt32>(fStep);
214 
215  for( sal_uInt16 i = 0; i < nCount; i++ )
216  {
217  const AnimationBitmap& rAnimationBitmap = rAnimation.Get( i );
218 
219  WriteBitmapEx(rAnimationBitmap.maBitmapEx, rAnimationBitmap.maPositionPixel, true,
220  rAnimationBitmap.mnWait, rAnimationBitmap.meDisposal );
221  nMinPercent = nMaxPercent;
222  nMaxPercent = static_cast<sal_uInt32>(nMaxPercent + fStep);
223  }
224 }
225 
226 
227 void GIFWriter::MayCallback(sal_uInt32 nPercent)
228 {
229  if ( xStatusIndicator.is() )
230  {
231  if( nPercent >= nLastPercent + 3 )
232  {
233  nLastPercent = nPercent;
234  if ( nPercent <= 100 )
235  xStatusIndicator->setValue( nPercent );
236  }
237  }
238 }
239 
240 
241 bool GIFWriter::CreateAccess( const BitmapEx& rBmpEx )
242 {
243  if( bStatus )
244  {
245  Bitmap aMask( rBmpEx.GetMask() );
246 
247  aAccBmp = rBmpEx.GetBitmap();
248  bTransparent = false;
249 
250  if( !aMask.IsEmpty() )
251  {
252  if( aAccBmp.Convert( BmpConversion::N8BitTrans ) )
253  {
255  aAccBmp.Replace( aMask, BMP_COL_TRANS );
256  bTransparent = true;
257  }
258  else
259  aAccBmp.Convert( BmpConversion::N8BitColors );
260  }
261  else
262  aAccBmp.Convert( BmpConversion::N8BitColors );
263 
264  m_pAcc = aAccBmp.AcquireReadAccess();
265 
266  if( !m_pAcc )
267  bStatus = false;
268  }
269 
270  return bStatus;
271 }
272 
273 
274 void GIFWriter::DestroyAccess()
275 {
276  Bitmap::ReleaseAccess( m_pAcc );
277  m_pAcc = nullptr;
278 }
279 
280 
281 void GIFWriter::WriteSignature( bool bGIF89a )
282 {
283  if( bStatus )
284  {
285  m_rGIF.WriteBytes(bGIF89a ? "GIF89a" : "GIF87a" , 6);
286 
287  if( m_rGIF.GetError() )
288  bStatus = false;
289  }
290 }
291 
292 
293 void GIFWriter::WriteGlobalHeader( const Size& rSize )
294 {
295  if( !bStatus )
296  return;
297 
298  // 256 colors
299  const sal_uInt16 nWidth = static_cast<sal_uInt16>(rSize.Width());
300  const sal_uInt16 nHeight = static_cast<sal_uInt16>(rSize.Height());
301  const sal_uInt8 cFlags = 128 | ( 7 << 4 );
302 
303  // write values
304  m_rGIF.WriteUInt16( nWidth );
305  m_rGIF.WriteUInt16( nHeight );
306  m_rGIF.WriteUChar( cFlags );
307  m_rGIF.WriteUChar( 0x00 );
308  m_rGIF.WriteUChar( 0x00 );
309 
310  // write dummy palette with two entries (black/white);
311  // we do this only because of a bug in Photoshop, since those can't
312  // read pictures without a global color palette
313  m_rGIF.WriteUInt16( 0 );
314  m_rGIF.WriteUInt16( 255 );
315  m_rGIF.WriteUInt16( 65535 );
316 
317  if( m_rGIF.GetError() )
318  bStatus = false;
319 }
320 
321 
322 void GIFWriter::WriteLoopExtension( const Animation& rAnimation )
323 {
324  DBG_ASSERT( rAnimation.Count() > 0, "Animation has no bitmaps!" );
325 
326  sal_uInt16 nLoopCount = static_cast<sal_uInt16>(rAnimation.GetLoopCount());
327 
328  // if only one run should take place
329  // the LoopExtension won't be written
330  // The default in this case is a single run
331  if( nLoopCount == 1 )
332  return;
333 
334  // Netscape interprets the LoopCount
335  // as the sole number of _repetitions_
336  if( nLoopCount )
337  nLoopCount--;
338 
339  const sal_uInt8 cLoByte = static_cast<sal_uInt8>(nLoopCount);
340  const sal_uInt8 cHiByte = static_cast<sal_uInt8>( nLoopCount >> 8 );
341 
342  m_rGIF.WriteUChar( 0x21 );
343  m_rGIF.WriteUChar( 0xff );
344  m_rGIF.WriteUChar( 0x0b );
345  m_rGIF.WriteBytes( "NETSCAPE2.0", 11 );
346  m_rGIF.WriteUChar( 0x03 );
347  m_rGIF.WriteUChar( 0x01 );
348  m_rGIF.WriteUChar( cLoByte );
349  m_rGIF.WriteUChar( cHiByte );
350  m_rGIF.WriteUChar( 0x00 );
351 }
352 
353 
354 void GIFWriter::WriteLogSizeExtension( const Size& rSize100 )
355 {
356  // writer PrefSize in 100th-mm as ApplicationExtension
357  if( rSize100.Width() && rSize100.Height() )
358  {
359  m_rGIF.WriteUChar( 0x21 );
360  m_rGIF.WriteUChar( 0xff );
361  m_rGIF.WriteUChar( 0x0b );
362  m_rGIF.WriteBytes( "STARDIV 5.0", 11 );
363  m_rGIF.WriteUChar( 0x09 );
364  m_rGIF.WriteUChar( 0x01 );
365  m_rGIF.WriteUInt32( rSize100.Width() );
366  m_rGIF.WriteUInt32( rSize100.Height() );
367  m_rGIF.WriteUChar( 0x00 );
368  }
369 }
370 
371 
372 void GIFWriter::WriteImageExtension( tools::Long nTimer, Disposal eDisposal )
373 {
374  if( !bStatus )
375  return;
376 
377  const sal_uInt16 nDelay = static_cast<sal_uInt16>(nTimer);
378  sal_uInt8 cFlags = 0;
379 
380  // set Transparency-Flag
381  if( bTransparent )
382  cFlags |= 1;
383 
384  // set Disposal-value
385  if( eDisposal == Disposal::Back )
386  cFlags |= ( 2 << 2 );
387  else if( eDisposal == Disposal::Previous )
388  cFlags |= ( 3 << 2 );
389 
390  m_rGIF.WriteUChar( 0x21 );
391  m_rGIF.WriteUChar( 0xf9 );
392  m_rGIF.WriteUChar( 0x04 );
393  m_rGIF.WriteUChar( cFlags );
394  m_rGIF.WriteUInt16( nDelay );
395  m_rGIF.WriteUChar( m_pAcc->GetBestPaletteIndex( BMP_COL_TRANS ) );
396  m_rGIF.WriteUChar( 0x00 );
397 
398  if( m_rGIF.GetError() )
399  bStatus = false;
400 }
401 
402 
403 void GIFWriter::WriteLocalHeader()
404 {
405  if( !bStatus )
406  return;
407 
408  const sal_uInt16 nPosX = static_cast<sal_uInt16>(nActX);
409  const sal_uInt16 nPosY = static_cast<sal_uInt16>(nActY);
410  const sal_uInt16 nWidth = static_cast<sal_uInt16>(m_pAcc->Width());
411  const sal_uInt16 nHeight = static_cast<sal_uInt16>(m_pAcc->Height());
412  sal_uInt8 cFlags = static_cast<sal_uInt8>( m_pAcc->GetBitCount() - 1 );
413 
414  // set Interlaced-Flag
415  if( nInterlaced )
416  cFlags |= 0x40;
417 
418  // set Flag for the local color palette
419  cFlags |= 0x80;
420 
421  m_rGIF.WriteUChar( 0x2c );
422  m_rGIF.WriteUInt16( nPosX );
423  m_rGIF.WriteUInt16( nPosY );
424  m_rGIF.WriteUInt16( nWidth );
425  m_rGIF.WriteUInt16( nHeight );
426  m_rGIF.WriteUChar( cFlags );
427 
428  if( m_rGIF.GetError() )
429  bStatus = false;
430 }
431 
432 
433 void GIFWriter::WritePalette()
434 {
435  if( !(bStatus && m_pAcc->HasPalette()) )
436  return;
437 
438  const sal_uInt16 nCount = m_pAcc->GetPaletteEntryCount();
439  const sal_uInt16 nMaxCount = ( 1 << m_pAcc->GetBitCount() );
440 
441  for ( sal_uInt16 i = 0; i < nCount; i++ )
442  {
443  const BitmapColor& rColor = m_pAcc->GetPaletteColor( i );
444 
445  m_rGIF.WriteUChar( rColor.GetRed() );
446  m_rGIF.WriteUChar( rColor.GetGreen() );
447  m_rGIF.WriteUChar( rColor.GetBlue() );
448  }
449 
450  // fill up the rest with 0
451  if( nCount < nMaxCount )
452  m_rGIF.SeekRel( ( nMaxCount - nCount ) * 3 );
453 
454  if( m_rGIF.GetError() )
455  bStatus = false;
456 }
457 
458 
459 void GIFWriter::WriteAccess()
460 {
461  GIFLZWCompressor aCompressor;
462  const tools::Long nWidth = m_pAcc->Width();
463  const tools::Long nHeight = m_pAcc->Height();
464  std::unique_ptr<sal_uInt8[]> pBuffer;
465  bool bNative = m_pAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal;
466 
467  if( !bNative )
468  pBuffer.reset(new sal_uInt8[ nWidth ]);
469 
470  if( !(bStatus && ( 8 == m_pAcc->GetBitCount() ) && m_pAcc->HasPalette()) )
471  return;
472 
473  aCompressor.StartCompression( m_rGIF, m_pAcc->GetBitCount() );
474 
475  tools::Long nY, nT;
476 
477  for( tools::Long i = 0; i < nHeight; ++i )
478  {
479  if( nInterlaced )
480  {
481  nY = i << 3;
482 
483  if( nY >= nHeight )
484  {
485  nT = i - ( ( nHeight + 7 ) >> 3 );
486  nY= ( nT << 3 ) + 4;
487 
488  if( nY >= nHeight )
489  {
490  nT -= ( nHeight + 3 ) >> 3;
491  nY = ( nT << 2 ) + 2;
492 
493  if ( nY >= nHeight )
494  {
495  nT -= ( ( nHeight + 1 ) >> 2 );
496  nY = ( nT << 1 ) + 1;
497  }
498  }
499  }
500  }
501  else
502  nY = i;
503 
504  if( bNative )
505  aCompressor.Compress( m_pAcc->GetScanline( nY ), nWidth );
506  else
507  {
508  Scanline pScanline = m_pAcc->GetScanline( nY );
509  for( tools::Long nX = 0; nX < nWidth; nX++ )
510  pBuffer[ nX ] = m_pAcc->GetIndexFromData( pScanline, nX );
511 
512  aCompressor.Compress( pBuffer.get(), nWidth );
513  }
514 
515  if ( m_rGIF.GetError() )
516  bStatus = false;
517 
518  MayCallback( nMinPercent + ( nMaxPercent - nMinPercent ) * i / nHeight );
519 
520  if( !bStatus )
521  break;
522  }
523 
524  aCompressor.EndCompression();
525 
526  if ( m_rGIF.GetError() )
527  bStatus = false;
528 }
529 
530 
531 void GIFWriter::WriteTerminator()
532 {
533  if( bStatus )
534  {
535  m_rGIF.WriteUChar( 0x3b );
536 
537  if( m_rGIF.GetError() )
538  bStatus = false;
539  }
540 }
541 
542 
543 bool ExportGifGraphic(SvStream& rStream, Graphic& rGraphic, FilterConfigItem* pConfigItem)
544 {
545  GIFWriter aWriter(rStream);
546  return aWriter.WriteGIF(rGraphic, pConfigItem);
547 }
548 
549 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Bitmap GetMask() const
Definition: BitmapEx.cxx:246
#define BMP_COL_TRANS
Definition: bitmap.hxx:69
sal_uInt8 GetRed() const
Point LogicToLogic(const Point &rPtSource, const MapMode *pMapModeSource, const MapMode *pMapModeDest) const
Definition: map.cxx:1466
long Long
bool IsTransparent() const
Definition: graph.cxx:310
sal_Int32 ReadInt32(const OUString &rKey, sal_Int32 nDefault)
constexpr tools::Long Width() const
HashMap_OWString_Interface aMap
int nCount
sal_uInt8 GetBlue() const
bool IsAnimated() const
Definition: graph.cxx:320
Animation GetAnimation() const
Definition: graph.cxx:335
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
SvStream & WriteAnimation(SvStream &rOStm, const Animation &rAnimation)
Definition: Animation.cxx:551
#define DBG_ASSERT(sCon, aError)
int i
static void ReleaseAccess(BitmapInfoAccess *pAccess)
BitmapEx GetBitmapEx(const GraphicConversionParameters &rParameters=GraphicConversionParameters()) const
Definition: graph.cxx:330
const Size & GetDisplaySizePixel() const
Definition: Animation.hxx:55
const AnimationBitmap & Get(sal_uInt16 nAnimation) const
Definition: Animation.cxx:399
Bitmap GetBitmap(Color aTransparentReplaceColor) const
Definition: BitmapEx.cxx:229
Size GetPrefSize() const
Definition: graph.cxx:364
bool ExportGifGraphic(SvStream &rStream, Graphic &rGraphic, FilterConfigItem *pConfigItem)
Definition: egif.cxx:543
sal_uInt8 GetGreen() const
MapMode GetPrefMapMode() const
Definition: graph.cxx:375
constexpr tools::Long Height() const
unsigned char sal_uInt8
bool Convert(BmpConversion eConversion)
Convert bitmap format.
void EndCompression()
Definition: giflzwc.cxx:212
sal_uInt32 GetLoopCount() const
Definition: Animation.hxx:61
css::uno::Reference< css::task::XStatusIndicator > GetStatusIndicator() const
void Compress(sal_uInt8 *pSrc, sal_uInt32 nSize)
Definition: giflzwc.cxx:154
size_t Count() const
Definition: Animation.hxx:69
void StartCompression(SvStream &rGIF, sal_uInt16 nPixelSize)
Definition: giflzwc.cxx:123
const Size & GetSizePixel() const
Definition: bitmapex.hxx:83
tools::Long mnWait
Disposal