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/bmpacc.hxx>
29 #include <vcl/svapp.hxx>
30 #include <vcl/alpha.hxx>
31 #include <osl/endian.h>
32 #include <boost/scoped_array.hpp>
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
= NULL
);
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 BitmapReadAccess
* 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 oder 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
)
107 , mpDeflateInBuf(NULL
)
108 , mpPreviousScan(NULL
)
109 , mpCurrentScan(NULL
)
119 if (!rBmpEx
.IsEmpty())
121 Bitmap
aBmp(rBmpEx
.GetBitmap());
123 mnInterlaced
= 0; // ( aBmp.GetSizePixel().Width() > 128 ) || ( aBmp.GetSizePixel().Height() > 128 ) ? 1 : 0; #i67236#
125 // #i67234# defaulting max chunk size to 256kb when using interlace mode
126 mnMaxChunkSize
= mnInterlaced
== 0 ? std::numeric_limits
<sal_uInt32
>::max() : 0x40000;
131 for (i
= 0; i
< pFilterData
->getLength(); i
++)
133 if ((*pFilterData
)[i
].Name
== "Compression")
134 (*pFilterData
)[i
].Value
>>= mnCompLevel
;
135 else if ((*pFilterData
)[i
].Name
== "Interlaced")
136 (*pFilterData
)[i
].Value
>>= mnInterlaced
;
137 else if ((*pFilterData
)[i
].Name
== "MaxChunkSize")
140 if ((*pFilterData
)[i
].Value
>>= nVal
)
141 mnMaxChunkSize
= static_cast<sal_uInt32
>(nVal
);
145 mnBitsPerPixel
= static_cast<sal_uInt8
>(aBmp
.GetBitCount());
147 if (rBmpEx
.IsTransparent())
149 if (mnBitsPerPixel
<= 8 && rBmpEx
.IsAlpha())
151 aBmp
.Convert( BMP_CONVERSION_24BIT
);
155 if (mnBitsPerPixel
<= 8) // transparent palette
157 aBmp
.Convert(BMP_CONVERSION_8BIT_TRANS
);
158 aBmp
.Replace(rBmpEx
.GetMask(), BMP_COL_TRANS
);
160 mpAccess
= aBmp
.AcquireReadAccess();
163 if (ImplWriteHeader())
165 ImplWritepHYs(rBmpEx
);
167 ImplWriteTransparent();
170 Bitmap::ReleaseAccess(mpAccess
);
180 mpAccess
= aBmp
.AcquireReadAccess(); // true RGB with alphachannel
183 if ((mbTrueAlpha
= rBmpEx
.IsAlpha()))
185 AlphaMask
aMask(rBmpEx
.GetAlpha());
186 mpMaskAccess
= aMask
.AcquireReadAccess();
189 if (ImplWriteHeader())
191 ImplWritepHYs(rBmpEx
);
194 aMask
.ReleaseAccess(mpMaskAccess
);
204 Bitmap
aMask(rBmpEx
.GetMask());
205 mpMaskAccess
= aMask
.AcquireReadAccess();
208 if (ImplWriteHeader())
210 ImplWritepHYs(rBmpEx
);
213 Bitmap::ReleaseAccess(mpMaskAccess
);
221 Bitmap::ReleaseAccess(mpAccess
);
232 mpAccess
= aBmp
.AcquireReadAccess(); // palette + RGB without alphachannel
235 if (ImplWriteHeader())
237 ImplWritepHYs(rBmpEx
);
238 if (mpAccess
->HasPalette())
243 Bitmap::ReleaseAccess(mpAccess
);
254 ImplOpenChunk(PNGCHUNK_IEND
); // create an IEND chunk
259 bool PNGWriterImpl::Write(SvStream
& rOStm
)
261 /* png signature is always an array of 8 bytes */
262 SvStreamEndian nOldMode
= rOStm
.GetEndian();
263 rOStm
.SetEndian(SvStreamEndian::BIG
);
264 rOStm
.WriteUInt32(0x89504e47);
265 rOStm
.WriteUInt32(0x0d0a1a0a);
267 std::vector
< vcl::PNGWriter::ChunkData
>::iterator
aBeg(maChunkSeq
.begin());
268 std::vector
< vcl::PNGWriter::ChunkData
>::iterator
aEnd(maChunkSeq
.end());
271 sal_uInt32 nType
= aBeg
->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
= aBeg
->aData
.size();
278 nCRC
= rtl_crc32(nCRC
, &aBeg
->aData
[0], nDataSize
);
279 rOStm
.WriteUInt32(nDataSize
);
280 rOStm
.WriteUInt32(aBeg
->nType
);
282 rOStm
.Write(&aBeg
->aData
[0], nDataSize
);
283 rOStm
.WriteUInt32(nCRC
);
286 rOStm
.SetEndian(nOldMode
);
291 bool PNGWriterImpl::ImplWriteHeader()
293 ImplOpenChunk(PNGCHUNK_IHDR
);
294 ImplWriteChunk(sal_uInt32(mnWidth
= mpAccess
->Width()));
295 ImplWriteChunk(sal_uInt32(mnHeight
= mpAccess
->Height()));
297 if (mnWidth
&& mnHeight
&& mnBitsPerPixel
&& mbStatus
)
299 sal_uInt8 nBitDepth
= mnBitsPerPixel
;
300 if (mnBitsPerPixel
<= 8)
305 sal_uInt8 nColorType
= 2; // colortype:
307 // bit 0 -> palette is used
308 if (mpAccess
->HasPalette()) // bit 1 -> color is used
309 nColorType
|= 1; // bit 2 -> alpha channel is used
316 ImplWriteChunk(nBitDepth
);
317 ImplWriteChunk(nColorType
); // colortype
318 ImplWriteChunk(static_cast<sal_uInt8
>(0)); // compression type
319 ImplWriteChunk(static_cast<sal_uInt8
>(0)); // filter type - is not supported in this version
320 ImplWriteChunk(static_cast<sal_uInt8
>(mnInterlaced
)); // interlace type
329 void PNGWriterImpl::ImplWritePalette()
331 const sal_uLong nCount
= mpAccess
->GetPaletteEntryCount();
332 boost::scoped_array
<sal_uInt8
> pTempBuf(new sal_uInt8
[nCount
* 3]);
333 sal_uInt8
* pTmp
= pTempBuf
.get();
335 ImplOpenChunk(PNGCHUNK_PLTE
);
337 for ( sal_uInt16 i
= 0; i
< nCount
; i
++ )
339 const BitmapColor
& rColor
= mpAccess
->GetPaletteColor(i
);
340 *pTmp
++ = rColor
.GetRed();
341 *pTmp
++ = rColor
.GetGreen();
342 *pTmp
++ = rColor
.GetBlue();
344 ImplWriteChunk(pTempBuf
.get(), nCount
* 3);
347 void PNGWriterImpl::ImplWriteTransparent()
349 const sal_uLong nTransIndex
= mpAccess
->GetBestPaletteIndex(BMP_COL_TRANS
);
351 ImplOpenChunk(PNGCHUNK_tRNS
);
353 for (sal_uLong n
= 0UL; n
<= nTransIndex
; n
++)
355 ImplWriteChunk((nTransIndex
== n
) ? static_cast<sal_uInt8
>(0x0) : static_cast<sal_uInt8
>(0xff));
359 void PNGWriterImpl::ImplWritepHYs(const BitmapEx
& rBmpEx
)
361 if (rBmpEx
.GetPrefMapMode() == MAP_100TH_MM
)
363 Size
aPrefSize(rBmpEx
.GetPrefSize());
365 if (aPrefSize
.Width() && aPrefSize
.Height() && mnWidth
&& mnHeight
)
367 ImplOpenChunk(PNGCHUNK_pHYs
);
368 sal_uInt8 nMapUnit
= 1;
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(nMapUnit
);
378 void PNGWriterImpl::ImplWriteIDAT()
380 mnDeflateInSize
= mnBitsPerPixel
;
383 mnDeflateInSize
+= 8;
385 mnBBP
= (mnDeflateInSize
+ 7) >> 3;
387 mnDeflateInSize
= mnBBP
* mnWidth
+ 1;
389 mpDeflateInBuf
= new sal_uInt8
[mnDeflateInSize
];
391 if (mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
393 mpPreviousScan
= new sal_uInt8
[mnDeflateInSize
];
394 mpCurrentScan
= new sal_uInt8
[mnDeflateInSize
];
395 ImplClearFirstScanline();
397 mpZCodec
.BeginCompression(mnCompLevel
, true);
398 mpZCodec
.SetCRC(mnCRC
);
399 SvMemoryStream aOStm
;
400 if (mnInterlaced
== 0)
402 for (sal_uLong nY
= 0; nY
< mnHeight
; nY
++)
404 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
));
411 for (nY
= 0; nY
< mnHeight
; nY
+= 8) // pass 1
413 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 0, 8));
415 ImplClearFirstScanline();
417 for (nY
= 0; nY
< mnHeight
; nY
+= 8) // pass 2
419 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 4, 8));
421 ImplClearFirstScanline();
423 if (mnHeight
>= 5) // pass 3
425 for (nY
= 4; nY
< mnHeight
; nY
+= 8)
427 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 0, 4));
429 ImplClearFirstScanline();
432 for (nY
= 0; nY
< mnHeight
; nY
+= 4) // pass 4
434 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 2, 4));
436 ImplClearFirstScanline();
438 if (mnHeight
>= 3) // pass 5
440 for (nY
= 2; nY
< mnHeight
; nY
+= 4)
442 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 0, 2));
444 ImplClearFirstScanline();
447 for (nY
= 0; nY
< mnHeight
; nY
+= 2) // pass 6
449 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter(nY
, 1, 2));
451 ImplClearFirstScanline();
453 if (mnHeight
>= 2) // pass 7
455 for (nY
= 1; nY
< mnHeight
; nY
+= 2)
457 mpZCodec
.Write(aOStm
, mpDeflateInBuf
, ImplGetFilter (nY
, 0, 1));
461 mpZCodec
.EndCompression();
462 mnCRC
= mpZCodec
.GetCRC();
464 if (mnFilterType
) // using filter type 4 we need memory for the scanline 3 times
466 delete[] mpCurrentScan
;
467 delete[] mpPreviousScan
;
469 delete[] mpDeflateInBuf
;
471 sal_uInt32 nIDATSize
= aOStm
.Tell();
472 sal_uInt32 nBytes
, nBytesToWrite
= nIDATSize
;
475 nBytes
= nBytesToWrite
<= mnMaxChunkSize
? nBytesToWrite
: mnMaxChunkSize
;
476 ImplOpenChunk(PNGCHUNK_IDAT
);
477 ImplWriteChunk(const_cast<unsigned char *>(static_cast<unsigned char const *>(aOStm
.GetData())) + (nIDATSize
- nBytesToWrite
), nBytes
);
478 nBytesToWrite
-= nBytes
;
482 // ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd
483 // appends to the currently used pass
484 // the complete size of scanline will be returned - in interlace mode zero is possible!
486 sal_uLong
PNGWriterImpl::ImplGetFilter (sal_uLong nY
, sal_uLong nXStart
, sal_uLong nXAdd
)
491 pDest
= mpCurrentScan
;
493 pDest
= mpDeflateInBuf
;
495 if (nXStart
< mnWidth
)
497 *pDest
++ = mnFilterType
; // in this version the filter type is either 0 or 4
499 if (mpAccess
->HasPalette()) // alphachannel is not allowed by pictures including palette entries
501 switch (mnBitsPerPixel
)
505 sal_uLong nX
, nXIndex
;
506 for (nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+= nXAdd
, nXIndex
++)
508 sal_uLong nShift
= (nXIndex
& 7) ^ 7;
510 *pDest
= mpAccess
->GetPixelIndex(nY
, nX
) << nShift
;
511 else if (nShift
== 0)
512 *pDest
++ |= mpAccess
->GetPixelIndex(nY
, nX
) << nShift
;
514 *pDest
|= mpAccess
->GetPixelIndex(nY
, nX
) << nShift
;
516 if ( (nXIndex
& 7) != 0 )
517 pDest
++; // byte is not completely used, so the bufferpointer is to correct
523 sal_uLong nX
, nXIndex
;
524 for (nX
= nXStart
, nXIndex
= 0; nX
< mnWidth
; nX
+= nXAdd
, nXIndex
++)
527 *pDest
++ |= mpAccess
->GetPixelIndex(nY
, nX
);
529 *pDest
= mpAccess
->GetPixelIndex(nY
, nX
) << 4;
538 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
540 *pDest
++ = mpAccess
->GetPixelIndex( nY
, nX
);
552 if (mpMaskAccess
) // mpMaskAccess != NULL -> alphachannel is to create
556 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
558 const BitmapColor
& rColor
= mpAccess
->GetPixel(nY
, nX
);
559 *pDest
++ = rColor
.GetRed();
560 *pDest
++ = rColor
.GetGreen();
561 *pDest
++ = rColor
.GetBlue();
562 *pDest
++ = 255 - mpMaskAccess
->GetPixelIndex(nY
, nX
);
567 const BitmapColor
aTrans(mpMaskAccess
->GetBestMatchingColor(Color(COL_WHITE
)));
569 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
571 const BitmapColor
& rColor
= mpAccess
->GetPixel(nY
, nX
);
572 *pDest
++ = rColor
.GetRed();
573 *pDest
++ = rColor
.GetGreen();
574 *pDest
++ = rColor
.GetBlue();
576 if(mpMaskAccess
->GetPixel(nY
, nX
) == aTrans
)
585 for (sal_uLong nX
= nXStart
; nX
< mnWidth
; nX
+= nXAdd
)
587 const BitmapColor
& rColor
= mpAccess
->GetPixel(nY
, nX
);
588 *pDest
++ = rColor
.GetRed();
589 *pDest
++ = rColor
.GetGreen();
590 *pDest
++ = rColor
.GetBlue();
595 // filter type4 ( PAETH ) will be used only for 24bit graphics
598 mnDeflateInSize
= pDest
- mpCurrentScan
;
599 pDest
= mpDeflateInBuf
;
600 *pDest
++ = 4; // filter type
602 sal_uLong na
, nb
, nc
;
603 long np
, npa
, npb
, npc
;
605 sal_uInt8
* p1
= mpCurrentScan
+ 1; // Current Pixel
606 sal_uInt8
* p2
= p1
- mnBBP
; // left pixel
607 sal_uInt8
* p3
= mpPreviousScan
; // upper pixel
608 sal_uInt8
* p4
= p3
- mnBBP
; // upperleft Pixel;
610 while (pDest
< mpDeflateInBuf
+ mnDeflateInSize
)
613 if (p2
>= mpCurrentScan
+ 1)
636 if (npa
<= npb
&& npa
<= npc
)
637 *pDest
++ = *p1
++ - static_cast<sal_uInt8
>(na
);
638 else if ( npb
<= npc
)
639 *pDest
++ = *p1
++ - static_cast<sal_uInt8
>(nb
);
641 *pDest
++ = *p1
++ - static_cast<sal_uInt8
>(nc
);
646 for (long i
= 0; i
< static_cast<long>(mnDeflateInSize
- 1); i
++)
648 mpPreviousScan
[i
] = mpCurrentScan
[i
+ 1];
653 mnDeflateInSize
= pDest
- mpDeflateInBuf
;
655 return mnDeflateInSize
;
658 void PNGWriterImpl::ImplClearFirstScanline()
661 memset(mpPreviousScan
, 0, mnDeflateInSize
);
664 void PNGWriterImpl::ImplOpenChunk (sal_uLong nChunkType
)
666 maChunkSeq
.resize(maChunkSeq
.size() + 1);
667 maChunkSeq
.back().nType
= nChunkType
;
670 void PNGWriterImpl::ImplWriteChunk (sal_uInt8 nSource
)
672 maChunkSeq
.back().aData
.push_back(nSource
);
675 void PNGWriterImpl::ImplWriteChunk (sal_uInt32 nSource
)
677 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
678 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
>> 24));
679 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
>> 16));
680 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
>> 8));
681 rChunkData
.aData
.push_back(static_cast<sal_uInt8
>(nSource
));
684 void PNGWriterImpl::ImplWriteChunk (unsigned char* pSource
, sal_uInt32 nDatSize
)
688 vcl::PNGWriter::ChunkData
& rChunkData
= maChunkSeq
.back();
689 sal_uInt32 nSize
= rChunkData
.aData
.size();
690 rChunkData
.aData
.resize(nSize
+ nDatSize
);
691 memcpy(&rChunkData
.aData
[nSize
], pSource
, nDatSize
);
695 PNGWriter::PNGWriter(const BitmapEx
& rBmpEx
,
696 const css::uno::Sequence
<css::beans::PropertyValue
>* pFilterData
)
697 : mpImpl(new vcl::PNGWriterImpl(rBmpEx
, pFilterData
))
701 PNGWriter::~PNGWriter()
705 bool PNGWriter::Write(SvStream
& rStream
)
707 return mpImpl
->Write(rStream
);
710 std::vector
<vcl::PNGWriter::ChunkData
>& PNGWriter::GetChunks()
712 return mpImpl
->GetChunks();
717 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */