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>
24#include <vcl/graph.hxx>
25#include <vcl/outdev.hxx>
27#include <com/sun/star/task/XStatusIndicator.hpp>
28#include "giflzwc.hxx"
29#include <memory>
30#include <filter/GifWriter.hxx>
31
32namespace {
33
34class 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
68public:
69
70 explicit GIFWriter(SvStream &rStream);
71
72 bool WriteGIF( const Graphic& rGraphic, FilterConfigItem* pConfigItem );
73};
74
75}
76
77GIFWriter::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
92bool 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
174void 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
203void 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
227void 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
241bool 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 {
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
274void GIFWriter::DestroyAccess()
275{
276 Bitmap::ReleaseAccess( m_pAcc );
277 m_pAcc = nullptr;
278}
279
280
281void 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
293void 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
322void 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
354void 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
372void 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
403void 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
433void 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
459void 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
531void 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
543bool ExportGifGraphic(SvStream& rStream, const 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: */
Disposal
SvStream & WriteAnimation(SvStream &rOStm, const Animation &rAnimation)
Definition: Animation.cxx:568
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
size_t Count() const
Definition: Animation.hxx:70
const Size & GetDisplaySizePixel() const
Definition: Animation.hxx:56
const AnimationBitmap & Get(sal_uInt16 nAnimation) const
Definition: Animation.cxx:416
sal_uInt32 GetLoopCount() const
Definition: Animation.hxx:62
AlphaMask GetAlpha() const
Definition: BitmapEx.cxx:215
Bitmap GetBitmap(Color aTransparentReplaceColor) const
Definition: BitmapEx.cxx:203
const Size & GetSizePixel() const
Definition: bitmapex.hxx:72
bool Convert(BmpConversion eConversion)
Convert bitmap format.
static void ReleaseAccess(BitmapInfoAccess *pAccess)
sal_uInt8 GetBlue() const
sal_uInt8 GetRed() const
sal_uInt8 GetGreen() const
css::uno::Reference< css::task::XStatusIndicator > GetStatusIndicator() const
sal_Int32 ReadInt32(const OUString &rKey, sal_Int32 nDefault)
void Compress(sal_uInt8 *pSrc, sal_uInt32 nSize)
Definition: giflzwc.cxx:154
void StartCompression(SvStream &rGIF, sal_uInt16 nPixelSize)
Definition: giflzwc.cxx:123
void EndCompression()
Definition: giflzwc.cxx:212
Size GetPrefSize() const
Definition: graph.cxx:363
Animation GetAnimation() const
Definition: graph.cxx:334
bool IsAnimated() const
Definition: graph.cxx:319
BitmapEx GetBitmapEx(const GraphicConversionParameters &rParameters=GraphicConversionParameters()) const
Definition: graph.cxx:329
MapMode GetPrefMapMode() const
Definition: graph.cxx:374
bool IsTransparent() const
Definition: graph.cxx:309
SAL_WARN_UNUSED_RESULT Point LogicToLogic(const Point &rPtSource, const MapMode *pMapModeSource, const MapMode *pMapModeDest) const
Definition: map.cxx:1619
constexpr tools::Long Height() const
constexpr tools::Long Width() const
int nCount
#define DBG_ASSERT(sCon, aError)
bool ExportGifGraphic(SvStream &rStream, const Graphic &rGraphic, FilterConfigItem *pConfigItem)
Definition: egif.cxx:543
#define BMP_COL_TRANS
int i
long Long
HashMap_OWString_Interface aMap
tools::Long mnWait
unsigned char sal_uInt8