1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_filter.hxx"
26 
27 #include <vcl/graph.hxx>
28 #include <vcl/svapp.hxx>
29 #include <vcl/msgbox.hxx>
30 #include <vcl/window.hxx>
31 #include <svl/solar.hrc>
32 #include <svtools/fltcall.hxx>
33 #include <svtools/FilterConfigItem.hxx>
34 #include "giflzwc.hxx"
35 
36 // -------------
37 // - GIFWriter -
38 // -------------
39 
40 class GIFWriter
41 {
42 	Bitmap				aAccBmp;
43 	BitmapReadAccess*	pAcc;
44 	SvStream*			pGIF;
45 	sal_uLong				nMinPercent;
46 	sal_uLong				nMaxPercent;
47 	sal_uLong				nLastPercent;
48 	long				nActX;
49 	long				nActY;
50 	sal_Int32			nInterlaced;
51 	sal_Bool				bStatus;
52 	sal_Bool				bTransparent;
53 
54 	void				MayCallback( sal_uLong nPercent );
55 	void				WriteSignature( sal_Bool bGIF89a );
56 	void				WriteGlobalHeader( const Size& rSize );
57 	void				WriteLoopExtension( const Animation& rAnimation );
58 	void				WriteLogSizeExtension( const Size& rSize100 );
59 	void				WriteImageExtension( long nTimer, Disposal eDisposal );
60 	void				WriteLocalHeader();
61 	void				WritePalette();
62 	void				WriteAccess();
63 	void				WriteTerminator();
64 
65 	sal_Bool				CreateAccess( const BitmapEx& rBmpEx );
66 	void				DestroyAccess();
67 
68 	void				WriteAnimation( const Animation& rAnimation );
69 	void				WriteBitmapEx( const BitmapEx& rBmpEx, const Point& rPoint, sal_Bool bExtended,
70 									   long nTimer = 0, Disposal eDisposal = DISPOSE_NOT );
71 
72 	com::sun::star::uno::Reference< com::sun::star::task::XStatusIndicator > xStatusIndicator;
73 
74 public:
75 
GIFWriter()76 						GIFWriter() {}
~GIFWriter()77 						~GIFWriter() {}
78 
79 	sal_Bool				WriteGIF( const Graphic& rGraphic, SvStream& rGIF,
80 										FilterConfigItem* pConfigItem );
81 };
82 
83 // ------------------------------------------------------------------------
84 
WriteGIF(const Graphic & rGraphic,SvStream & rGIF,FilterConfigItem * pFilterConfigItem)85 sal_Bool GIFWriter::WriteGIF( const Graphic& rGraphic, SvStream& rGIF,
86 								FilterConfigItem* pFilterConfigItem )
87 {
88 	if ( pFilterConfigItem )
89 	{
90 		xStatusIndicator = pFilterConfigItem->GetStatusIndicator();
91 		if ( xStatusIndicator.is() )
92 		{
93 			rtl::OUString aMsg;
94 			xStatusIndicator->start( aMsg, 100 );
95 		}
96 	}
97 
98 	Size			aSize100;
99 	const MapMode	aMap( rGraphic.GetPrefMapMode() );
100 	sal_Bool			bLogSize = ( aMap.GetMapUnit() != MAP_PIXEL );
101 
102 	if( bLogSize )
103 		aSize100 = Application::GetDefaultDevice()->LogicToLogic( rGraphic.GetPrefSize(), aMap, MAP_100TH_MM );
104 
105 	pGIF = &rGIF;
106 	bStatus = sal_True;
107 	nLastPercent = 0;
108 	nInterlaced = 0;
109 	pAcc = NULL;
110 
111 	if ( pFilterConfigItem )
112 		nInterlaced = pFilterConfigItem->ReadInt32( String( RTL_CONSTASCII_USTRINGPARAM( "Interlaced" ) ), 0 );
113 
114 	pGIF->SetNumberFormatInt( NUMBERFORMAT_INT_LITTLEENDIAN );
115 
116 	if( rGraphic.IsAnimated() )
117 	{
118 		const Animation& rAnimation = rGraphic.GetAnimation();
119 
120 		WriteSignature( sal_True );
121 
122 		if ( bStatus )
123 		{
124 			WriteGlobalHeader( rAnimation.GetDisplaySizePixel() );
125 
126 			if( bStatus )
127 			{
128 				WriteLoopExtension( rAnimation );
129 
130 				if( bStatus )
131 					WriteAnimation( rAnimation );
132 			}
133 		}
134 	}
135 	else
136 	{
137 		const sal_Bool bGrafTrans = rGraphic.IsTransparent();
138 
139 		BitmapEx aBmpEx;
140 
141 		if( bGrafTrans )
142 			aBmpEx = rGraphic.GetBitmapEx();
143 		else
144 			aBmpEx = BitmapEx( rGraphic.GetBitmap() );
145 
146 		nMinPercent = 0;
147 		nMaxPercent = 100;
148 
149 		WriteSignature( bGrafTrans || bLogSize );
150 
151 		if( bStatus )
152 		{
153 			WriteGlobalHeader( aBmpEx.GetSizePixel() );
154 
155 			if( bStatus )
156 				WriteBitmapEx( aBmpEx, Point(), bGrafTrans );
157 		}
158 	}
159 
160 	if( bStatus )
161 	{
162 		if( bLogSize )
163 			WriteLogSizeExtension( aSize100 );
164 
165 		WriteTerminator();
166 	}
167 
168 	if ( xStatusIndicator.is() )
169 		xStatusIndicator->end();
170 
171 	return bStatus;
172 }
173 
174 // ------------------------------------------------------------------------
175 
WriteBitmapEx(const BitmapEx & rBmpEx,const Point & rPoint,sal_Bool bExtended,long nTimer,Disposal eDisposal)176 void GIFWriter::WriteBitmapEx( const BitmapEx& rBmpEx, const Point& rPoint,
177 							   sal_Bool bExtended, long nTimer, Disposal eDisposal )
178 {
179 	if( CreateAccess( rBmpEx ) )
180 	{
181 		nActX = rPoint.X();
182 		nActY = rPoint.Y();
183 
184 		if( bExtended )
185 			WriteImageExtension( nTimer, eDisposal );
186 
187 		if( bStatus )
188 		{
189 			WriteLocalHeader();
190 
191 			if( bStatus )
192 			{
193 				WritePalette();
194 
195 				if( bStatus )
196 					WriteAccess();
197 			}
198 		}
199 
200 		DestroyAccess();
201 	}
202 }
203 
204 // ------------------------------------------------------------------------
205 
WriteAnimation(const Animation & rAnimation)206 void GIFWriter::WriteAnimation( const Animation& rAnimation )
207 {
208 	const sal_uInt16	nCount = rAnimation.Count();
209 
210 	if( nCount )
211 	{
212 		const double fStep = 100. / nCount;
213 
214 		nMinPercent = 0L;
215 		nMaxPercent = (sal_uLong) fStep;
216 
217 		for( sal_uInt16 i = 0; i < nCount; i++ )
218 		{
219 			const AnimationBitmap& rAnimBmp = rAnimation.Get( i );
220 
221 			WriteBitmapEx( rAnimBmp.aBmpEx, rAnimBmp.aPosPix, sal_True,
222 						   rAnimBmp.nWait, rAnimBmp.eDisposal );
223 			nMinPercent = nMaxPercent;
224 			nMaxPercent = (sal_uLong) ( nMaxPercent + fStep );
225 		}
226 	}
227 }
228 
229 // ------------------------------------------------------------------------
230 
MayCallback(sal_uLong nPercent)231 void GIFWriter::MayCallback( sal_uLong nPercent )
232 {
233 	if ( xStatusIndicator.is() )
234 	{
235 		if( nPercent >= nLastPercent + 3 )
236 		{
237 			nLastPercent = nPercent;
238 			if ( nPercent <= 100 )
239 				xStatusIndicator->setValue( nPercent );
240 		}
241 	}
242 }
243 
244 // ------------------------------------------------------------------------
245 
CreateAccess(const BitmapEx & rBmpEx)246 sal_Bool GIFWriter::CreateAccess( const BitmapEx& rBmpEx )
247 {
248 	if( bStatus )
249 	{
250 		Bitmap aMask( rBmpEx.GetMask() );
251 
252 		aAccBmp = rBmpEx.GetBitmap();
253 		bTransparent = sal_False;
254 
255 		if( !!aMask )
256 		{
257 			if( aAccBmp.Convert( BMP_CONVERSION_8BIT_TRANS ) )
258 			{
259 				aMask.Convert( BMP_CONVERSION_1BIT_THRESHOLD );
260 				aAccBmp.Replace( aMask, BMP_COL_TRANS );
261 				bTransparent = sal_True;
262 			}
263 			else
264 				aAccBmp.Convert( BMP_CONVERSION_8BIT_COLORS );
265 		}
266 		else
267 			aAccBmp.Convert( BMP_CONVERSION_8BIT_COLORS );
268 
269 		pAcc = aAccBmp.AcquireReadAccess();
270 
271 		if( !pAcc )
272 			bStatus = sal_False;
273 	}
274 
275 	return bStatus;
276 }
277 
278 // ------------------------------------------------------------------------
279 
DestroyAccess()280 void GIFWriter::DestroyAccess()
281 {
282 	aAccBmp.ReleaseAccess( pAcc );
283 	pAcc = NULL;
284 }
285 
286 // ------------------------------------------------------------------------
287 
WriteSignature(sal_Bool bGIF89a)288 void GIFWriter::WriteSignature( sal_Bool bGIF89a )
289 {
290 	if( bStatus )
291 	{
292 		pGIF->Write( bGIF89a ? "GIF89a" : "GIF87a" , 6 );
293 
294 		if( pGIF->GetError() )
295 			bStatus = sal_False;
296 	}
297 }
298 
299 // ------------------------------------------------------------------------
300 
WriteGlobalHeader(const Size & rSize)301 void GIFWriter::WriteGlobalHeader( const Size& rSize )
302 {
303 	if( bStatus )
304 	{
305 		// 256 Farben
306 		const sal_uInt16	nWidth = (sal_uInt16) rSize.Width();
307 		const sal_uInt16	nHeight = (sal_uInt16) rSize.Height();
308 		const sal_uInt8		cFlags = 128 | ( 7 << 4 );
309 
310 		// Werte rausschreiben
311 		*pGIF << nWidth;
312 		*pGIF << nHeight;
313 		*pGIF << cFlags;
314 		*pGIF << (sal_uInt8) 0x00;
315 		*pGIF << (sal_uInt8) 0x00;
316 
317 		// Dummy-Palette mit zwei Eintraegen (Schwarz/Weiss) schreiben;
318 		// dieses nur wegen Photoshop-Bug, da die keine Bilder ohne
319 		// globale Farbpalette lesen koennen
320 		*pGIF << (sal_uInt16) 0;
321 		*pGIF << (sal_uInt16) 255;
322 		*pGIF << (sal_uInt16) 65535;
323 
324 		if( pGIF->GetError() )
325 			bStatus = sal_False;
326 	}
327 }
328 
329 // ------------------------------------------------------------------------
330 
WriteLoopExtension(const Animation & rAnimation)331 void GIFWriter::WriteLoopExtension( const Animation& rAnimation )
332 {
333 	DBG_ASSERT( rAnimation.Count() > 0, "Animation has no bitmaps!" );
334 
335 	sal_uInt16 nLoopCount = (sal_uInt16) rAnimation.GetLoopCount();
336 
337 	// falls nur ein Durchlauf stattfinden soll,
338 	// wird keine LoopExtension geschrieben;
339 	// Default ist dann immer ein Durchlauf
340 	if( nLoopCount != 1 )
341 	{
342 		// Netscape interpretiert den LoopCount
343 		// als reine Anzahl der _Wiederholungen_
344 		if( nLoopCount )
345 			nLoopCount--;
346 
347 		const sal_uInt8 cLoByte = (const sal_uInt8) nLoopCount;
348 		const sal_uInt8 cHiByte = (const sal_uInt8) ( nLoopCount >> 8 );
349 
350 		*pGIF << (sal_uInt8) 0x21;
351 		*pGIF << (sal_uInt8) 0xff;
352 		*pGIF << (sal_uInt8) 0x0b;
353 		pGIF->Write( "NETSCAPE2.0", 11 );
354 		*pGIF << (sal_uInt8) 0x03;
355 		*pGIF << (sal_uInt8) 0x01;
356 		*pGIF << cLoByte;
357 		*pGIF << cHiByte;
358 		*pGIF << (sal_uInt8) 0x00;
359 	}
360 }
361 
362 // ------------------------------------------------------------------------
363 
WriteLogSizeExtension(const Size & rSize100)364 void GIFWriter::WriteLogSizeExtension( const Size& rSize100 )
365 {
366 	// PrefSize in 100th-mm als ApplicationExtension schreiben
367 	if( rSize100.Width() && rSize100.Height() )
368 	{
369 		*pGIF << (sal_uInt8) 0x21;
370 		*pGIF << (sal_uInt8) 0xff;
371 		*pGIF << (sal_uInt8) 0x0b;
372 		pGIF->Write( "STARDIV 5.0", 11 );
373 		*pGIF << (sal_uInt8) 0x09;
374 		*pGIF << (sal_uInt8) 0x01;
375 		*pGIF << (sal_uInt32) rSize100.Width();
376 		*pGIF << (sal_uInt32) rSize100.Height();
377 		*pGIF << (sal_uInt8) 0x00;
378 	}
379 }
380 
381 // ------------------------------------------------------------------------
382 
WriteImageExtension(long nTimer,Disposal eDisposal)383 void GIFWriter::WriteImageExtension( long nTimer, Disposal eDisposal )
384 {
385 	if( bStatus )
386 	{
387 		const sal_uInt16	nDelay = (sal_uInt16) nTimer;
388 		sal_uInt8			cFlags = 0;
389 
390 		// Transparent-Flag setzen
391 		if( bTransparent )
392 			cFlags |= 1;
393 
394 		// Disposal-Wert setzen
395 		if( eDisposal == DISPOSE_BACK )
396 			cFlags |= ( 2 << 2 );
397 		else if( eDisposal == DISPOSE_PREVIOUS )
398 			cFlags |= ( 3 << 2 );
399 
400 		*pGIF << (sal_uInt8) 0x21;
401 		*pGIF << (sal_uInt8) 0xf9;
402 		*pGIF << (sal_uInt8) 0x04;
403 		*pGIF << cFlags;
404 		*pGIF << nDelay;
405 		*pGIF << (sal_uInt8) pAcc->GetBestPaletteIndex( BMP_COL_TRANS );
406 		*pGIF << (sal_uInt8) 0x00;
407 
408 		if( pGIF->GetError() )
409 			bStatus = sal_False;
410 	}
411 }
412 
413 // ------------------------------------------------------------------------
414 
WriteLocalHeader()415 void GIFWriter::WriteLocalHeader()
416 {
417 	if( bStatus )
418 	{
419 		const sal_uInt16	nPosX = (sal_uInt16) nActX;
420 		const sal_uInt16	nPosY = (sal_uInt16) nActY;
421 		const sal_uInt16	nWidth = (sal_uInt16) pAcc->Width();
422 		const sal_uInt16	nHeight = (sal_uInt16) pAcc->Height();
423 		sal_uInt8			cFlags = (sal_uInt8) ( pAcc->GetBitCount() - 1 );
424 
425 		// Interlaced-Flag setzen
426 		if( nInterlaced )
427 			cFlags |= 0x40;
428 
429 		// Flag fuer lokale Farbpalette setzen
430 		cFlags |= 0x80;
431 
432 		// alles rausschreiben
433 		*pGIF << (sal_uInt8) 0x2c;
434 		*pGIF << nPosX;
435 		*pGIF << nPosY;
436 		*pGIF << nWidth;
437 		*pGIF << nHeight;
438 		*pGIF << cFlags;
439 
440 		if( pGIF->GetError() )
441 			bStatus = sal_False;
442 	}
443 }
444 
445 // ------------------------------------------------------------------------
446 
WritePalette()447 void GIFWriter::WritePalette()
448 {
449 	if( bStatus && pAcc->HasPalette() )
450 	{
451 		const sal_uInt16 nCount = pAcc->GetPaletteEntryCount();
452 		const sal_uInt16 nMaxCount = ( 1 << pAcc->GetBitCount() );
453 
454 		for ( sal_uInt16 i = 0; i < nCount; i++ )
455 		{
456 			const BitmapColor& rColor = pAcc->GetPaletteColor( i );
457 
458 			*pGIF << rColor.GetRed();
459 			*pGIF << rColor.GetGreen();
460 			*pGIF << rColor.GetBlue();
461 		}
462 
463 		// Rest mit 0 auffuellen
464 		if( nCount < nMaxCount )
465 			pGIF->SeekRel( ( nMaxCount - nCount ) * 3 );
466 
467 		if( pGIF->GetError() )
468 			bStatus = sal_False;
469 	}
470 }
471 
472 // ------------------------------------------------------------------------
473 
WriteAccess()474 void GIFWriter::WriteAccess()
475 {
476 	GIFLZWCompressor	aCompressor;
477 	const long			nWidth = pAcc->Width();
478 	const long			nHeight = pAcc->Height();
479 	sal_uInt8*				pBuffer = NULL;
480 	const sal_uLong			nFormat = pAcc->GetScanlineFormat();
481 	long				nY;
482 	long				nT;
483 	long				i;
484 	sal_Bool				bNative = ( BMP_FORMAT_8BIT_PAL == nFormat );
485 
486 	if( !bNative )
487 		pBuffer = new sal_uInt8[ nWidth ];
488 
489 	if( bStatus && ( 8 == pAcc->GetBitCount() ) && pAcc->HasPalette() )
490 	{
491 		aCompressor.StartCompression( *pGIF, pAcc->GetBitCount() );
492 
493 		for( i = 0; i < nHeight; i++ )
494 		{
495 			if( nInterlaced )
496 			{
497 				nY = i << 3;
498 
499 				if( nY >= nHeight )
500 				{
501 					nT = i - ( ( nHeight + 7 ) >> 3 );
502 					nY= ( nT << 3 ) + 4;
503 
504 					if( nY >= nHeight )
505 					{
506 						nT -= ( nHeight + 3 ) >> 3;
507 						nY = ( nT << 2 ) + 2;
508 
509 						if ( nY >= nHeight )
510 						{
511 							nT -= ( ( nHeight + 1 ) >> 2 );
512 							nY = ( nT << 1 ) + 1;
513 						}
514 					}
515 				}
516 			}
517 			else
518 				nY = i;
519 
520 			if( bNative )
521 				aCompressor.Compress( pAcc->GetScanline( nY ), nWidth );
522 			else
523 			{
524 				for( long nX = 0L; nX < nWidth; nX++ )
525 					pBuffer[ nX ] = pAcc->GetPixelIndex( nY, nX );
526 
527 				aCompressor.Compress( pBuffer, nWidth );
528 			}
529 
530 			if ( pGIF->GetError() )
531 				bStatus = sal_False;
532 
533 			MayCallback( nMinPercent + ( nMaxPercent - nMinPercent ) * i / nHeight );
534 
535 			if( !bStatus )
536 				break;
537 		}
538 
539 		aCompressor.EndCompression();
540 
541 		if ( pGIF->GetError() )
542 			bStatus = sal_False;
543 	}
544 
545 	delete[] pBuffer;
546 }
547 
548 // ------------------------------------------------------------------------
549 
WriteTerminator()550 void GIFWriter::WriteTerminator()
551 {
552 	if( bStatus )
553 	{
554 		*pGIF << (sal_uInt8) 0x3b;
555 
556 		if( pGIF->GetError() )
557 			bStatus = sal_False;
558 	}
559 }
560 
561 // ------------------------------------------------------------------------
562 
GraphicExport(SvStream & rStream,Graphic & rGraphic,FilterConfigItem * pConfigItem,sal_Bool)563 extern "C" sal_Bool __LOADONCALLAPI GraphicExport( SvStream& rStream, Graphic& rGraphic,
564 											   FilterConfigItem* pConfigItem, sal_Bool )
565 {
566 	return GIFWriter().WriteGIF( rGraphic, rStream, pConfigItem );
567 }
568 
569 // ------------------------------------------------------------------------
570