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 .
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"
30 #include <filter/GifWriter.hxx>
38 BitmapScopedReadAccess m_pAcc
;
39 sal_uInt32 nMinPercent
;
40 sal_uInt32 nMaxPercent
;
41 sal_uInt32 nLastPercent
;
44 sal_Int32 nInterlaced
;
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();
57 void WriteTerminator();
59 bool CreateAccess( const BitmapEx
& rBmpEx
);
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
);
66 css::uno::Reference
< css::task::XStatusIndicator
> xStatusIndicator
;
70 explicit GIFWriter(SvStream
&rStream
);
72 bool WriteGIF( const Graphic
& rGraphic
, FilterConfigItem
* pConfigItem
);
77 GIFWriter::GIFWriter(SvStream
&rStream
)
91 bool GIFWriter::WriteGIF(const Graphic
& rGraphic
, FilterConfigItem
* pFilterConfigItem
)
93 if ( pFilterConfigItem
)
95 xStatusIndicator
= pFilterConfigItem
->GetStatusIndicator();
96 if ( xStatusIndicator
.is() )
98 xStatusIndicator
->start( OUString(), 100 );
103 const MapMode
aMap( rGraphic
.GetPrefMapMode() );
104 bool bLogSize
= ( aMap
.GetMapUnit() != MapUnit::MapPixel
);
107 aSize100
= OutputDevice::LogicToLogic(rGraphic
.GetPrefSize(), aMap
, MapMode(MapUnit::Map100thMM
));
114 if ( pFilterConfigItem
)
115 nInterlaced
= pFilterConfigItem
->ReadInt32( u
"Interlaced"_ustr
, 0 );
117 m_rGIF
.SetEndian( SvStreamEndian::LITTLE
);
119 if( rGraphic
.IsAnimated() )
121 const Animation aAnimation
= rGraphic
.GetAnimation();
123 WriteSignature( true );
127 WriteGlobalHeader( aAnimation
.GetDisplaySizePixel() );
131 WriteLoopExtension( aAnimation
);
134 WriteAnimation( aAnimation
);
140 const bool bGrafTrans
= rGraphic
.IsTransparent();
142 BitmapEx aBmpEx
= rGraphic
.GetBitmapEx();
147 WriteSignature( bGrafTrans
|| bLogSize
);
151 WriteGlobalHeader( aBmpEx
.GetSizePixel() );
154 WriteBitmapEx( aBmpEx
, Point(), bGrafTrans
);
161 WriteLogSizeExtension( aSize100
);
166 if ( xStatusIndicator
.is() )
167 xStatusIndicator
->end();
173 void GIFWriter::WriteBitmapEx( const BitmapEx
& rBmpEx
, const Point
& rPoint
,
174 bool bExtended
, tools::Long nTimer
, Disposal eDisposal
)
176 if( !CreateAccess( rBmpEx
) )
183 WriteImageExtension( nTimer
, eDisposal
);
202 void GIFWriter::WriteAnimation( const Animation
& rAnimation
)
204 const sal_uInt16 nCount
= rAnimation
.Count();
209 const double fStep
= 100. / nCount
;
212 nMaxPercent
= static_cast<sal_uInt32
>(fStep
);
214 for( sal_uInt16 i
= 0; i
< nCount
; i
++ )
216 const AnimationFrame
& rAnimationFrame
= rAnimation
.Get( i
);
218 WriteBitmapEx(rAnimationFrame
.maBitmapEx
, rAnimationFrame
.maPositionPixel
, true,
219 rAnimationFrame
.mnWait
, rAnimationFrame
.meDisposal
);
220 nMinPercent
= nMaxPercent
;
221 nMaxPercent
= static_cast<sal_uInt32
>(nMaxPercent
+ fStep
);
226 void GIFWriter::MayCallback(sal_uInt32 nPercent
)
228 if ( xStatusIndicator
.is() )
230 if( nPercent
>= nLastPercent
+ 3 )
232 nLastPercent
= nPercent
;
233 if ( nPercent
<= 100 )
234 xStatusIndicator
->setValue( nPercent
);
240 bool GIFWriter::CreateAccess( const BitmapEx
& rBmpEx
)
244 AlphaMask
aMask( rBmpEx
.GetAlphaMask() );
246 aAccBmp
= rBmpEx
.GetBitmap();
247 bTransparent
= false;
249 if( !aMask
.IsEmpty() )
251 if( aAccBmp
.Convert( BmpConversion::N8BitTrans
) )
253 aMask
.Convert( BmpConversion::N1BitThreshold
);
255 aAccBmp
.ReplaceMask( aMask
, BMP_COL_TRANS
);
259 aAccBmp
.Convert( BmpConversion::N8BitColors
);
262 aAccBmp
.Convert( BmpConversion::N8BitColors
);
274 void GIFWriter::DestroyAccess()
280 void GIFWriter::WriteSignature( bool bGIF89a
)
284 m_rGIF
.WriteBytes(bGIF89a
? "GIF89a" : "GIF87a" , 6);
286 if( m_rGIF
.GetError() )
292 void GIFWriter::WriteGlobalHeader( const Size
& rSize
)
298 const sal_uInt16 nWidth
= static_cast<sal_uInt16
>(rSize
.Width());
299 const sal_uInt16 nHeight
= static_cast<sal_uInt16
>(rSize
.Height());
300 const sal_uInt8 cFlags
= 128 | ( 7 << 4 );
303 m_rGIF
.WriteUInt16( nWidth
);
304 m_rGIF
.WriteUInt16( nHeight
);
305 m_rGIF
.WriteUChar( cFlags
);
306 m_rGIF
.WriteUChar( 0x00 );
307 m_rGIF
.WriteUChar( 0x00 );
309 // write dummy palette with two entries (black/white);
310 // we do this only because of a bug in Photoshop, since those can't
311 // read pictures without a global color palette
312 m_rGIF
.WriteUInt16( 0 );
313 m_rGIF
.WriteUInt16( 255 );
314 m_rGIF
.WriteUInt16( 65535 );
316 if( m_rGIF
.GetError() )
321 void GIFWriter::WriteLoopExtension( const Animation
& rAnimation
)
323 DBG_ASSERT( rAnimation
.Count() > 0, "Animation has no bitmaps!" );
325 sal_uInt16 nLoopCount
= static_cast<sal_uInt16
>(rAnimation
.GetLoopCount());
327 // if only one run should take place
328 // the LoopExtension won't be written
329 // The default in this case is a single run
330 if( nLoopCount
== 1 )
333 // Netscape interprets the LoopCount
334 // as the sole number of _repetitions_
338 const sal_uInt8 cLoByte
= static_cast<sal_uInt8
>(nLoopCount
);
339 const sal_uInt8 cHiByte
= static_cast<sal_uInt8
>( nLoopCount
>> 8 );
341 m_rGIF
.WriteUChar( 0x21 );
342 m_rGIF
.WriteUChar( 0xff );
343 m_rGIF
.WriteUChar( 0x0b );
344 m_rGIF
.WriteBytes( "NETSCAPE2.0", 11 );
345 m_rGIF
.WriteUChar( 0x03 );
346 m_rGIF
.WriteUChar( 0x01 );
347 m_rGIF
.WriteUChar( cLoByte
);
348 m_rGIF
.WriteUChar( cHiByte
);
349 m_rGIF
.WriteUChar( 0x00 );
353 void GIFWriter::WriteLogSizeExtension( const Size
& rSize100
)
355 // writer PrefSize in 100th-mm as ApplicationExtension
356 if( rSize100
.Width() && rSize100
.Height() )
358 m_rGIF
.WriteUChar( 0x21 );
359 m_rGIF
.WriteUChar( 0xff );
360 m_rGIF
.WriteUChar( 0x0b );
361 m_rGIF
.WriteBytes( "STARDIV 5.0", 11 );
362 m_rGIF
.WriteUChar( 0x09 );
363 m_rGIF
.WriteUChar( 0x01 );
364 m_rGIF
.WriteUInt32( rSize100
.Width() );
365 m_rGIF
.WriteUInt32( rSize100
.Height() );
366 m_rGIF
.WriteUChar( 0x00 );
371 void GIFWriter::WriteImageExtension( tools::Long nTimer
, Disposal eDisposal
)
376 const sal_uInt16 nDelay
= static_cast<sal_uInt16
>(nTimer
);
377 sal_uInt8 cFlags
= 0;
379 // set Transparency-Flag
383 // set Disposal-value
384 if( eDisposal
== Disposal::Back
)
385 cFlags
|= ( 2 << 2 );
386 else if( eDisposal
== Disposal::Previous
)
387 cFlags
|= ( 3 << 2 );
389 m_rGIF
.WriteUChar( 0x21 );
390 m_rGIF
.WriteUChar( 0xf9 );
391 m_rGIF
.WriteUChar( 0x04 );
392 m_rGIF
.WriteUChar( cFlags
);
393 m_rGIF
.WriteUInt16( nDelay
);
394 m_rGIF
.WriteUChar( static_cast<sal_uInt8
>(m_pAcc
->GetBestPaletteIndex( BMP_COL_TRANS
)) );
395 m_rGIF
.WriteUChar( 0x00 );
397 if( m_rGIF
.GetError() )
402 void GIFWriter::WriteLocalHeader()
407 const sal_uInt16 nPosX
= static_cast<sal_uInt16
>(nActX
);
408 const sal_uInt16 nPosY
= static_cast<sal_uInt16
>(nActY
);
409 const sal_uInt16 nWidth
= static_cast<sal_uInt16
>(m_pAcc
->Width());
410 const sal_uInt16 nHeight
= static_cast<sal_uInt16
>(m_pAcc
->Height());
411 sal_uInt8 cFlags
= static_cast<sal_uInt8
>( m_pAcc
->GetBitCount() - 1 );
413 // set Interlaced-Flag
417 // set Flag for the local color palette
420 m_rGIF
.WriteUChar( 0x2c );
421 m_rGIF
.WriteUInt16( nPosX
);
422 m_rGIF
.WriteUInt16( nPosY
);
423 m_rGIF
.WriteUInt16( nWidth
);
424 m_rGIF
.WriteUInt16( nHeight
);
425 m_rGIF
.WriteUChar( cFlags
);
427 if( m_rGIF
.GetError() )
432 void GIFWriter::WritePalette()
434 if( !(bStatus
&& m_pAcc
->HasPalette()) )
437 const sal_uInt16 nCount
= m_pAcc
->GetPaletteEntryCount();
438 const sal_uInt16 nMaxCount
= ( 1 << m_pAcc
->GetBitCount() );
440 for ( sal_uInt16 i
= 0; i
< nCount
; i
++ )
442 const BitmapColor
& rColor
= m_pAcc
->GetPaletteColor( i
);
444 m_rGIF
.WriteUChar( rColor
.GetRed() );
445 m_rGIF
.WriteUChar( rColor
.GetGreen() );
446 m_rGIF
.WriteUChar( rColor
.GetBlue() );
449 // fill up the rest with 0
450 if( nCount
< nMaxCount
)
451 m_rGIF
.SeekRel( ( nMaxCount
- nCount
) * 3 );
453 if( m_rGIF
.GetError() )
458 void GIFWriter::WriteAccess()
460 GIFLZWCompressor aCompressor
;
461 const tools::Long nWidth
= m_pAcc
->Width();
462 const tools::Long nHeight
= m_pAcc
->Height();
463 std::unique_ptr
<sal_uInt8
[]> pBuffer
;
464 bool bNative
= m_pAcc
->GetScanlineFormat() == ScanlineFormat::N8BitPal
;
467 pBuffer
.reset(new sal_uInt8
[ nWidth
]);
469 assert(bStatus
&& "should not calling here if status is bad");
470 assert( 8 == m_pAcc
->GetBitCount() && m_pAcc
->HasPalette()
471 && "by the time we get here, the image should be in palette format");
472 if( !(bStatus
&& ( 8 == m_pAcc
->GetBitCount() ) && m_pAcc
->HasPalette()) )
475 aCompressor
.StartCompression( m_rGIF
, m_pAcc
->GetBitCount() );
479 for( tools::Long i
= 0; i
< nHeight
; ++i
)
487 nT
= i
- ( ( nHeight
+ 7 ) >> 3 );
492 nT
-= ( nHeight
+ 3 ) >> 3;
493 nY
= ( nT
<< 2 ) + 2;
497 nT
-= ( ( nHeight
+ 1 ) >> 2 );
498 nY
= ( nT
<< 1 ) + 1;
507 aCompressor
.Compress( m_pAcc
->GetScanline( nY
), nWidth
);
510 Scanline pScanline
= m_pAcc
->GetScanline( nY
);
511 for( tools::Long nX
= 0; nX
< nWidth
; nX
++ )
512 pBuffer
[ nX
] = m_pAcc
->GetIndexFromData( pScanline
, nX
);
514 aCompressor
.Compress( pBuffer
.get(), nWidth
);
517 if ( m_rGIF
.GetError() )
520 MayCallback( nMinPercent
+ ( nMaxPercent
- nMinPercent
) * i
/ nHeight
);
526 aCompressor
.EndCompression();
528 if ( m_rGIF
.GetError() )
533 void GIFWriter::WriteTerminator()
537 m_rGIF
.WriteUChar( 0x3b );
539 if( m_rGIF
.GetError() )
545 bool ExportGifGraphic(SvStream
& rStream
, const Graphic
& rGraphic
, FilterConfigItem
* pConfigItem
)
547 GIFWriter
aWriter(rStream
);
548 return aWriter
.WriteGIF(rGraphic
, pConfigItem
);
551 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */