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 .
20 #include <vcl/pngwrite.hxx>
21 #include <vcl/bitmapex.hxx>
23 #include <com/sun/star/beans/PropertyValue.hpp>
24 #include <com/sun/star/uno/Sequence.hxx>
28 #include <tools/zcodec.hxx>
29 #include <tools/stream.hxx>
30 #include <vcl/bitmapaccess.hxx>
31 #include <vcl/alpha.hxx>
32 #include <osl/endian.h>
34 #include <vcl/BitmapTools.hxx>
36 #define PNG_DEF_COMPRESSION 6
38 #define PNGCHUNK_IHDR 0x49484452
39 #define PNGCHUNK_PLTE 0x504c5445
40 #define PNGCHUNK_IDAT 0x49444154
41 #define PNGCHUNK_IEND 0x49454e44
42 #define PNGCHUNK_pHYs 0x70485973
43 #define PNGCHUNK_tRNS 0x74524e53
52 PNGWriterImpl(const BitmapEx
& BmpEx
,
53 const css::uno::Sequence
<css::beans::PropertyValue
>* pFilterData
);
55 bool Write(SvStream
& rOutStream
);
57 std::vector
<vcl::PNGWriter::ChunkData
>& GetChunks()
64 std::vector
<vcl::PNGWriter::ChunkData
> maChunkSeq
;
66 sal_Int32 mnCompLevel
;
67 sal_Int32 mnInterlaced
;
68 sal_uInt32 mnMaxChunkSize
;
71 Bitmap::ScopedReadAccess mpAccess
;
72 BitmapReadAccess
* mpMaskAccess
;
75 std::unique_ptr
<sal_uInt8
[]> mpDeflateInBuf
; // as big as the size of a scanline + alphachannel + 1
76 std::unique_ptr
<sal_uInt8
[]> mpPreviousScan
; // as big as mpDeflateInBuf
77 std::unique_ptr
<sal_uInt8
[]> mpCurrentScan
;
78 sal_uLong mnDeflateInSize
;
82 sal_uInt8 mnBitsPerPixel
;
83 sal_uInt8 mnFilterType
; // 0 or 4;
84 sal_uLong mnBBP
; // bytes per pixel ( needed for filtering )
87 void ImplWritepHYs(const BitmapEx
& rBitmapEx
);
89 sal_uLong
ImplGetFilter(sal_uLong nY
, sal_uLong nXStart
= 0, sal_uLong nXAdd
= 1);
90 void ImplClearFirstScanline();
91 void ImplWriteTransparent();
92 bool ImplWriteHeader();
93 void ImplWritePalette();
94 void ImplOpenChunk(sal_uLong nChunkType
);
95 void ImplWriteChunk(sal_uInt8 nNumb
);
96 void ImplWriteChunk(sal_uInt32 nNumb
);
97 void ImplWriteChunk(unsigned char const * pSource
, sal_uInt32 nDatSize
);
100 PNGWriterImpl::PNGWriterImpl( const BitmapEx
& rBitmapEx
,
101 const css::uno::Sequence
<css::beans::PropertyValue
>* pFilterData
)
102 : mnCompLevel(PNG_DEF_COMPRESSION
)
106 , mpMaskAccess(nullptr)
115 if (!rBitmapEx
.IsEmpty())
119 if (rBitmapEx
.GetBitmap().GetBitCount() == 32)
121 if (!vcl::bitmap::convertBitmap32To24Plus8(rBitmapEx
, aBitmapEx
))
126 aBitmapEx
= rBitmapEx
;
129 Bitmap
aBmp(aBitmapEx
.GetBitmap());
131 mnMaxChunkSize
= std::numeric_limits
<sal_uInt32
>::max();
135 for (const auto& rPropVal
: *pFilterData
)
137 if (rPropVal
.Name
== "Compression")
138 rPropVal
.Value
>>= mnCompLevel
;
139 else if (rPropVal
.Name
== "Interlaced")
140 rPropVal
.Value
>>= mnInterlaced
;
141 else if (rPropVal
.Name
== "MaxChunkSize")
144 if (rPropVal
.Value
>>= nVal
)
145 mnMaxChunkSize
= static_cast<sal_uInt32
>(nVal
);
149 mnBitsPerPixel
= static_cast<sal_uInt8
>(aBmp
.GetBitCount());
151 if (aBitmapEx
.IsTransparent())
153 if (mnBitsPerPixel
<= 8 && aBitmapEx
.IsAlpha())
155 aBmp
.Convert( BmpConversion::N24Bit
);
159 if (mnBitsPerPixel
<= 8) // transparent palette
161 aBmp
.Convert(BmpConversion::N8BitTrans
);
162 aBmp
.Replace(aBitmapEx
.GetMask(), BMP_COL_TRANS
);
164 mpAccess
= Bitmap::ScopedReadAccess(aBmp
);
167 if (ImplWriteHeader())
169 ImplWritepHYs(aBitmapEx
);
171 ImplWriteTransparent();
183 mpAccess
= Bitmap::ScopedReadAccess(aBmp
); // true RGB with alphachannel
186 mbTrueAlpha
= aBitmapEx
.IsAlpha();
189 AlphaMask
aMask(aBitmapEx
.GetAlpha());
190 mpMaskAccess
= aMask
.AcquireReadAccess();
193 if (ImplWriteHeader())
195 ImplWritepHYs(aBitmapEx
);
198 aMask
.ReleaseAccess(mpMaskAccess
);
199 mpMaskAccess
= nullptr;
208 Bitmap
aMask(aBitmapEx
.GetMask());
209 mpMaskAccess
= aMask
.AcquireReadAccess();
212 if (ImplWriteHeader())
214 ImplWritepHYs(aBitmapEx
);
217 Bitmap::ReleaseAccess(mpMaskAccess
);
218 mpMaskAccess
= nullptr;
235 mpAccess
= Bitmap::ScopedReadAccess(aBmp
); // palette + RGB without alphachannel
238 if (ImplWriteHeader())
240 ImplWritepHYs(aBitmapEx
);
241 if (mpAccess
->HasPalette())
256 ImplOpenChunk(PNGCHUNK_IEND
); // create an IEND chunk
261 bool PNGWriterImpl::Write(SvStream
& rOStm
)
263 /* png signature is always an array of 8 bytes */
264 SvStreamEndian nOldMode
= rOStm
.GetEndian();
265 rOStm
.SetEndian(SvStreamEndian::BIG
);
266 rOStm
.WriteUInt32(0x89504e47);
267 rOStm
.WriteUInt32(0x0d0a1a0a);
269 for (auto const& chunk
: maChunkSeq
)
271 sal_uInt32 nType
= chunk
.nType
;
272 #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
273 nType
= OSL_SWAPDWORD(nType
);
275 sal_uInt32 nCRC
= rtl_crc32(0, &nType
, 4);
276 sal_uInt32 nDataSize
= chunk
.aData
.size();
278 nCRC
= rtl_crc32(nCRC
, chunk
.aData
.data(), nDataSize
);
279 rOStm
.WriteUInt32(nDataSize
);
280 rOStm
.WriteUInt32(chunk
.nType
);
282 rOStm
.WriteBytes(chunk
.aData
.data(), nDataSize
);
283 rOStm
.WriteUInt32(nCRC
);
285 rOStm
.SetEndian(nOldMode
);
290 bool PNGWriterImpl::ImplWriteHeader()
292 ImplOpenChunk(PNGCHUNK_IHDR
);
293 mnWidth
= mpAccess
->Width();
294 ImplWriteChunk(sal_uInt32(mnWidth
));
295 mnHeight
= mpAccess
->Height();
296 ImplWriteChunk(sal_uInt32(mnHeight
));
298 if (mnWidth
&& mnHeight
&& mnBitsPerPixel
&& mbStatus
)
300 sal_uInt8 nBitDepth
= mnBitsPerPixel
;
301 if (mnBitsPerPixel
<= 8)
306 sal_uInt8 nColorType
= 2; // colortype:
308 // bit 0 -> palette is used
309 if (mpAccess
->HasPalette()) // bit 1 -> color is used
310 nColorType
|= 1; // bit 2 -> alpha channel is used
317 ImplWriteChunk(nBitDepth
);
318 ImplWriteChunk(nColorType
); // colortype
319 ImplWriteChunk(static_cast<sal_uInt8
>(0)); // compression type
320 ImplWriteChunk(static_cast<sal_uInt8
>(0)); // filter type - is not supported in this version
321 ImplWriteChunk(static_cast<sal_uInt8
>(mnInterlaced
)); // interlace type
330 void PNGWriterImpl::ImplWritePalette()
332 const sal_uLong nCount
= mpAccess
->GetPaletteEntryCount();
333 std::unique_ptr
<sal_uInt8
[]> pTempBuf(new sal_uInt8
[nCount
* 3]);
334 sal_uInt8
* pTmp
= pTempBuf
.get();
336 ImplOpenChunk(PNGCHUNK_PLTE
);
338 for ( sal_uLong i
= 0; i
< nCount
; i
++ )
340 const BitmapColor
& rColor
= mpAccess
->GetPaletteColor(i
);
341 *pTmp
++ = rColor
.GetRed();
342 *pTmp
++ = rColor
.GetGreen();
343 *pTmp
++ = rColor
.GetBlue();
345 ImplWriteChunk(pTempBuf
.get(), nCount
* 3);
348 void PNGWriterImpl::ImplWriteTransparent()
350 const sal_uLong nTransIndex
= mpAccess
->GetBestPaletteIndex(BMP_COL_TRANS
);
352 ImplOpenChunk(PNGCHUNK_tRNS
);
354 for (sal_uLong n
= 0; n
<= nTransIndex
; n
++)
356 ImplWriteChunk((nTransIndex
== n
) ? static_cast<sal_uInt8
>(0x0) : static_cast<sal_uInt8
>(0xff));
360 void PNGWriterImpl::ImplWritepHYs(const BitmapEx
& rBmpEx
)
362 if (rBmpEx
.GetPrefMapMode().GetMapUnit() == MapUnit::Map100thMM
)
364 Size
aPrefSize(rBmpEx
.GetPrefSize());
366 if (aPrefSize
.Width() && aPrefSize
.Height() && mnWidth
&& mnHeight
)
368 ImplOpenChunk(PNGCHUNK_pHYs
);
369 sal_uInt32 nPrefSizeX
= static_cast<sal_uInt32
>(100000.0 / (static_cast<double>(aPrefSize
.Width()) / mnWidth
) + 0.5);
370 sal_uInt32 nPrefSizeY
= static_cast<sal_uInt32
>(100000.0 / (static_cast<double>(aPrefSize
.Height()) / mnHeight
) + 0.5);
371 ImplWriteChunk(nPrefSizeX
);
372 ImplWriteChunk(nPrefSizeY
);
373 ImplWriteChunk(sal_uInt8(1)); // nMapUnit
378 void PNGWriterImpl::ImplWriteIDAT()
380 mnDeflateInSize
= mnBitsPerPixel
;
383 mnDeflateInSize
+= 8;
385 mnBBP
= (mnDeflateInSize
+ 7) >> 3;
387 mnDeflateInSize
= mnBBP
* mnWidth
+ 1;
389 mpDeflateInBuf
.reset(new sal_uInt8
[mnDeflateInSize
]);
391 if (mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
393 mpPreviousScan
.reset(new sal_uInt8
[mnDeflateInSize
]);
394 mpCurrentScan
.reset(new sal_uInt8
[mnDeflateInSize
]);
395 ImplClearFirstScanline();
397 mpZCodec
.BeginCompression(mnCompLevel
);
398 SvMemoryStream aOStm
;
399 if (mnInterlaced
== 0)
401 for (sal_uLong nY
= 0; nY
< mnHeight
; nY
++)
403 mpZCodec
.Write(aOStm
, mpDeflateInBuf
.get(), ImplGetFilter(nY
));
410 for (nY
= 0; nY
< mnHeight
; nY
+= 8) // pass 1
412 mpZCodec
.Write(aOStm
, mpDeflateInBuf
.get(), ImplGetFilter(nY
, 0, 8));
414 ImplClearFirstScanline();
416 for (nY
= 0; nY
< mnHeight
; nY
+= 8) // pass 2
418 mpZCodec
.Write(aOStm
, mpDeflateInBuf
.get(), ImplGetFilter(nY
, 4, 8));
420 ImplClearFirstScanline();
422 if (mnHeight
>= 5) // pass 3
424 for (nY
= 4; nY
< mnHeight
; nY
+= 8)
426 mpZCodec
.Write(aOStm
, mpDeflateInBuf
.get(), ImplGetFilter(nY
, 0, 4));
428 ImplClearFirstScanline();
431 for (nY
= 0; nY
< mnHeight
; nY
+= 4) // pass 4
433 mpZCodec
.Write(aOStm
, mpDeflateInBuf
.get(), ImplGetFilter(nY
, 2, 4));
435 ImplClearFirstScanline();
437 if (mnHeight
>= 3) // pass 5
439 for (nY
= 2; nY
< mnHeight
; nY
+= 4)
441 mpZCodec
.Write(aOStm
, mpDeflateInBuf
.get(), ImplGetFilter(nY
, 0, 2));
443 ImplClearFirstScanline();
446 for (nY
= 0; nY
< mnHeight
; nY
+= 2) // pass 6
448 mpZCodec
.Write(aOStm
, mpDeflateInBuf
.get(), ImplGetFilter(nY
, 1, 2));
450 ImplClearFirstScanline();
452 if (mnHeight
>= 2) // pass 7
454 for (nY
= 1; nY
< mnHeight
; nY
+= 2)
456 mpZCodec
.Write(aOStm
, mpDeflateInBuf
.get(), ImplGetFilter (nY
));
460 mpZCodec
.EndCompression();
462 if (mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
464 mpCurrentScan
.reset();
465 mpPreviousScan
.reset();
467 mpDeflateInBuf
.reset();
469 sal_uInt32 nIDATSize
= aOStm
.Tell();
470 sal_uInt32 nBytes
, nBytesToWrite
= nIDATSize
;
473 nBytes
= nBytesToWrite
<= mnMaxChunkSize
? nBytesToWrite
: mnMaxChunkSize
;
474 ImplOpenChunk(PNGCHUNK_IDAT
);
475 ImplWriteChunk(const_cast<unsigned char *>(static_cast<unsigned char const *>(aOStm
.GetData())) + (nIDATSize
- nBytesToWrite
), nBytes
);
476 nBytesToWrite
-= nBytes
;
480 // ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd
481 // appends to the currently used pass
482 // the complete size of scanline will be returned - in interlace mode zero is possible!
484 sal_uLong
PNGWriterImpl::ImplGetFilter (sal_uLong nY
, sal_uLong nXStart
, sal_uLong nXAdd
)
489 pDest
= mpCurrentScan
.get();
491 pDest
= mpDeflateInBuf
.get();
493 if (nXStart
< mnWidth
)
495 *pDest
++ = mnFilterType
; // in this version the filter type is either 0 or 4
497 if (mpAccess
->HasPalette()) // alphachannel is not allowed by pictures including palette entries
499 switch (mnBitsPerPixel
)
503 Scanline pScanline
= mpAccess
->GetScanline( nY
);
504 sal_uLong nX
, nXIndex
;
505 for (nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+= nXAdd
, nXIndex
++)
507 sal_uLong nShift
= (nXIndex
& 7) ^ 7;
509 *pDest
= mpAccess
->GetIndexFromData(pScanline
, nX
) << nShift
;
510 else if (nShift
== 0)
511 *pDest
++ |= mpAccess
->GetIndexFromData(pScanline
, nX
) << nShift
;
513 *pDest
|= mpAccess
->GetIndexFromData(pScanline
, nX
) << nShift
;
515 if ( (nXIndex
& 7) != 0 )
516 pDest
++; // byte is not completely used, so the bufferpointer is to correct
522 Scanline pScanline
= mpAccess
->GetScanline( nY
);
523 sal_uLong nX
, nXIndex
;
524 for (nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+= nXAdd
, nXIndex
++)
527 *pDest
++ |= mpAccess
->GetIndexFromData(pScanline
, nX
);
529 *pDest
= mpAccess
->GetIndexFromData(pScanline
, nX
) << 4;
538 Scanline pScanline
= mpAccess
->GetScanline( nY
);
539 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
541 *pDest
++ = mpAccess
->GetIndexFromData( pScanline
, nX
);
553 if (mpMaskAccess
) // mpMaskAccess != NULL -> alphachannel is to create
557 Scanline pScanline
= mpAccess
->GetScanline( nY
);
558 Scanline pScanlineMask
= mpMaskAccess
->GetScanline( nY
);
559 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
561 const BitmapColor
& rColor
= mpAccess
->GetPixelFromData(pScanline
, nX
);
562 *pDest
++ = rColor
.GetRed();
563 *pDest
++ = rColor
.GetGreen();
564 *pDest
++ = rColor
.GetBlue();
565 *pDest
++ = 255 - mpMaskAccess
->GetIndexFromData(pScanlineMask
, nX
);
570 const BitmapColor
aTrans(mpMaskAccess
->GetBestMatchingColor(COL_WHITE
));
571 Scanline pScanline
= mpAccess
->GetScanline( nY
);
572 Scanline pScanlineMask
= mpMaskAccess
->GetScanline( nY
);
574 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
576 const BitmapColor
& rColor
= mpAccess
->GetPixelFromData(pScanline
, nX
);
577 *pDest
++ = rColor
.GetRed();
578 *pDest
++ = rColor
.GetGreen();
579 *pDest
++ = rColor
.GetBlue();
581 if(mpMaskAccess
->GetPixelFromData(pScanlineMask
, nX
) == aTrans
)
590 Scanline pScanline
= mpAccess
->GetScanline( nY
);
591 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
593 const BitmapColor
& rColor
= mpAccess
->GetPixelFromData(pScanline
, nX
);
594 *pDest
++ = rColor
.GetRed();
595 *pDest
++ = rColor
.GetGreen();
596 *pDest
++ = rColor
.GetBlue();
601 // filter type4 ( PAETH ) will be used only for 24bit graphics
604 mnDeflateInSize
= pDest
- mpCurrentScan
.get();
605 pDest
= mpDeflateInBuf
.get();
606 *pDest
++ = 4; // filter type
608 sal_uInt8
* p1
= mpCurrentScan
.get() + 1; // Current Pixel
609 sal_uInt8
* p2
= p1
- mnBBP
; // left pixel
610 sal_uInt8
* p3
= mpPreviousScan
.get(); // upper pixel
611 sal_uInt8
* p4
= p3
- mnBBP
; // upperleft Pixel;
613 while (pDest
< mpDeflateInBuf
.get() + mnDeflateInSize
)
615 sal_uLong nb
= *p3
++;
617 if (p2
>= mpCurrentScan
.get() + 1)
627 long np
= na
+ nb
- nc
;
639 if (npa
<= npb
&& npa
<= npc
)
640 *pDest
++ = *p1
++ - static_cast<sal_uInt8
>(na
);
641 else if ( npb
<= npc
)
642 *pDest
++ = *p1
++ - static_cast<sal_uInt8
>(nb
);
644 *pDest
++ = *p1
++ - static_cast<sal_uInt8
>(nc
);
649 for (long i
= 0; i
< static_cast<long>(mnDeflateInSize
- 1); i
++)
651 mpPreviousScan
[i
] = mpCurrentScan
[i
+ 1];
656 mnDeflateInSize
= pDest
- mpDeflateInBuf
.get();
658 return mnDeflateInSize
;
661 void PNGWriterImpl::ImplClearFirstScanline()
664 memset(mpPreviousScan
.get(), 0, mnDeflateInSize
);
667 void PNGWriterImpl::ImplOpenChunk (sal_uLong nChunkType
)
669 maChunkSeq
.emplace_back();
670 maChunkSeq
.back().nType
= nChunkType
;
673 void PNGWriterImpl::ImplWriteChunk (sal_uInt8 nSource
)
675 maChunkSeq
.back().aData
.push_back(nSource
);
678 void PNGWriterImpl::ImplWriteChunk (sal_uInt32 nSource
)
680 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
681 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
>> 24));
682 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
>> 16));
683 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
>> 8));
684 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
));
687 void PNGWriterImpl::ImplWriteChunk (unsigned char const * pSource
, sal_uInt32 nDatSize
)
691 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
692 sal_uInt32 nSize
= rChunkData
.aData
.size();
693 rChunkData
.aData
.resize(nSize
+ nDatSize
);
694 memcpy(&rChunkData
.aData
[nSize
], pSource
, nDatSize
);
698 PNGWriter::PNGWriter(const BitmapEx
& rBmpEx
,
699 const css::uno::Sequence
<css::beans::PropertyValue
>* pFilterData
)
700 : mpImpl(new vcl::PNGWriterImpl(rBmpEx
, pFilterData
))
704 PNGWriter::~PNGWriter()
708 bool PNGWriter::Write(SvStream
& rStream
)
710 return mpImpl
->Write(rStream
);
713 std::vector
<vcl::PNGWriter::ChunkData
>& PNGWriter::GetChunks()
715 return mpImpl
->GetChunks();
720 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */