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 <vcl/pngwrite.hxx>
26 #include <rtl/alloc.h>
27 #include <tools/zcodec.hxx>
28 #include <tools/stream.hxx>
29 #include <vcl/bmpacc.hxx>
30 #include <vcl/svapp.hxx>
31 #include <vcl/alpha.hxx>
32 #include <osl/endian.h>
34 #define PNG_DEF_COMPRESSION 6
36 #define PNGCHUNK_IHDR 0x49484452
37 #define PNGCHUNK_PLTE 0x504c5445
38 #define PNGCHUNK_IDAT 0x49444154
39 #define PNGCHUNK_IEND 0x49454e44
40 #define PNGCHUNK_pHYs 0x70485973
41 #define PNGCHUNK_tRNS 0x74524e53
50 PNGWriterImpl( const BitmapEx
& BmpEx
,
51 const ::com::sun::star::uno::Sequence
< ::com::sun::star::beans::PropertyValue
>* pFilterData
= NULL
);
54 sal_Bool
Write( SvStream
& rOStm
);
56 std::vector
< vcl::PNGWriter::ChunkData
>& GetChunks();
60 std::vector
< vcl::PNGWriter::ChunkData
> maChunkSeq
;
62 sal_Int32 mnCompLevel
;
63 sal_Int32 mnInterlaced
;
64 sal_uInt32 mnMaxChunkSize
;
67 BitmapReadAccess
* mpAccess
;
68 BitmapReadAccess
* mpMaskAccess
;
71 sal_uInt8
* mpDeflateInBuf
; // as big as the size of a scanline + alphachannel + 1
72 sal_uInt8
* mpPreviousScan
; // as big as mpDeflateInBuf
73 sal_uInt8
* mpCurrentScan
;
74 sal_uLong mnDeflateInSize
;
76 sal_uLong mnWidth
, mnHeight
;
77 sal_uInt8 mnBitsPerPixel
;
78 sal_uInt8 mnFilterType
; // 0 oder 4;
79 sal_uLong mnBBP
; // bytes per pixel ( needed for filtering )
83 void ImplWritepHYs( const BitmapEx
& rBitmapEx
);
85 sal_uLong
ImplGetFilter( sal_uLong nY
, sal_uLong nXStart
=0, sal_uLong nXAdd
=1 );
86 void ImplClearFirstScanline();
87 void ImplWriteTransparent();
88 sal_Bool
ImplWriteHeader();
89 void ImplWritePalette();
90 void ImplOpenChunk( sal_uLong nChunkType
);
91 void ImplWriteChunk( sal_uInt8 nNumb
);
92 void ImplWriteChunk( sal_uInt32 nNumb
);
93 void ImplWriteChunk( unsigned char* pSource
, sal_uInt32 nDatSize
);
94 void ImplCloseChunk( void ) const;
97 PNGWriterImpl::PNGWriterImpl( const BitmapEx
& rBmpEx
,
98 const ::com::sun::star::uno::Sequence
< ::com::sun::star::beans::PropertyValue
>* pFilterData
) :
99 mnCompLevel ( PNG_DEF_COMPRESSION
),
100 mbStatus ( sal_True
),
102 mpMaskAccess ( NULL
),
103 mpZCodec ( new ZCodec( DEFAULT_IN_BUFSIZE
, DEFAULT_OUT_BUFSIZE
, MAX_MEM_USAGE
) ),
106 if ( !rBmpEx
.IsEmpty() )
108 Bitmap
aBmp( rBmpEx
.GetBitmap() );
110 mnInterlaced
= 0; // ( aBmp.GetSizePixel().Width() > 128 ) || ( aBmp.GetSizePixel().Height() > 128 ) ? 1 : 0; #i67236#
112 // #i67234# defaulting max chunk size to 256kb when using interlace mode
113 mnMaxChunkSize
= mnInterlaced
== 0 ? std::numeric_limits
< sal_uInt32
>::max() : 0x40000;
118 for ( i
= 0; i
< pFilterData
->getLength(); i
++ )
120 if ( (*pFilterData
)[ i
].Name
== "Compression" )
121 (*pFilterData
)[ i
].Value
>>= mnCompLevel
;
122 else if ( (*pFilterData
)[ i
].Name
== "Interlaced" )
123 (*pFilterData
)[ i
].Value
>>= mnInterlaced
;
124 else if ( (*pFilterData
)[ i
].Name
== "MaxChunkSize" )
127 if ( (*pFilterData
)[ i
].Value
>>= nVal
)
128 mnMaxChunkSize
= (sal_uInt32
)nVal
;
132 mnBitsPerPixel
= (sal_uInt8
)aBmp
.GetBitCount();
134 if( rBmpEx
.IsTransparent() )
136 if ( mnBitsPerPixel
<= 8 && rBmpEx
.IsAlpha() )
138 aBmp
.Convert( BMP_CONVERSION_24BIT
);
142 if ( mnBitsPerPixel
<= 8 ) // transparent palette
144 aBmp
.Convert( BMP_CONVERSION_8BIT_TRANS
);
145 aBmp
.Replace( rBmpEx
.GetMask(), BMP_COL_TRANS
);
147 mpAccess
= aBmp
.AcquireReadAccess();
150 if ( ImplWriteHeader() )
152 ImplWritepHYs( rBmpEx
);
154 ImplWriteTransparent();
157 aBmp
.ReleaseAccess( mpAccess
), mpAccess
= 0;
160 mbStatus
= sal_False
;
164 mpAccess
= aBmp
.AcquireReadAccess(); // sal_True RGB with alphachannel
167 if ( ( mbTrueAlpha
= rBmpEx
.IsAlpha() ) != sal_False
)
169 AlphaMask
aMask( rBmpEx
.GetAlpha() );
170 mpMaskAccess
= aMask
.AcquireReadAccess();
173 if ( ImplWriteHeader() )
175 ImplWritepHYs( rBmpEx
);
178 aMask
.ReleaseAccess( mpMaskAccess
), mpMaskAccess
= 0;
181 mbStatus
= sal_False
;
185 Bitmap
aMask( rBmpEx
.GetMask() );
186 mpMaskAccess
= aMask
.AcquireReadAccess();
189 if ( ImplWriteHeader() )
191 ImplWritepHYs( rBmpEx
);
194 aMask
.ReleaseAccess( mpMaskAccess
), mpMaskAccess
= 0;
197 mbStatus
= sal_False
;
199 aBmp
.ReleaseAccess( mpAccess
), mpAccess
= 0;
202 mbStatus
= sal_False
;
207 mpAccess
= aBmp
.AcquireReadAccess(); // palette + RGB without alphachannel
210 if ( ImplWriteHeader() )
212 ImplWritepHYs( rBmpEx
);
213 if( mpAccess
->HasPalette() )
218 aBmp
.ReleaseAccess( mpAccess
), mpAccess
= 0;
221 mbStatus
= sal_False
;
225 ImplOpenChunk( PNGCHUNK_IEND
); // create an IEND chunk
231 PNGWriterImpl::~PNGWriterImpl()
236 sal_Bool
PNGWriterImpl::Write( SvStream
& rOStm
)
238 /* png signature is always an array of 8 bytes */
239 sal_uInt16 nOldMode
= rOStm
.GetNumberFormatInt();
240 rOStm
.SetNumberFormatInt( NUMBERFORMAT_INT_BIGENDIAN
);
241 rOStm
<< static_cast<sal_uInt32
>(0x89504e47);
242 rOStm
<< static_cast<sal_uInt32
>(0x0d0a1a0a);
244 std::vector
< vcl::PNGWriter::ChunkData
>::iterator
aBeg( maChunkSeq
.begin() );
245 std::vector
< vcl::PNGWriter::ChunkData
>::iterator
aEnd( maChunkSeq
.end() );
246 while( aBeg
!= aEnd
)
248 sal_uInt32 nType
= aBeg
->nType
;
249 #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
250 nType
= OSL_SWAPDWORD( nType
);
252 sal_uInt32 nCRC
= rtl_crc32( 0, &nType
, 4 );
253 sal_uInt32 nDataSize
= aBeg
->aData
.size();
255 nCRC
= rtl_crc32( nCRC
, &aBeg
->aData
[ 0 ], nDataSize
);
259 rOStm
.Write( &aBeg
->aData
[ 0 ], nDataSize
);
263 rOStm
.SetNumberFormatInt( nOldMode
);
267 std::vector
< vcl::PNGWriter::ChunkData
>& PNGWriterImpl::GetChunks()
272 sal_Bool
PNGWriterImpl::ImplWriteHeader()
274 ImplOpenChunk(PNGCHUNK_IHDR
);
275 ImplWriteChunk( sal_uInt32( mnWidth
= mpAccess
->Width() ) );
276 ImplWriteChunk( sal_uInt32( mnHeight
= mpAccess
->Height() ) );
278 if ( mnWidth
&& mnHeight
&& mnBitsPerPixel
&& mbStatus
)
280 sal_uInt8 nBitDepth
= mnBitsPerPixel
;
281 if ( mnBitsPerPixel
<= 8 )
286 sal_uInt8 nColorType
= 2; // colortype:
287 // bit 0 -> palette is used
288 if ( mpAccess
->HasPalette() ) // bit 1 -> color is used
289 nColorType
|= 1; // bit 2 -> alpha channel is used
296 ImplWriteChunk( nBitDepth
);
297 ImplWriteChunk( nColorType
); // colortype
298 ImplWriteChunk((sal_uInt8
) 0 ); // compression type
299 ImplWriteChunk((sal_uInt8
) 0 ); // filter type - is not supported in this version
300 ImplWriteChunk((sal_uInt8
) mnInterlaced
); // interlace type
304 mbStatus
= sal_False
;
308 void PNGWriterImpl::ImplWritePalette()
310 const sal_uLong nCount
= mpAccess
->GetPaletteEntryCount();
311 sal_uInt8
* pTempBuf
= new sal_uInt8
[ nCount
*3 ];
312 sal_uInt8
* pTmp
= pTempBuf
;
314 ImplOpenChunk( PNGCHUNK_PLTE
);
316 for ( sal_uInt16 i
= 0; i
< nCount
; i
++ )
318 const BitmapColor
& rColor
= mpAccess
->GetPaletteColor( i
);
319 *pTmp
++ = rColor
.GetRed();
320 *pTmp
++ = rColor
.GetGreen();
321 *pTmp
++ = rColor
.GetBlue();
323 ImplWriteChunk( pTempBuf
, nCount
*3 );
328 void PNGWriterImpl::ImplWriteTransparent ()
330 const sal_uLong nTransIndex
= mpAccess
->GetBestPaletteIndex( BMP_COL_TRANS
);
332 ImplOpenChunk( PNGCHUNK_tRNS
);
334 for ( sal_uLong n
= 0UL; n
<= nTransIndex
; n
++ )
335 ImplWriteChunk( ( nTransIndex
== n
) ? (sal_uInt8
) 0x0 : (sal_uInt8
) 0xff );
340 void PNGWriterImpl::ImplWritepHYs( const BitmapEx
& rBmpEx
)
342 if ( rBmpEx
.GetPrefMapMode() == MAP_100TH_MM
)
344 Size
aPrefSize( rBmpEx
.GetPrefSize() );
345 if ( aPrefSize
.Width() && aPrefSize
.Height() )
347 ImplOpenChunk( PNGCHUNK_pHYs
);
348 sal_uInt8 nMapUnit
= 1;
349 sal_uInt32 nPrefSizeX
= (sal_uInt32
)( (double)100000.0 / ( (double)aPrefSize
.Width() / mnWidth
) + 0.5 );
350 sal_uInt32 nPrefSizeY
= (sal_uInt32
)( (double)100000.0 / ( (double)aPrefSize
.Height() / mnHeight
) + 0.5 );
351 ImplWriteChunk( nPrefSizeX
);
352 ImplWriteChunk( nPrefSizeY
);
353 ImplWriteChunk( nMapUnit
);
359 void PNGWriterImpl::ImplWriteIDAT ()
361 mnDeflateInSize
= mnBitsPerPixel
;
364 mnDeflateInSize
+= 8;
366 mnBBP
= ( mnDeflateInSize
+ 7 ) >> 3;
368 mnDeflateInSize
= mnBBP
* mnWidth
+ 1;
370 mpDeflateInBuf
= new sal_uInt8
[ mnDeflateInSize
];
372 if ( mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
374 mpPreviousScan
= new sal_uInt8
[ mnDeflateInSize
];
375 mpCurrentScan
= new sal_uInt8
[ mnDeflateInSize
];
376 ImplClearFirstScanline();
378 mpZCodec
->BeginCompression( ZCODEC_PNG_DEFAULT
+ mnCompLevel
);
379 mpZCodec
->SetCRC( mnCRC
);
380 SvMemoryStream aOStm
;
381 if ( mnInterlaced
== 0 )
383 for ( sal_uLong nY
= 0; nY
< mnHeight
; nY
++ )
384 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter( nY
) );
390 for ( nY
= 0; nY
< mnHeight
; nY
+=8 ) // pass 1
391 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 0, 8 ) );
392 ImplClearFirstScanline();
394 for ( nY
= 0; nY
< mnHeight
; nY
+=8 ) // pass 2
395 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 4, 8 ) );
396 ImplClearFirstScanline();
398 if ( mnHeight
>= 5 ) // pass 3
400 for ( nY
= 4; nY
< mnHeight
; nY
+=8 )
401 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 0, 4 ) );
402 ImplClearFirstScanline();
405 for ( nY
= 0; nY
< mnHeight
; nY
+=4 ) // pass 4
406 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 2, 4 ) );
407 ImplClearFirstScanline();
409 if ( mnHeight
>= 3 ) // pass 5
411 for ( nY
= 2; nY
< mnHeight
; nY
+=4 )
412 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 0, 2 ) );
413 ImplClearFirstScanline();
416 for ( nY
= 0; nY
< mnHeight
; nY
+=2 ) // pass 6
417 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 1, 2 ) );
418 ImplClearFirstScanline();
420 if ( mnHeight
>= 2 ) // pass 7
422 for ( nY
= 1; nY
< mnHeight
; nY
+=2 )
423 mpZCodec
->Write( aOStm
, mpDeflateInBuf
, ImplGetFilter ( nY
, 0, 1 ) );
426 mpZCodec
->EndCompression();
427 mnCRC
= mpZCodec
->GetCRC();
429 if ( mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
431 delete[] mpCurrentScan
;
432 delete[] mpPreviousScan
;
434 delete[] mpDeflateInBuf
;
436 sal_uInt32 nIDATSize
= aOStm
.Tell();
437 sal_uInt32 nBytes
, nBytesToWrite
= nIDATSize
;
438 while( nBytesToWrite
)
440 nBytes
= nBytesToWrite
<= mnMaxChunkSize
? nBytesToWrite
: mnMaxChunkSize
;
441 ImplOpenChunk( PNGCHUNK_IDAT
);
442 ImplWriteChunk( (unsigned char*)aOStm
.GetData() + ( nIDATSize
- nBytesToWrite
), nBytes
);
444 nBytesToWrite
-= nBytes
;
448 // ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd
449 // appends to the currently used pass
450 // the complete size of scanline will be returned - in interlace mode zero is possible!
452 sal_uLong
PNGWriterImpl::ImplGetFilter ( sal_uLong nY
, sal_uLong nXStart
, sal_uLong nXAdd
)
457 pDest
= mpCurrentScan
;
459 pDest
= mpDeflateInBuf
;
461 if ( nXStart
< mnWidth
)
463 *pDest
++ = mnFilterType
; // in this version the filter type is either 0 or 4
465 if ( mpAccess
->HasPalette() ) // alphachannel is not allowed by pictures including palette entries
467 switch ( mnBitsPerPixel
)
471 sal_uLong nX
, nXIndex
;
472 for ( nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+=nXAdd
, nXIndex
++ )
474 sal_uLong nShift
= ( nXIndex
& 7 ) ^ 7;
476 *pDest
= mpAccess
->GetPixelIndex( nY
, nX
) << nShift
;
477 else if ( nShift
== 0 )
478 *pDest
++ |= mpAccess
->GetPixelIndex( nY
, nX
) << nShift
;
480 *pDest
|= mpAccess
->GetPixelIndex( nY
, nX
) << nShift
;
482 if ( ( nXIndex
& 7 ) != 0 ) pDest
++; // byte is not completely used, so the
483 } // bufferpointer is to correct
488 sal_uLong nX
, nXIndex
;
489 for ( nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+= nXAdd
, nXIndex
++ )
492 *pDest
++ |= mpAccess
->GetPixelIndex( nY
, nX
);
494 *pDest
= mpAccess
->GetPixelIndex( nY
, nX
) << 4;
496 if ( nXIndex
& 1 ) pDest
++;
502 for ( sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+=nXAdd
)
503 *pDest
++ = mpAccess
->GetPixelIndex( nY
, nX
);
508 mbStatus
= sal_False
;
514 if ( mpMaskAccess
) // mpMaskAccess != NULL -> alphachannel is to create
518 for ( sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
520 const BitmapColor
& rColor
= mpAccess
->GetPixel( nY
, nX
);
521 *pDest
++ = rColor
.GetRed();
522 *pDest
++ = rColor
.GetGreen();
523 *pDest
++ = rColor
.GetBlue();
524 *pDest
++ = 255 - mpMaskAccess
->GetPixelIndex( nY
, nX
);
529 const BitmapColor
aTrans( mpMaskAccess
->GetBestMatchingColor( Color( COL_WHITE
) ) );
531 for ( sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+=nXAdd
)
533 const BitmapColor
& rColor
= mpAccess
->GetPixel( nY
, nX
);
534 *pDest
++ = rColor
.GetRed();
535 *pDest
++ = rColor
.GetGreen();
536 *pDest
++ = rColor
.GetBlue();
538 if( mpMaskAccess
->GetPixel( nY
, nX
) == aTrans
)
547 for ( sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+=nXAdd
)
549 const BitmapColor
& rColor
= mpAccess
->GetPixel( nY
, nX
);
550 *pDest
++ = rColor
.GetRed();
551 *pDest
++ = rColor
.GetGreen();
552 *pDest
++ = rColor
.GetBlue();
557 // filter type4 ( PAETH ) will be used only for 24bit graphics
560 mnDeflateInSize
= pDest
- mpCurrentScan
;
561 pDest
= mpDeflateInBuf
;
562 *pDest
++ = 4; // filter type
564 sal_uLong na
, nb
, nc
;
565 long np
, npa
, npb
, npc
;
567 sal_uInt8
* p1
= mpCurrentScan
+ 1; // Current Pixel
568 sal_uInt8
* p2
= p1
- mnBBP
; // left pixel
569 sal_uInt8
* p3
= mpPreviousScan
; // upper pixel
570 sal_uInt8
* p4
= p3
- mnBBP
; // upperleft Pixel;
572 while ( pDest
< mpDeflateInBuf
+ mnDeflateInSize
)
575 if ( p2
>= mpCurrentScan
+ 1 )
594 if ( ( npa
<= npb
) && ( npa
<= npc
) ) *pDest
++ = *p1
++ - (sal_uInt8
)na
;
595 else if ( npb
<= npc
) *pDest
++ = *p1
++ - (sal_uInt8
)nb
;
596 else *pDest
++ = *p1
++ - (sal_uInt8
)nc
;
600 for ( long i
= 0; i
< (long)( mnDeflateInSize
- 1 ); i
++ )
601 mpPreviousScan
[ i
] = mpCurrentScan
[ i
+ 1 ];
604 mnDeflateInSize
= pDest
- mpDeflateInBuf
;
605 return ( mnDeflateInSize
);
608 void PNGWriterImpl::ImplClearFirstScanline()
611 memset( mpPreviousScan
, 0, mnDeflateInSize
);
614 void PNGWriterImpl::ImplOpenChunk ( sal_uLong nChunkType
)
616 maChunkSeq
.resize( maChunkSeq
.size() + 1 );
617 maChunkSeq
.back().nType
= nChunkType
;
620 void PNGWriterImpl::ImplWriteChunk ( sal_uInt8 nSource
)
622 maChunkSeq
.back().aData
.push_back( nSource
);
625 void PNGWriterImpl::ImplWriteChunk ( sal_uInt32 nSource
)
627 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
628 rChunkData
.aData
.push_back( (sal_uInt8
)( nSource
>> 24 ) );
629 rChunkData
.aData
.push_back( (sal_uInt8
)( nSource
>> 16 ) );
630 rChunkData
.aData
.push_back( (sal_uInt8
)( nSource
>> 8 ) );
631 rChunkData
.aData
.push_back( (sal_uInt8
)( nSource
) );
634 void PNGWriterImpl::ImplWriteChunk ( unsigned char* pSource
, sal_uInt32 nDatSize
)
638 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
639 sal_uInt32 nSize
= rChunkData
.aData
.size();
640 rChunkData
.aData
.resize( nSize
+ nDatSize
);
641 memcpy( &rChunkData
.aData
[ nSize
], pSource
, nDatSize
);
646 void PNGWriterImpl::ImplCloseChunk ( void ) const
650 PNGWriter::PNGWriter( const BitmapEx
& rBmpEx
,
651 const ::com::sun::star::uno::Sequence
< ::com::sun::star::beans::PropertyValue
>* pFilterData
) :
652 mpImpl( new ::vcl::PNGWriterImpl( rBmpEx
, pFilterData
) )
656 PNGWriter::~PNGWriter()
661 sal_Bool
PNGWriter::Write( SvStream
& rIStm
)
663 return mpImpl
->Write( rIStm
);
666 std::vector
< vcl::PNGWriter::ChunkData
>& PNGWriter::GetChunks()
668 return mpImpl
->GetChunks();
673 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */