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 
GIFWriter(SvStream & rStream)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 
WriteGIF(const Graphic & rGraphic,FilterConfigItem * pFilterConfigItem)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 
WriteBitmapEx(const BitmapEx & rBmpEx,const Point & rPoint,bool bExtended,tools::Long nTimer,Disposal eDisposal)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 
WriteAnimation(const Animation & rAnimation)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 
MayCallback(sal_uInt32 nPercent)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 
CreateAccess(const BitmapEx & rBmpEx)241 bool GIFWriter::CreateAccess( const BitmapEx& rBmpEx )
242 {
243     if( bStatus )
244     {
245         Bitmap aMask( rBmpEx.GetAlpha() );
246 
247         aAccBmp = rBmpEx.GetBitmap();
248         bTransparent = false;
249 
250         if( !aMask.IsEmpty() )
251         {
252             if( aAccBmp.Convert( BmpConversion::N8BitTrans ) )
253             {
254                 aMask.Convert( BmpConversion::N1BitThreshold );
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 
DestroyAccess()274 void GIFWriter::DestroyAccess()
275 {
276     Bitmap::ReleaseAccess( m_pAcc );
277     m_pAcc = nullptr;
278 }
279 
280 
WriteSignature(bool bGIF89a)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 
WriteGlobalHeader(const Size & rSize)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 
WriteLoopExtension(const Animation & rAnimation)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 
WriteLogSizeExtension(const Size & rSize100)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 
WriteImageExtension(tools::Long nTimer,Disposal eDisposal)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 
WriteLocalHeader()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 
WritePalette()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 
WriteAccess()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 
WriteTerminator()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 
ExportGifGraphic(SvStream & rStream,Graphic & rGraphic,FilterConfigItem * pConfigItem)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: */
550