Version 5.4.3.2, tag libreoffice-5.4.3.2
[LibreOffice.git] / vcl / source / gdi / pngwrite.cxx
blob68a66800b13bb6191012ae8c353c436ff8de2bf7
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
22 #include <cmath>
23 #include <limits>
24 #include <rtl/crc.h>
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>
32 #include <memory>
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
43 namespace vcl
46 class PNGWriterImpl
48 public:
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()
57 return maChunkSeq;
60 private:
62 std::vector<vcl::PNGWriter::ChunkData> maChunkSeq;
64 sal_Int32 mnCompLevel;
65 sal_Int32 mnInterlaced;
66 sal_uInt32 mnMaxChunkSize;
67 bool mbStatus;
69 Bitmap::ScopedReadAccess mpAccess;
70 BitmapReadAccess* mpMaskAccess;
71 ZCodec mpZCodec;
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;
78 sal_uLong mnWidth;
79 sal_uLong mnHeight;
80 sal_uInt8 mnBitsPerPixel;
81 sal_uInt8 mnFilterType; // 0 or 4;
82 sal_uLong mnBBP; // bytes per pixel ( needed for filtering )
83 bool mbTrueAlpha;
84 sal_uLong mnCRC;
86 void ImplWritepHYs(const BitmapEx& rBitmapEx);
87 void ImplWriteIDAT();
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)
102 , mnInterlaced(0)
103 , mnMaxChunkSize(0)
104 , mbStatus(true)
105 , mpMaskAccess(nullptr)
106 , mpDeflateInBuf(nullptr)
107 , mpPreviousScan(nullptr)
108 , mpCurrentScan(nullptr)
109 , mnDeflateInSize(0)
110 , mnWidth(0)
111 , mnHeight(0)
112 , mnBitsPerPixel(0)
113 , mnFilterType(0)
114 , mnBBP(0)
115 , mbTrueAlpha(false)
116 , mnCRC(0UL)
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;
127 if (pFilterData)
129 sal_Int32 i = 0;
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")
138 sal_Int32 nVal = 0;
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 );
151 mnBitsPerPixel = 24;
154 if (mnBitsPerPixel <= 8) // transparent palette
156 aBmp.Convert(BmpConversion::N8BitTrans);
157 aBmp.Replace(rBmpEx.GetMask(), BMP_COL_TRANS);
158 mnBitsPerPixel = 8;
159 mpAccess = Bitmap::ScopedReadAccess(aBmp);
160 if (mpAccess)
162 if (ImplWriteHeader())
164 ImplWritepHYs(rBmpEx);
165 ImplWritePalette();
166 ImplWriteTransparent();
167 ImplWriteIDAT();
169 mpAccess.reset();
171 else
173 mbStatus = false;
176 else
178 mpAccess = Bitmap::ScopedReadAccess(aBmp); // true RGB with alphachannel
179 if (mpAccess)
181 mbTrueAlpha = rBmpEx.IsAlpha();
182 if (mbTrueAlpha)
184 AlphaMask aMask(rBmpEx.GetAlpha());
185 mpMaskAccess = aMask.AcquireReadAccess();
186 if (mpMaskAccess)
188 if (ImplWriteHeader())
190 ImplWritepHYs(rBmpEx);
191 ImplWriteIDAT();
193 aMask.ReleaseAccess(mpMaskAccess);
194 mpMaskAccess = nullptr;
196 else
198 mbStatus = false;
201 else
203 Bitmap aMask(rBmpEx.GetMask());
204 mpMaskAccess = aMask.AcquireReadAccess();
205 if (mpMaskAccess)
207 if (ImplWriteHeader())
209 ImplWritepHYs(rBmpEx);
210 ImplWriteIDAT();
212 Bitmap::ReleaseAccess(mpMaskAccess);
213 mpMaskAccess = nullptr;
215 else
217 mbStatus = false;
220 mpAccess.reset();
222 else
224 mbStatus = false;
228 else
230 mpAccess = Bitmap::ScopedReadAccess(aBmp); // palette + RGB without alphachannel
231 if (mpAccess)
233 if (ImplWriteHeader())
235 ImplWritepHYs(rBmpEx);
236 if (mpAccess->HasPalette())
237 ImplWritePalette();
239 ImplWriteIDAT();
241 mpAccess.reset();
243 else
245 mbStatus = false;
249 if (mbStatus)
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());
266 while (aBeg != aEnd)
268 sal_uInt32 nType = aBeg->nType;
269 #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
270 nType = OSL_SWAPDWORD(nType);
271 #endif
272 sal_uInt32 nCRC = rtl_crc32(0, &nType, 4);
273 sal_uInt32 nDataSize = aBeg->aData.size();
274 if (nDataSize)
275 nCRC = rtl_crc32(nCRC, &aBeg->aData[0], nDataSize);
276 rOStm.WriteUInt32(nDataSize);
277 rOStm.WriteUInt32(aBeg->nType);
278 if (nDataSize)
279 rOStm.WriteBytes(&aBeg->aData[0], nDataSize);
280 rOStm.WriteUInt32(nCRC);
281 ++aBeg;
283 rOStm.SetEndian(nOldMode);
284 return mbStatus;
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)
298 mnFilterType = 0;
299 else
300 mnFilterType = 4;
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
307 else
308 nBitDepth /= 3;
310 if (mpMaskAccess)
311 nColorType |= 4;
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
319 else
321 mbStatus = false;
323 return mbStatus;
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;
379 if (mpMaskAccess)
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));
404 else
406 // interlace mode
407 sal_uLong 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;
470 while(nBytesToWrite)
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)
485 sal_uInt8* pDest;
487 if (mnFilterType)
488 pDest = mpCurrentScan;
489 else
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)
500 case 1:
502 sal_uLong nX, nXIndex;
503 for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++)
505 sal_uLong nShift = (nXIndex & 7) ^ 7;
506 if (nShift == 7)
507 *pDest = mpAccess->GetPixelIndex(nY, nX) << nShift;
508 else if (nShift == 0)
509 *pDest++ |= mpAccess->GetPixelIndex(nY, nX) << nShift;
510 else
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
516 break;
518 case 4:
520 sal_uLong nX, nXIndex;
521 for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++)
523 if(nXIndex & 1)
524 *pDest++ |= mpAccess->GetPixelIndex(nY, nX);
525 else
526 *pDest = mpAccess->GetPixelIndex(nY, nX) << 4;
528 if (nXIndex & 1)
529 pDest++;
531 break;
533 case 8:
535 for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd)
537 *pDest++ = mpAccess->GetPixelIndex( nY, nX );
540 break;
542 default :
543 mbStatus = false;
544 break;
547 else
549 if (mpMaskAccess) // mpMaskAccess != NULL -> alphachannel is to create
551 if (mbTrueAlpha)
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);
562 else
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)
574 *pDest++ = 0;
575 else
576 *pDest++ = 0xff;
580 else
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
593 if (mnFilterType)
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++;
607 sal_uLong na, nc;
608 if (p2 >= mpCurrentScan + 1)
610 na = *p2;
611 nc = *p4;
613 else
615 na = nc = 0;
618 long np = na + nb - nc;
619 long npa = np - na;
620 long npb = np - nb;
621 long npc = np - nc;
623 if (npa < 0)
624 npa =-npa;
625 if (npb < 0)
626 npb =-npb;
627 if (npc < 0)
628 npc =-npc;
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);
634 else
635 *pDest++ = *p1++ - static_cast<sal_uInt8>(nc);
637 p4++;
638 p2++;
640 for (long i = 0; i < static_cast<long>(mnDeflateInSize - 1); i++)
642 mpPreviousScan[i] = mpCurrentScan[i + 1];
645 else
647 mnDeflateInSize = pDest - mpDeflateInBuf;
649 return mnDeflateInSize;
652 void PNGWriterImpl::ImplClearFirstScanline()
654 if (mnFilterType)
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)
680 if (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();
709 } // namespace vcl
711 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */