1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: pngwrite.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_vcl.hxx"
34 #include <vcl/pngwrite.hxx>
39 #include <rtl/memory.h>
40 #include <rtl/alloc.h>
41 #include <tools/zcodec.hxx>
42 #include <tools/stream.hxx>
43 #include <vcl/bmpacc.hxx>
44 #include <vcl/svapp.hxx>
45 #include <vcl/alpha.hxx>
46 #include <osl/endian.h>
52 #define PNG_DEF_COMPRESSION 6
54 #define PNGCHUNK_IHDR 0x49484452
55 #define PNGCHUNK_PLTE 0x504c5445
56 #define PNGCHUNK_IDAT 0x49444154
57 #define PNGCHUNK_IEND 0x49454e44
58 #define PNGCHUNK_bKGD 0x624b4744
59 #define PNGCHUNK_cHRM 0x6348524d
60 #define PNGCHUNK_gAMA 0x67414d41
61 #define PNGCHUNK_hIST 0x68495354
62 #define PNGCHUNK_pHYs 0x70485973
63 #define PNGCHUNK_sBIT 0x73425420
64 #define PNGCHUNK_tIME 0x74494d45
65 #define PNGCHUNK_tEXt 0x74455874
66 #define PNGCHUNK_tRNS 0x74524e53
67 #define PNGCHUNK_zTXt 0x7a545874
72 // - PNGWriterImplImpl -
79 PNGWriterImpl( const BitmapEx
& BmpEx
,
80 const ::com::sun::star::uno::Sequence
< ::com::sun::star::beans::PropertyValue
>* pFilterData
= NULL
);
83 sal_Bool
Write( SvStream
& rOStm
);
85 std::vector
< vcl::PNGWriter::ChunkData
>& GetChunks();
89 std::vector
< vcl::PNGWriter::ChunkData
> maChunkSeq
;
91 sal_Int32 mnCompLevel
;
92 sal_Int32 mnInterlaced
;
93 sal_uInt32 mnMaxChunkSize
;
96 BitmapReadAccess
* mpAccess
;
97 BitmapReadAccess
* mpMaskAccess
;
100 BYTE
* mpDeflateInBuf
; // as big as the size of a scanline + alphachannel + 1
101 BYTE
* mpPreviousScan
; // as big as mpDeflateInBuf
103 ULONG mnDeflateInSize
;
105 ULONG mnWidth
, mnHeight
;
107 BYTE mnFilterType
; // 0 oder 4;
108 ULONG mnBBP
; // bytes per pixel ( needed for filtering )
114 void ImplWritepHYs( const BitmapEx
& rBitmapEx
);
115 void ImplWriteIDAT();
116 ULONG
ImplGetFilter( ULONG nY
, ULONG nXStart
=0, ULONG nXAdd
=1 );
117 void ImplClearFirstScanline();
118 void ImplWriteTransparent();
119 BOOL
ImplWriteHeader();
120 void ImplWritePalette();
121 void ImplOpenChunk( ULONG nChunkType
);
122 void ImplWriteChunk( BYTE nNumb
);
123 void ImplWriteChunk( sal_uInt32 nNumb
);
124 void ImplWriteChunk( unsigned char* pSource
, sal_uInt32 nDatSize
);
125 void ImplCloseChunk( void );
128 // ------------------------------------------------------------------------
130 PNGWriterImpl::PNGWriterImpl( const BitmapEx
& rBmpEx
,
131 const ::com::sun::star::uno::Sequence
< ::com::sun::star::beans::PropertyValue
>* pFilterData
) :
132 mnCompLevel ( PNG_DEF_COMPRESSION
),
135 mpMaskAccess ( NULL
),
136 mpZCodec ( new ZCodec( DEFAULT_IN_BUFSIZE
, DEFAULT_OUT_BUFSIZE
, MAX_MEM_USAGE
) ),
137 mnLastPercent ( 0UL )
139 if ( !rBmpEx
.IsEmpty() )
141 Bitmap
aBmp( rBmpEx
.GetBitmap() );
143 mnInterlaced
= 0; // ( aBmp.GetSizePixel().Width() > 128 ) || ( aBmp.GetSizePixel().Height() > 128 ) ? 1 : 0; #i67236#
145 // #i67234# defaulting max chunk size to 256kb when using interlace mode
146 mnMaxChunkSize
= mnInterlaced
== 0 ? std::numeric_limits
< sal_uInt32
>::max() : 0x40000;
151 for ( i
= 0; i
< pFilterData
->getLength(); i
++ )
153 if ( (*pFilterData
)[ i
].Name
.equalsAscii( "Compression" ) )
154 (*pFilterData
)[ i
].Value
>>= mnCompLevel
;
155 else if ( (*pFilterData
)[ i
].Name
.equalsAscii( "Interlaced" ) )
156 (*pFilterData
)[ i
].Value
>>= mnInterlaced
;
157 else if ( (*pFilterData
)[ i
].Name
.equalsAscii( "MaxChunkSize" ) )
160 if ( (*pFilterData
)[ i
].Value
>>= nVal
)
161 mnMaxChunkSize
= (sal_uInt32
)nVal
;
165 mnBitsPerPixel
= (BYTE
)aBmp
.GetBitCount();
167 if( rBmpEx
.IsTransparent() )
169 if ( mnBitsPerPixel
<= 8 && rBmpEx
.IsAlpha() )
171 aBmp
.Convert( BMP_CONVERSION_24BIT
);
175 if ( mnBitsPerPixel
<= 8 ) // transparent palette
177 aBmp
.Convert( BMP_CONVERSION_8BIT_TRANS
);
178 aBmp
.Replace( rBmpEx
.GetMask(), BMP_COL_TRANS
);
180 mpAccess
= aBmp
.AcquireReadAccess();
183 if ( ImplWriteHeader() )
185 ImplWritepHYs( rBmpEx
);
187 ImplWriteTransparent();
190 aBmp
.ReleaseAccess( mpAccess
);
197 mpAccess
= aBmp
.AcquireReadAccess(); // TRUE RGB with alphachannel
200 if ( ( mbTrueAlpha
= rBmpEx
.IsAlpha() ) != FALSE
)
202 AlphaMask
aMask( rBmpEx
.GetAlpha() );
203 mpMaskAccess
= aMask
.AcquireReadAccess();
206 if ( ImplWriteHeader() )
208 ImplWritepHYs( rBmpEx
);
211 aMask
.ReleaseAccess( mpMaskAccess
);
218 Bitmap
aMask( rBmpEx
.GetMask() );
219 mpMaskAccess
= aMask
.AcquireReadAccess();
222 if ( ImplWriteHeader() )
224 ImplWritepHYs( rBmpEx
);
227 aMask
.ReleaseAccess( mpMaskAccess
);
232 aBmp
.ReleaseAccess( mpAccess
);
240 mpAccess
= aBmp
.AcquireReadAccess(); // palette + RGB without alphachannel
243 if ( ImplWriteHeader() )
245 ImplWritepHYs( rBmpEx
);
246 if( mpAccess
->HasPalette() )
251 aBmp
.ReleaseAccess( mpAccess
);
258 ImplOpenChunk( PNGCHUNK_IEND
); // create an IEND chunk
264 // ------------------------------------------------------------------------
266 PNGWriterImpl::~PNGWriterImpl()
271 // ------------------------------------------------------------------------
273 sal_Bool
PNGWriterImpl::Write( SvStream
& rOStm
)
275 /* png signature is always an array of 8 bytes */
276 sal_uInt16 nOldMode
= rOStm
.GetNumberFormatInt();
277 rOStm
.SetNumberFormatInt( NUMBERFORMAT_INT_BIGENDIAN
);
278 rOStm
<< static_cast<sal_uInt32
>(0x89504e47);
279 rOStm
<< static_cast<sal_uInt32
>(0x0d0a1a0a);
281 std::vector
< vcl::PNGWriter::ChunkData
>::iterator
aBeg( maChunkSeq
.begin() );
282 std::vector
< vcl::PNGWriter::ChunkData
>::iterator
aEnd( maChunkSeq
.end() );
283 while( aBeg
!= aEnd
)
285 sal_uInt32 nType
= aBeg
->nType
;
286 #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
287 nType
= SWAPLONG( nType
);
289 sal_uInt32 nCRC
= rtl_crc32( 0, &nType
, 4 );
290 sal_uInt32 nDataSize
= aBeg
->aData
.size();
292 nCRC
= rtl_crc32( nCRC
, &aBeg
->aData
[ 0 ], nDataSize
);
296 rOStm
.Write( &aBeg
->aData
[ 0 ], nDataSize
);
300 rOStm
.SetNumberFormatInt( nOldMode
);
304 // ------------------------------------------------------------------------
306 std::vector
< vcl::PNGWriter::ChunkData
>& PNGWriterImpl::GetChunks()
311 // ------------------------------------------------------------------------
313 BOOL
PNGWriterImpl::ImplWriteHeader()
315 ImplOpenChunk(PNGCHUNK_IHDR
);
316 ImplWriteChunk( sal_uInt32( mnWidth
= mpAccess
->Width() ) );
317 ImplWriteChunk( sal_uInt32( mnHeight
= mpAccess
->Height() ) );
319 if ( mnWidth
&& mnHeight
&& mnBitsPerPixel
&& mbStatus
)
321 BYTE nBitDepth
= mnBitsPerPixel
;
322 if ( mnBitsPerPixel
<= 8 )
327 BYTE nColorType
= 2; // colortype:
328 // bit 0 -> palette is used
329 if ( mpAccess
->HasPalette() ) // bit 1 -> color is used
330 nColorType
|= 1; // bit 2 -> alpha channel is used
337 ImplWriteChunk( nBitDepth
);
338 ImplWriteChunk( nColorType
); // colortype
339 ImplWriteChunk((BYTE
) 0 ); // compression type
340 ImplWriteChunk((BYTE
) 0 ); // filter type - is not supported in this version
341 ImplWriteChunk((BYTE
) mnInterlaced
); // interlace type
349 // ------------------------------------------------------------------------
351 void PNGWriterImpl::ImplWritePalette()
353 const ULONG nCount
= mpAccess
->GetPaletteEntryCount();
354 BYTE
* pTempBuf
= new BYTE
[ nCount
*3 ];
355 BYTE
* pTmp
= pTempBuf
;
357 ImplOpenChunk( PNGCHUNK_PLTE
);
359 for ( USHORT i
= 0; i
< nCount
; i
++ )
361 const BitmapColor
& rColor
= mpAccess
->GetPaletteColor( i
);
362 *pTmp
++ = rColor
.GetRed();
363 *pTmp
++ = rColor
.GetGreen();
364 *pTmp
++ = rColor
.GetBlue();
366 ImplWriteChunk( pTempBuf
, nCount
*3 );
371 // ------------------------------------------------------------------------
373 void PNGWriterImpl::ImplWriteTransparent ()
375 const ULONG nTransIndex
= mpAccess
->GetBestMatchingColor( BMP_COL_TRANS
);
377 ImplOpenChunk( PNGCHUNK_tRNS
);
379 for ( ULONG n
= 0UL; n
<= nTransIndex
; n
++ )
380 ImplWriteChunk( ( nTransIndex
== n
) ? (BYTE
) 0x0 : (BYTE
) 0xff );
385 // ------------------------------------------------------------------------
387 void PNGWriterImpl::ImplWritepHYs( const BitmapEx
& rBmpEx
)
389 if ( rBmpEx
.GetPrefMapMode() == MAP_100TH_MM
)
391 Size
aPrefSize( rBmpEx
.GetPrefSize() );
392 if ( aPrefSize
.Width() && aPrefSize
.Height() )
394 ImplOpenChunk( PNGCHUNK_pHYs
);
395 sal_uInt8 nMapUnit
= 1;
396 sal_uInt32 nPrefSizeX
= (sal_uInt32
)( (double)100000.0 / ( (double)aPrefSize
.Width() / mnWidth
) + 0.5 );
397 sal_uInt32 nPrefSizeY
= (sal_uInt32
)( (double)100000.0 / ( (double)aPrefSize
.Height() / mnHeight
) + 0.5 );
398 ImplWriteChunk( nPrefSizeX
);
399 ImplWriteChunk( nPrefSizeY
);
400 ImplWriteChunk( nMapUnit
);
406 // ------------------------------------------------------------------------
408 void PNGWriterImpl::ImplWriteIDAT ()
410 mnDeflateInSize
= mnBitsPerPixel
;
413 mnDeflateInSize
+= 8;
415 mnBBP
= ( mnDeflateInSize
+ 7 ) >> 3;
417 mnDeflateInSize
= mnBBP
* mnWidth
+ 1;
419 mpDeflateInBuf
= new BYTE
[ mnDeflateInSize
];
421 if ( mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
423 mpPreviousScan
= new BYTE
[ mnDeflateInSize
];
424 mpCurrentScan
= new BYTE
[ mnDeflateInSize
];
425 ImplClearFirstScanline();
427 mpZCodec
->BeginCompression( ZCODEC_PNG_DEFAULT
+ mnCompLevel
);
428 mpZCodec
->SetCRC( mnCRC
);
429 SvMemoryStream aOStm
;
430 if ( mnInterlaced
== 0 )
432 for ( ULONG nY
= 0; nY
< mnHeight
; nY
++ )
433 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter( nY
) );
439 for ( nY
= 0; nY
< mnHeight
; nY
+=8 ) // pass 1
440 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 0, 8 ) );
441 ImplClearFirstScanline();
443 for ( nY
= 0; nY
< mnHeight
; nY
+=8 ) // pass 2
444 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 4, 8 ) );
445 ImplClearFirstScanline();
447 if ( mnHeight
>= 5 ) // pass 3
449 for ( nY
= 4; nY
< mnHeight
; nY
+=8 )
450 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 0, 4 ) );
451 ImplClearFirstScanline();
454 for ( nY
= 0; nY
< mnHeight
; nY
+=4 ) // pass 4
455 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 2, 4 ) );
456 ImplClearFirstScanline();
458 if ( mnHeight
>= 3 ) // pass 5
460 for ( nY
= 2; nY
< mnHeight
; nY
+=4 )
461 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 0, 2 ) );
462 ImplClearFirstScanline();
465 for ( nY
= 0; nY
< mnHeight
; nY
+=2 ) // pass 6
466 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 1, 2 ) );
467 ImplClearFirstScanline();
469 if ( mnHeight
>= 2 ) // pass 7
471 for ( nY
= 1; nY
< mnHeight
; nY
+=2 )
472 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 0, 1 ) );
475 mpZCodec
->EndCompression();
476 mnCRC
= mpZCodec
->GetCRC();
478 if ( mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
480 delete[] mpCurrentScan
;
481 delete[] mpPreviousScan
;
483 delete[] mpDeflateInBuf
;
485 sal_uInt32 nIDATSize
= aOStm
.Tell();
486 sal_uInt32 nBytes
, nBytesToWrite
= nIDATSize
;
487 while( nBytesToWrite
)
489 nBytes
= nBytesToWrite
<= mnMaxChunkSize
? nBytesToWrite
: mnMaxChunkSize
;
490 ImplOpenChunk( PNGCHUNK_IDAT
);
491 ImplWriteChunk( (unsigned char*)aOStm
.GetData() + ( nIDATSize
- nBytesToWrite
), nBytes
);
493 nBytesToWrite
-= nBytes
;
497 // ---------------------------------------------------------------------------------------------------
498 // ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd
499 // appends to the currently used pass
500 // the complete size of scanline will be returned - in interlace mode zero is possible!
502 ULONG
PNGWriterImpl::ImplGetFilter ( ULONG nY
, ULONG nXStart
, ULONG nXAdd
)
507 pDest
= mpCurrentScan
;
509 pDest
= mpDeflateInBuf
;
511 if ( nXStart
< mnWidth
)
513 *pDest
++ = mnFilterType
; // in this version the filter type is either 0 or 4
515 if ( mpAccess
->HasPalette() ) // alphachannel is not allowed by pictures including palette entries
517 switch ( mnBitsPerPixel
)
522 for ( nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+=nXAdd
, nXIndex
++ )
524 ULONG nShift
= ( nXIndex
& 7 ) ^ 7;
526 *pDest
= (BYTE
)(mpAccess
->GetPixel( nY
, nX
) << nShift
);
527 else if ( nShift
== 0 )
528 *pDest
++ |= (BYTE
) mpAccess
->GetPixel( nY
, nX
) << nShift
;
530 *pDest
|= (BYTE
) mpAccess
->GetPixel( nY
, nX
) << nShift
;
532 if ( ( nXIndex
& 7 ) != 0 ) pDest
++; // byte is not completely used, so the
533 } // bufferpointer is to correct
539 for ( nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+= nXAdd
, nXIndex
++ )
542 *pDest
++ |= (BYTE
) mpAccess
->GetPixel( nY
, nX
);
544 *pDest
= (BYTE
) mpAccess
->GetPixel( nY
, nX
) << 4;
546 if ( nXIndex
& 1 ) pDest
++;
552 for ( ULONG nX
= nXStart
; nX
< mnWidth
; nX
+=nXAdd
)
553 *pDest
++ = mpAccess
->GetPixel( nY
, nX
);
564 if ( mpMaskAccess
) // mpMaskAccess != NULL -> alphachannel is to create
568 for ( ULONG nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
570 const BitmapColor
& rColor
= mpAccess
->GetPixel( nY
, nX
);
571 *pDest
++ = rColor
.GetRed();
572 *pDest
++ = rColor
.GetGreen();
573 *pDest
++ = rColor
.GetBlue();
574 *pDest
++ = 255 - mpMaskAccess
->GetPixel( nY
, nX
);
579 const BitmapColor
aTrans( mpMaskAccess
->GetBestMatchingColor( Color( COL_WHITE
) ) );
581 for ( ULONG nX
= nXStart
; nX
< mnWidth
; nX
+=nXAdd
)
583 const BitmapColor
& rColor
= mpAccess
->GetPixel( nY
, nX
);
584 *pDest
++ = rColor
.GetRed();
585 *pDest
++ = rColor
.GetGreen();
586 *pDest
++ = rColor
.GetBlue();
588 if( mpMaskAccess
->GetPixel( nY
, nX
) == aTrans
)
597 for ( ULONG nX
= nXStart
; nX
< mnWidth
; nX
+=nXAdd
)
599 const BitmapColor
& rColor
= mpAccess
->GetPixel( nY
, nX
);
600 *pDest
++ = rColor
.GetRed();
601 *pDest
++ = rColor
.GetGreen();
602 *pDest
++ = rColor
.GetBlue();
607 // filter type4 ( PAETH ) will be used only for 24bit graphics
610 mnDeflateInSize
= pDest
- mpCurrentScan
;
611 pDest
= mpDeflateInBuf
;
612 *pDest
++ = 4; // filter type
615 long np
, npa
, npb
, npc
;
617 BYTE
* p1
= mpCurrentScan
+ 1; // Current Pixel
618 BYTE
* p2
= p1
- mnBBP
; // left pixel
619 BYTE
* p3
= mpPreviousScan
; // upper pixel
620 BYTE
* p4
= p3
- mnBBP
; // upperleft Pixel;
622 while ( pDest
< mpDeflateInBuf
+ mnDeflateInSize
)
625 if ( p2
>= mpCurrentScan
+ 1 )
644 if ( ( npa
<= npb
) && ( npa
<= npc
) ) *pDest
++ = *p1
++ - (BYTE
)na
;
645 else if ( npb
<= npc
) *pDest
++ = *p1
++ - (BYTE
)nb
;
646 else *pDest
++ = *p1
++ - (BYTE
)nc
;
650 for ( long i
= 0; i
< (long)( mnDeflateInSize
- 1 ); i
++ )
651 mpPreviousScan
[ i
] = mpCurrentScan
[ i
+ 1 ];
654 mnDeflateInSize
= pDest
- mpDeflateInBuf
;
655 return ( mnDeflateInSize
);
658 // ------------------------------------------------------------------------
660 void PNGWriterImpl::ImplClearFirstScanline()
663 rtl_zeroMemory( mpPreviousScan
, mnDeflateInSize
);
666 // ------------------------------------------------------------------------
668 void PNGWriterImpl::ImplOpenChunk ( ULONG nChunkType
)
670 maChunkSeq
.resize( maChunkSeq
.size() + 1 );
671 maChunkSeq
.back().nType
= nChunkType
;
674 // ------------------------------------------------------------------------
676 void PNGWriterImpl::ImplWriteChunk ( BYTE nSource
)
678 maChunkSeq
.back().aData
.push_back( nSource
);
681 void PNGWriterImpl::ImplWriteChunk ( sal_uInt32 nSource
)
683 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
684 rChunkData
.aData
.push_back( (sal_uInt8
)( nSource
>> 24 ) );
685 rChunkData
.aData
.push_back( (sal_uInt8
)( nSource
>> 16 ) );
686 rChunkData
.aData
.push_back( (sal_uInt8
)( nSource
>> 8 ) );
687 rChunkData
.aData
.push_back( (sal_uInt8
)( nSource
) );
690 void PNGWriterImpl::ImplWriteChunk ( unsigned char* pSource
, sal_uInt32 nDatSize
)
694 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
695 sal_uInt32 nSize
= rChunkData
.aData
.size();
696 rChunkData
.aData
.resize( nSize
+ nDatSize
);
697 rtl_copyMemory( &rChunkData
.aData
[ nSize
], pSource
, nDatSize
);
701 // ------------------------------------------------------------------------
703 void PNGWriterImpl::ImplCloseChunk ( void )
711 PNGWriter::PNGWriter( const BitmapEx
& rBmpEx
,
712 const ::com::sun::star::uno::Sequence
< ::com::sun::star::beans::PropertyValue
>* pFilterData
) :
713 mpImpl( new ::vcl::PNGWriterImpl( rBmpEx
, pFilterData
) )
717 // ------------------------------------------------------------------------
719 PNGWriter::~PNGWriter()
724 // ------------------------------------------------------------------------
726 sal_Bool
PNGWriter::Write( SvStream
& rIStm
)
728 return mpImpl
->Write( rIStm
);
731 // ------------------------------------------------------------------------
733 std::vector
< vcl::PNGWriter::ChunkData
>& PNGWriter::GetChunks()
735 return mpImpl
->GetChunks();