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>
25 #include <rtl/alloc.h>
26 #include <tools/zcodec.hxx>
27 #include <tools/stream.hxx>
28 #include <vcl/bitmapaccess.hxx>
29 #include <vcl/svapp.hxx>
30 #include <vcl/alpha.hxx>
31 #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 css::uno::Sequence
<css::beans::PropertyValue
>* pFilterData
);
53 bool Write(SvStream
& rOutStream
);
55 std::vector
<vcl::PNGWriter::ChunkData
>& GetChunks()
62 std::vector
<vcl::PNGWriter::ChunkData
> maChunkSeq
;
64 sal_Int32 mnCompLevel
;
65 sal_Int32 mnInterlaced
;
66 sal_uInt32 mnMaxChunkSize
;
69 Bitmap::ScopedReadAccess mpAccess
;
70 BitmapReadAccess
* mpMaskAccess
;
73 sal_uInt8
* mpDeflateInBuf
; // as big as the size of a scanline + alphachannel + 1
74 sal_uInt8
* mpPreviousScan
; // as big as mpDeflateInBuf
75 sal_uInt8
* mpCurrentScan
;
76 sal_uLong mnDeflateInSize
;
80 sal_uInt8 mnBitsPerPixel
;
81 sal_uInt8 mnFilterType
; // 0 or 4;
82 sal_uLong mnBBP
; // bytes per pixel ( needed for filtering )
86 void ImplWritepHYs(const BitmapEx
& rBitmapEx
);
88 sal_uLong
ImplGetFilter(sal_uLong nY
, sal_uLong nXStart
= 0, sal_uLong nXAdd
= 1);
89 void ImplClearFirstScanline();
90 void ImplWriteTransparent();
91 bool ImplWriteHeader();
92 void ImplWritePalette();
93 void ImplOpenChunk(sal_uLong nChunkType
);
94 void ImplWriteChunk(sal_uInt8 nNumb
);
95 void ImplWriteChunk(sal_uInt32 nNumb
);
96 void ImplWriteChunk(unsigned char* pSource
, sal_uInt32 nDatSize
);
99 PNGWriterImpl::PNGWriterImpl( const BitmapEx
& rBmpEx
,
100 const css::uno::Sequence
<css::beans::PropertyValue
>* pFilterData
)
101 : mnCompLevel(PNG_DEF_COMPRESSION
)
105 , mpMaskAccess(nullptr)
106 , mpDeflateInBuf(nullptr)
107 , mpPreviousScan(nullptr)
108 , mpCurrentScan(nullptr)
118 if (!rBmpEx
.IsEmpty())
120 Bitmap
aBmp(rBmpEx
.GetBitmap());
122 mnInterlaced
= 0; // ( aBmp.GetSizePixel().Width() > 128 ) || ( aBmp.GetSizePixel().Height() > 128 ) ? 1 : 0; #i67236#
124 // #i67234# defaulting max chunk size to 256kb when using interlace mode
125 mnMaxChunkSize
= mnInterlaced
== 0 ? std::numeric_limits
<sal_uInt32
>::max() : 0x40000;
130 for (i
= 0; i
< pFilterData
->getLength(); i
++)
132 if ((*pFilterData
)[i
].Name
== "Compression")
133 (*pFilterData
)[i
].Value
>>= mnCompLevel
;
134 else if ((*pFilterData
)[i
].Name
== "Interlaced")
135 (*pFilterData
)[i
].Value
>>= mnInterlaced
;
136 else if ((*pFilterData
)[i
].Name
== "MaxChunkSize")
139 if ((*pFilterData
)[i
].Value
>>= nVal
)
140 mnMaxChunkSize
= static_cast<sal_uInt32
>(nVal
);
144 mnBitsPerPixel
= static_cast<sal_uInt8
>(aBmp
.GetBitCount());
146 if (rBmpEx
.IsTransparent())
148 if (mnBitsPerPixel
<= 8 && rBmpEx
.IsAlpha())
150 aBmp
.Convert( BmpConversion::N24Bit
);
154 if (mnBitsPerPixel
<= 8) // transparent palette
156 aBmp
.Convert(BmpConversion::N8BitTrans
);
157 aBmp
.Replace(rBmpEx
.GetMask(), BMP_COL_TRANS
);
159 mpAccess
= Bitmap::ScopedReadAccess(aBmp
);
162 if (ImplWriteHeader())
164 ImplWritepHYs(rBmpEx
);
166 ImplWriteTransparent();
178 mpAccess
= Bitmap::ScopedReadAccess(aBmp
); // true RGB with alphachannel
181 mbTrueAlpha
= rBmpEx
.IsAlpha();
184 AlphaMask
aMask(rBmpEx
.GetAlpha());
185 mpMaskAccess
= aMask
.AcquireReadAccess();
188 if (ImplWriteHeader())
190 ImplWritepHYs(rBmpEx
);
193 aMask
.ReleaseAccess(mpMaskAccess
);
194 mpMaskAccess
= nullptr;
203 Bitmap
aMask(rBmpEx
.GetMask());
204 mpMaskAccess
= aMask
.AcquireReadAccess();
207 if (ImplWriteHeader())
209 ImplWritepHYs(rBmpEx
);
212 Bitmap::ReleaseAccess(mpMaskAccess
);
213 mpMaskAccess
= nullptr;
230 mpAccess
= Bitmap::ScopedReadAccess(aBmp
); // palette + RGB without alphachannel
233 if (ImplWriteHeader())
235 ImplWritepHYs(rBmpEx
);
236 if (mpAccess
->HasPalette())
251 ImplOpenChunk(PNGCHUNK_IEND
); // create an IEND chunk
256 bool PNGWriterImpl::Write(SvStream
& rOStm
)
258 /* png signature is always an array of 8 bytes */
259 SvStreamEndian nOldMode
= rOStm
.GetEndian();
260 rOStm
.SetEndian(SvStreamEndian::BIG
);
261 rOStm
.WriteUInt32(0x89504e47);
262 rOStm
.WriteUInt32(0x0d0a1a0a);
264 std::vector
< vcl::PNGWriter::ChunkData
>::iterator
aBeg(maChunkSeq
.begin());
265 std::vector
< vcl::PNGWriter::ChunkData
>::iterator
aEnd(maChunkSeq
.end());
268 sal_uInt32 nType
= aBeg
->nType
;
269 #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
270 nType
= OSL_SWAPDWORD(nType
);
272 sal_uInt32 nCRC
= rtl_crc32(0, &nType
, 4);
273 sal_uInt32 nDataSize
= aBeg
->aData
.size();
275 nCRC
= rtl_crc32(nCRC
, &aBeg
->aData
[0], nDataSize
);
276 rOStm
.WriteUInt32(nDataSize
);
277 rOStm
.WriteUInt32(aBeg
->nType
);
279 rOStm
.WriteBytes(&aBeg
->aData
[0], nDataSize
);
280 rOStm
.WriteUInt32(nCRC
);
283 rOStm
.SetEndian(nOldMode
);
288 bool PNGWriterImpl::ImplWriteHeader()
290 ImplOpenChunk(PNGCHUNK_IHDR
);
291 ImplWriteChunk(sal_uInt32(mnWidth
= mpAccess
->Width()));
292 ImplWriteChunk(sal_uInt32(mnHeight
= mpAccess
->Height()));
294 if (mnWidth
&& mnHeight
&& mnBitsPerPixel
&& mbStatus
)
296 sal_uInt8 nBitDepth
= mnBitsPerPixel
;
297 if (mnBitsPerPixel
<= 8)
302 sal_uInt8 nColorType
= 2; // colortype:
304 // bit 0 -> palette is used
305 if (mpAccess
->HasPalette()) // bit 1 -> color is used
306 nColorType
|= 1; // bit 2 -> alpha channel is used
313 ImplWriteChunk(nBitDepth
);
314 ImplWriteChunk(nColorType
); // colortype
315 ImplWriteChunk(static_cast<sal_uInt8
>(0)); // compression type
316 ImplWriteChunk(static_cast<sal_uInt8
>(0)); // filter type - is not supported in this version
317 ImplWriteChunk(static_cast<sal_uInt8
>(mnInterlaced
)); // interlace type
326 void PNGWriterImpl::ImplWritePalette()
328 const sal_uLong nCount
= mpAccess
->GetPaletteEntryCount();
329 std::unique_ptr
<sal_uInt8
[]> pTempBuf(new sal_uInt8
[nCount
* 3]);
330 sal_uInt8
* pTmp
= pTempBuf
.get();
332 ImplOpenChunk(PNGCHUNK_PLTE
);
334 for ( sal_uLong i
= 0; i
< nCount
; i
++ )
336 const BitmapColor
& rColor
= mpAccess
->GetPaletteColor(i
);
337 *pTmp
++ = rColor
.GetRed();
338 *pTmp
++ = rColor
.GetGreen();
339 *pTmp
++ = rColor
.GetBlue();
341 ImplWriteChunk(pTempBuf
.get(), nCount
* 3);
344 void PNGWriterImpl::ImplWriteTransparent()
346 const sal_uLong nTransIndex
= mpAccess
->GetBestPaletteIndex(BMP_COL_TRANS
);
348 ImplOpenChunk(PNGCHUNK_tRNS
);
350 for (sal_uLong n
= 0UL; n
<= nTransIndex
; n
++)
352 ImplWriteChunk((nTransIndex
== n
) ? static_cast<sal_uInt8
>(0x0) : static_cast<sal_uInt8
>(0xff));
356 void PNGWriterImpl::ImplWritepHYs(const BitmapEx
& rBmpEx
)
358 if (rBmpEx
.GetPrefMapMode() == MapUnit::Map100thMM
)
360 Size
aPrefSize(rBmpEx
.GetPrefSize());
362 if (aPrefSize
.Width() && aPrefSize
.Height() && mnWidth
&& mnHeight
)
364 ImplOpenChunk(PNGCHUNK_pHYs
);
365 sal_uInt8 nMapUnit
= 1;
366 sal_uInt32 nPrefSizeX
= static_cast<sal_uInt32
>(100000.0 / (static_cast<double>(aPrefSize
.Width()) / mnWidth
) + 0.5);
367 sal_uInt32 nPrefSizeY
= static_cast<sal_uInt32
>(100000.0 / (static_cast<double>(aPrefSize
.Height()) / mnHeight
) + 0.5);
368 ImplWriteChunk(nPrefSizeX
);
369 ImplWriteChunk(nPrefSizeY
);
370 ImplWriteChunk(nMapUnit
);
375 void PNGWriterImpl::ImplWriteIDAT()
377 mnDeflateInSize
= mnBitsPerPixel
;
380 mnDeflateInSize
+= 8;
382 mnBBP
= (mnDeflateInSize
+ 7) >> 3;
384 mnDeflateInSize
= mnBBP
* mnWidth
+ 1;
386 mpDeflateInBuf
= new sal_uInt8
[mnDeflateInSize
];
388 if (mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
390 mpPreviousScan
= new sal_uInt8
[mnDeflateInSize
];
391 mpCurrentScan
= new sal_uInt8
[mnDeflateInSize
];
392 ImplClearFirstScanline();
394 mpZCodec
.BeginCompression(mnCompLevel
, true);
395 mpZCodec
.SetCRC(mnCRC
);
396 SvMemoryStream aOStm
;
397 if (mnInterlaced
== 0)
399 for (sal_uLong nY
= 0; nY
< mnHeight
; nY
++)
401 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
));
408 for (nY
= 0; nY
< mnHeight
; nY
+= 8) // pass 1
410 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 0, 8));
412 ImplClearFirstScanline();
414 for (nY
= 0; nY
< mnHeight
; nY
+= 8) // pass 2
416 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 4, 8));
418 ImplClearFirstScanline();
420 if (mnHeight
>= 5) // pass 3
422 for (nY
= 4; nY
< mnHeight
; nY
+= 8)
424 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 0, 4));
426 ImplClearFirstScanline();
429 for (nY
= 0; nY
< mnHeight
; nY
+= 4) // pass 4
431 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 2, 4));
433 ImplClearFirstScanline();
435 if (mnHeight
>= 3) // pass 5
437 for (nY
= 2; nY
< mnHeight
; nY
+= 4)
439 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 0, 2));
441 ImplClearFirstScanline();
444 for (nY
= 0; nY
< mnHeight
; nY
+= 2) // pass 6
446 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 1, 2));
448 ImplClearFirstScanline();
450 if (mnHeight
>= 2) // pass 7
452 for (nY
= 1; nY
< mnHeight
; nY
+= 2)
454 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter (nY
));
458 mpZCodec
.EndCompression();
459 mnCRC
= mpZCodec
.GetCRC();
461 if (mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
463 delete[] mpCurrentScan
;
464 delete[] mpPreviousScan
;
466 delete[] mpDeflateInBuf
;
468 sal_uInt32 nIDATSize
= aOStm
.Tell();
469 sal_uInt32 nBytes
, nBytesToWrite
= nIDATSize
;
472 nBytes
= nBytesToWrite
<= mnMaxChunkSize
? nBytesToWrite
: mnMaxChunkSize
;
473 ImplOpenChunk(PNGCHUNK_IDAT
);
474 ImplWriteChunk(const_cast<unsigned char *>(static_cast<unsigned char const *>(aOStm
.GetData())) + (nIDATSize
- nBytesToWrite
), nBytes
);
475 nBytesToWrite
-= nBytes
;
479 // ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd
480 // appends to the currently used pass
481 // the complete size of scanline will be returned - in interlace mode zero is possible!
483 sal_uLong
PNGWriterImpl::ImplGetFilter (sal_uLong nY
, sal_uLong nXStart
, sal_uLong nXAdd
)
488 pDest
= mpCurrentScan
;
490 pDest
= mpDeflateInBuf
;
492 if (nXStart
< mnWidth
)
494 *pDest
++ = mnFilterType
; // in this version the filter type is either 0 or 4
496 if (mpAccess
->HasPalette()) // alphachannel is not allowed by pictures including palette entries
498 switch (mnBitsPerPixel
)
502 sal_uLong nX
, nXIndex
;
503 for (nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+= nXAdd
, nXIndex
++)
505 sal_uLong nShift
= (nXIndex
& 7) ^ 7;
507 *pDest
= mpAccess
->GetPixelIndex(nY
, nX
) << nShift
;
508 else if (nShift
== 0)
509 *pDest
++ |= mpAccess
->GetPixelIndex(nY
, nX
) << nShift
;
511 *pDest
|= mpAccess
->GetPixelIndex(nY
, nX
) << nShift
;
513 if ( (nXIndex
& 7) != 0 )
514 pDest
++; // byte is not completely used, so the bufferpointer is to correct
520 sal_uLong nX
, nXIndex
;
521 for (nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+= nXAdd
, nXIndex
++)
524 *pDest
++ |= mpAccess
->GetPixelIndex(nY
, nX
);
526 *pDest
= mpAccess
->GetPixelIndex(nY
, nX
) << 4;
535 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
537 *pDest
++ = mpAccess
->GetPixelIndex( nY
, nX
);
549 if (mpMaskAccess
) // mpMaskAccess != NULL -> alphachannel is to create
553 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
555 const BitmapColor
& rColor
= mpAccess
->GetPixel(nY
, nX
);
556 *pDest
++ = rColor
.GetRed();
557 *pDest
++ = rColor
.GetGreen();
558 *pDest
++ = rColor
.GetBlue();
559 *pDest
++ = 255 - mpMaskAccess
->GetPixelIndex(nY
, nX
);
564 const BitmapColor
aTrans(mpMaskAccess
->GetBestMatchingColor(Color(COL_WHITE
)));
566 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
568 const BitmapColor
& rColor
= mpAccess
->GetPixel(nY
, nX
);
569 *pDest
++ = rColor
.GetRed();
570 *pDest
++ = rColor
.GetGreen();
571 *pDest
++ = rColor
.GetBlue();
573 if(mpMaskAccess
->GetPixel(nY
, nX
) == aTrans
)
582 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
584 const BitmapColor
& rColor
= mpAccess
->GetPixel(nY
, nX
);
585 *pDest
++ = rColor
.GetRed();
586 *pDest
++ = rColor
.GetGreen();
587 *pDest
++ = rColor
.GetBlue();
592 // filter type4 ( PAETH ) will be used only for 24bit graphics
595 mnDeflateInSize
= pDest
- mpCurrentScan
;
596 pDest
= mpDeflateInBuf
;
597 *pDest
++ = 4; // filter type
599 sal_uInt8
* p1
= mpCurrentScan
+ 1; // Current Pixel
600 sal_uInt8
* p2
= p1
- mnBBP
; // left pixel
601 sal_uInt8
* p3
= mpPreviousScan
; // upper pixel
602 sal_uInt8
* p4
= p3
- mnBBP
; // upperleft Pixel;
604 while (pDest
< mpDeflateInBuf
+ mnDeflateInSize
)
606 sal_uLong nb
= *p3
++;
608 if (p2
>= mpCurrentScan
+ 1)
618 long np
= na
+ nb
- nc
;
630 if (npa
<= npb
&& npa
<= npc
)
631 *pDest
++ = *p1
++ - static_cast<sal_uInt8
>(na
);
632 else if ( npb
<= npc
)
633 *pDest
++ = *p1
++ - static_cast<sal_uInt8
>(nb
);
635 *pDest
++ = *p1
++ - static_cast<sal_uInt8
>(nc
);
640 for (long i
= 0; i
< static_cast<long>(mnDeflateInSize
- 1); i
++)
642 mpPreviousScan
[i
] = mpCurrentScan
[i
+ 1];
647 mnDeflateInSize
= pDest
- mpDeflateInBuf
;
649 return mnDeflateInSize
;
652 void PNGWriterImpl::ImplClearFirstScanline()
655 memset(mpPreviousScan
, 0, mnDeflateInSize
);
658 void PNGWriterImpl::ImplOpenChunk (sal_uLong nChunkType
)
660 maChunkSeq
.resize(maChunkSeq
.size() + 1);
661 maChunkSeq
.back().nType
= nChunkType
;
664 void PNGWriterImpl::ImplWriteChunk (sal_uInt8 nSource
)
666 maChunkSeq
.back().aData
.push_back(nSource
);
669 void PNGWriterImpl::ImplWriteChunk (sal_uInt32 nSource
)
671 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
672 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
>> 24));
673 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
>> 16));
674 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
>> 8));
675 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
));
678 void PNGWriterImpl::ImplWriteChunk (unsigned char* pSource
, sal_uInt32 nDatSize
)
682 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
683 sal_uInt32 nSize
= rChunkData
.aData
.size();
684 rChunkData
.aData
.resize(nSize
+ nDatSize
);
685 memcpy(&rChunkData
.aData
[nSize
], pSource
, nDatSize
);
689 PNGWriter::PNGWriter(const BitmapEx
& rBmpEx
,
690 const css::uno::Sequence
<css::beans::PropertyValue
>* pFilterData
)
691 : mpImpl(new vcl::PNGWriterImpl(rBmpEx
, pFilterData
))
695 PNGWriter::~PNGWriter()
699 bool PNGWriter::Write(SvStream
& rStream
)
701 return mpImpl
->Write(rStream
);
704 std::vector
<vcl::PNGWriter::ChunkData
>& PNGWriter::GetChunks()
706 return mpImpl
->GetChunks();
711 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */