/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #define PNG_DEF_COMPRESSION 6 #define PNGCHUNK_IHDR 0x49484452 #define PNGCHUNK_PLTE 0x504c5445 #define PNGCHUNK_IDAT 0x49444154 #define PNGCHUNK_IEND 0x49454e44 #define PNGCHUNK_pHYs 0x70485973 #define PNGCHUNK_tRNS 0x74524e53 namespace vcl { class PNGWriterImpl { public: PNGWriterImpl( const BitmapEx& BmpEx, const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::PropertyValue >* pFilterData = NULL ); bool Write( SvStream& rOStm ); std::vector< vcl::PNGWriter::ChunkData >& GetChunks() { return maChunkSeq;} private: std::vector< vcl::PNGWriter::ChunkData > maChunkSeq; sal_Int32 mnCompLevel; sal_Int32 mnInterlaced; sal_uInt32 mnMaxChunkSize; bool mbStatus; BitmapReadAccess* mpAccess; BitmapReadAccess* mpMaskAccess; ZCodec mpZCodec; sal_uInt8* mpDeflateInBuf; // as big as the size of a scanline + alphachannel + 1 sal_uInt8* mpPreviousScan; // as big as mpDeflateInBuf sal_uInt8* mpCurrentScan; sal_uLong mnDeflateInSize; sal_uLong mnWidth, mnHeight; sal_uInt8 mnBitsPerPixel; sal_uInt8 mnFilterType; // 0 oder 4; sal_uLong mnBBP; // bytes per pixel ( needed for filtering ) bool mbTrueAlpha; sal_uLong mnCRC; void ImplWritepHYs( const BitmapEx& rBitmapEx ); void ImplWriteIDAT(); sal_uLong ImplGetFilter( sal_uLong nY, sal_uLong nXStart=0, sal_uLong nXAdd=1 ); void ImplClearFirstScanline(); void ImplWriteTransparent(); bool ImplWriteHeader(); void ImplWritePalette(); void ImplOpenChunk( sal_uLong nChunkType ); void ImplWriteChunk( sal_uInt8 nNumb ); void ImplWriteChunk( sal_uInt32 nNumb ); void ImplWriteChunk( unsigned char* pSource, sal_uInt32 nDatSize ); void ImplCloseChunk( void ) const; }; PNGWriterImpl::PNGWriterImpl( const BitmapEx& rBmpEx, const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::PropertyValue >* pFilterData ) : mnCompLevel(PNG_DEF_COMPRESSION) , mnInterlaced(0) , mnMaxChunkSize(0) , mbStatus(true) , mpAccess(NULL) , mpMaskAccess(NULL) , mpDeflateInBuf(NULL) , mpPreviousScan(NULL) , mpCurrentScan(NULL) , mnDeflateInSize(0) , mnWidth(0) , mnHeight(0) , mnBitsPerPixel(0) , mnFilterType(0) , mnBBP(0) , mbTrueAlpha(false) , mnCRC(0UL) { if ( !rBmpEx.IsEmpty() ) { Bitmap aBmp( rBmpEx.GetBitmap() ); mnInterlaced = 0; // ( aBmp.GetSizePixel().Width() > 128 ) || ( aBmp.GetSizePixel().Height() > 128 ) ? 1 : 0; #i67236# // #i67234# defaulting max chunk size to 256kb when using interlace mode mnMaxChunkSize = mnInterlaced == 0 ? std::numeric_limits< sal_uInt32 >::max() : 0x40000; if ( pFilterData ) { sal_Int32 i = 0; for ( i = 0; i < pFilterData->getLength(); i++ ) { if ( (*pFilterData)[ i ].Name == "Compression" ) (*pFilterData)[ i ].Value >>= mnCompLevel; else if ( (*pFilterData)[ i ].Name == "Interlaced" ) (*pFilterData)[ i ].Value >>= mnInterlaced; else if ( (*pFilterData)[ i ].Name == "MaxChunkSize" ) { sal_Int32 nVal = 0; if ( (*pFilterData)[ i ].Value >>= nVal ) mnMaxChunkSize = (sal_uInt32)nVal; } } } mnBitsPerPixel = (sal_uInt8)aBmp.GetBitCount(); if( rBmpEx.IsTransparent() ) { if ( mnBitsPerPixel <= 8 && rBmpEx.IsAlpha() ) { aBmp.Convert( BMP_CONVERSION_24BIT ); mnBitsPerPixel = 24; } if ( mnBitsPerPixel <= 8 ) // transparent palette { aBmp.Convert( BMP_CONVERSION_8BIT_TRANS ); aBmp.Replace( rBmpEx.GetMask(), BMP_COL_TRANS ); mnBitsPerPixel = 8; mpAccess = aBmp.AcquireReadAccess(); if ( mpAccess ) { if ( ImplWriteHeader() ) { ImplWritepHYs( rBmpEx ); ImplWritePalette(); ImplWriteTransparent(); ImplWriteIDAT(); } aBmp.ReleaseAccess( mpAccess ), mpAccess = 0; } else mbStatus = false; } else { mpAccess = aBmp.AcquireReadAccess(); // true RGB with alphachannel if( mpAccess ) { if ( ( mbTrueAlpha = rBmpEx.IsAlpha() ) ) { AlphaMask aMask( rBmpEx.GetAlpha() ); mpMaskAccess = aMask.AcquireReadAccess(); if ( mpMaskAccess ) { if ( ImplWriteHeader() ) { ImplWritepHYs( rBmpEx ); ImplWriteIDAT(); } aMask.ReleaseAccess( mpMaskAccess ), mpMaskAccess = 0; } else mbStatus = false; } else { Bitmap aMask( rBmpEx.GetMask() ); mpMaskAccess = aMask.AcquireReadAccess(); if( mpMaskAccess ) { if ( ImplWriteHeader() ) { ImplWritepHYs( rBmpEx ); ImplWriteIDAT(); } aMask.ReleaseAccess( mpMaskAccess ), mpMaskAccess = 0; } else mbStatus = false; } aBmp.ReleaseAccess( mpAccess ), mpAccess = 0; } else mbStatus = false; } } else { mpAccess = aBmp.AcquireReadAccess(); // palette + RGB without alphachannel if( mpAccess ) { if ( ImplWriteHeader() ) { ImplWritepHYs( rBmpEx ); if( mpAccess->HasPalette() ) ImplWritePalette(); ImplWriteIDAT(); } aBmp.ReleaseAccess( mpAccess ), mpAccess = 0; } else mbStatus = false; } if ( mbStatus ) { ImplOpenChunk( PNGCHUNK_IEND ); // create an IEND chunk ImplCloseChunk(); } } } bool PNGWriterImpl::Write( SvStream& rOStm ) { /* png signature is always an array of 8 bytes */ SvStreamEndian nOldMode = rOStm.GetEndian(); rOStm.SetEndian( SvStreamEndian::BIG ); rOStm.WriteUInt32( 0x89504e47 ); rOStm.WriteUInt32( 0x0d0a1a0a ); std::vector< vcl::PNGWriter::ChunkData >::iterator aBeg( maChunkSeq.begin() ); std::vector< vcl::PNGWriter::ChunkData >::iterator aEnd( maChunkSeq.end() ); while( aBeg != aEnd ) { sal_uInt32 nType = aBeg->nType; #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN) nType = OSL_SWAPDWORD( nType ); #endif sal_uInt32 nCRC = rtl_crc32( 0, &nType, 4 ); sal_uInt32 nDataSize = aBeg->aData.size(); if ( nDataSize ) nCRC = rtl_crc32( nCRC, &aBeg->aData[ 0 ], nDataSize ); rOStm.WriteUInt32( nDataSize ) .WriteUInt32( aBeg->nType ); if ( nDataSize ) rOStm.Write( &aBeg->aData[ 0 ], nDataSize ); rOStm.WriteUInt32( nCRC ); ++aBeg; } rOStm.SetEndian( nOldMode ); return mbStatus; } bool PNGWriterImpl::ImplWriteHeader() { ImplOpenChunk(PNGCHUNK_IHDR); ImplWriteChunk( sal_uInt32( mnWidth = mpAccess->Width() ) ); ImplWriteChunk( sal_uInt32( mnHeight = mpAccess->Height() ) ); if ( mnWidth && mnHeight && mnBitsPerPixel && mbStatus ) { sal_uInt8 nBitDepth = mnBitsPerPixel; if ( mnBitsPerPixel <= 8 ) mnFilterType = 0; else mnFilterType = 4; sal_uInt8 nColorType = 2; // colortype: // bit 0 -> palette is used if ( mpAccess->HasPalette() ) // bit 1 -> color is used nColorType |= 1; // bit 2 -> alpha channel is used else nBitDepth /= 3; if ( mpMaskAccess ) nColorType |= 4; ImplWriteChunk( nBitDepth ); ImplWriteChunk( nColorType ); // colortype ImplWriteChunk((sal_uInt8) 0 ); // compression type ImplWriteChunk((sal_uInt8) 0 ); // filter type - is not supported in this version ImplWriteChunk((sal_uInt8) mnInterlaced ); // interlace type ImplCloseChunk(); } else mbStatus = false; return mbStatus; } void PNGWriterImpl::ImplWritePalette() { const sal_uLong nCount = mpAccess->GetPaletteEntryCount(); boost::scoped_array pTempBuf(new sal_uInt8[ nCount*3 ]); sal_uInt8* pTmp = pTempBuf.get(); ImplOpenChunk( PNGCHUNK_PLTE ); for ( sal_uInt16 i = 0; i < nCount; i++ ) { const BitmapColor& rColor = mpAccess->GetPaletteColor( i ); *pTmp++ = rColor.GetRed(); *pTmp++ = rColor.GetGreen(); *pTmp++ = rColor.GetBlue(); } ImplWriteChunk( pTempBuf.get(), nCount*3 ); ImplCloseChunk(); } void PNGWriterImpl::ImplWriteTransparent () { const sal_uLong nTransIndex = mpAccess->GetBestPaletteIndex( BMP_COL_TRANS ); ImplOpenChunk( PNGCHUNK_tRNS ); for ( sal_uLong n = 0UL; n <= nTransIndex; n++ ) ImplWriteChunk( ( nTransIndex == n ) ? (sal_uInt8) 0x0 : (sal_uInt8) 0xff ); ImplCloseChunk(); } void PNGWriterImpl::ImplWritepHYs( const BitmapEx& rBmpEx ) { if ( rBmpEx.GetPrefMapMode() == MAP_100TH_MM ) { Size aPrefSize( rBmpEx.GetPrefSize() ); if ( aPrefSize.Width() && aPrefSize.Height() && mnWidth && mnHeight ) { ImplOpenChunk( PNGCHUNK_pHYs ); sal_uInt8 nMapUnit = 1; sal_uInt32 nPrefSizeX = (sal_uInt32)( (double)100000.0 / ( (double)aPrefSize.Width() / mnWidth ) + 0.5 ); sal_uInt32 nPrefSizeY = (sal_uInt32)( (double)100000.0 / ( (double)aPrefSize.Height() / mnHeight ) + 0.5 ); ImplWriteChunk( nPrefSizeX ); ImplWriteChunk( nPrefSizeY ); ImplWriteChunk( nMapUnit ); ImplCloseChunk(); } } } void PNGWriterImpl::ImplWriteIDAT () { mnDeflateInSize = mnBitsPerPixel; if( mpMaskAccess ) mnDeflateInSize += 8; mnBBP = ( mnDeflateInSize + 7 ) >> 3; mnDeflateInSize = mnBBP * mnWidth + 1; mpDeflateInBuf = new sal_uInt8[ mnDeflateInSize ]; if ( mnFilterType ) // using filter type 4 we need memory for the scanline 3 times { mpPreviousScan = new sal_uInt8[ mnDeflateInSize ]; mpCurrentScan = new sal_uInt8[ mnDeflateInSize ]; ImplClearFirstScanline(); } mpZCodec.BeginCompression( mnCompLevel, true ); mpZCodec.SetCRC( mnCRC ); SvMemoryStream aOStm; if ( mnInterlaced == 0 ) { for ( sal_uLong nY = 0; nY < mnHeight; nY++ ) mpZCodec.Write( aOStm, mpDeflateInBuf, ImplGetFilter( nY ) ); } else { // interlace mode sal_uLong nY; for ( nY = 0; nY < mnHeight; nY+=8 ) // pass 1 mpZCodec.Write( aOStm, mpDeflateInBuf, ImplGetFilter ( nY, 0, 8 ) ); ImplClearFirstScanline(); for ( nY = 0; nY < mnHeight; nY+=8 ) // pass 2 mpZCodec.Write( aOStm, mpDeflateInBuf, ImplGetFilter ( nY, 4, 8 ) ); ImplClearFirstScanline(); if ( mnHeight >= 5 ) // pass 3 { for ( nY = 4; nY < mnHeight; nY+=8 ) mpZCodec.Write( aOStm, mpDeflateInBuf, ImplGetFilter ( nY, 0, 4 ) ); ImplClearFirstScanline(); } for ( nY = 0; nY < mnHeight; nY+=4 ) // pass 4 mpZCodec.Write( aOStm, mpDeflateInBuf, ImplGetFilter ( nY, 2, 4 ) ); ImplClearFirstScanline(); if ( mnHeight >= 3 ) // pass 5 { for ( nY = 2; nY < mnHeight; nY+=4 ) mpZCodec.Write( aOStm, mpDeflateInBuf, ImplGetFilter ( nY, 0, 2 ) ); ImplClearFirstScanline(); } for ( nY = 0; nY < mnHeight; nY+=2 ) // pass 6 mpZCodec.Write( aOStm, mpDeflateInBuf, ImplGetFilter ( nY, 1, 2 ) ); ImplClearFirstScanline(); if ( mnHeight >= 2 ) // pass 7 { for ( nY = 1; nY < mnHeight; nY+=2 ) mpZCodec.Write( aOStm, mpDeflateInBuf, ImplGetFilter ( nY, 0, 1 ) ); } } mpZCodec.EndCompression(); mnCRC = mpZCodec.GetCRC(); if ( mnFilterType ) // using filter type 4 we need memory for the scanline 3 times { delete[] mpCurrentScan; delete[] mpPreviousScan; } delete[] mpDeflateInBuf; sal_uInt32 nIDATSize = aOStm.Tell(); sal_uInt32 nBytes, nBytesToWrite = nIDATSize; while( nBytesToWrite ) { nBytes = nBytesToWrite <= mnMaxChunkSize ? nBytesToWrite : mnMaxChunkSize; ImplOpenChunk( PNGCHUNK_IDAT ); ImplWriteChunk( (unsigned char*)aOStm.GetData() + ( nIDATSize - nBytesToWrite ), nBytes ); ImplCloseChunk(); nBytesToWrite -= nBytes; } } // ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd // appends to the currently used pass // the complete size of scanline will be returned - in interlace mode zero is possible! sal_uLong PNGWriterImpl::ImplGetFilter ( sal_uLong nY, sal_uLong nXStart, sal_uLong nXAdd ) { sal_uInt8* pDest; if ( mnFilterType ) pDest = mpCurrentScan; else pDest = mpDeflateInBuf; if ( nXStart < mnWidth ) { *pDest++ = mnFilterType; // in this version the filter type is either 0 or 4 if ( mpAccess->HasPalette() ) // alphachannel is not allowed by pictures including palette entries { switch ( mnBitsPerPixel ) { case( 1 ): { sal_uLong nX, nXIndex; for ( nX = nXStart, nXIndex = 0; nX < mnWidth; nX+=nXAdd, nXIndex++ ) { sal_uLong nShift = ( nXIndex & 7 ) ^ 7; if ( nShift == 7) *pDest = mpAccess->GetPixelIndex( nY, nX ) << nShift; else if ( nShift == 0 ) *pDest++ |= mpAccess->GetPixelIndex( nY, nX ) << nShift; else *pDest |= mpAccess->GetPixelIndex( nY, nX ) << nShift; } if ( ( nXIndex & 7 ) != 0 ) pDest++; // byte is not completely used, so the } // bufferpointer is to correct break; case( 4 ): { sal_uLong nX, nXIndex; for ( nX = nXStart, nXIndex = 0; nX < mnWidth; nX+= nXAdd, nXIndex++ ) { if( nXIndex & 1 ) *pDest++ |= mpAccess->GetPixelIndex( nY, nX ); else *pDest = mpAccess->GetPixelIndex( nY, nX ) << 4; } if ( nXIndex & 1 ) pDest++; } break; case( 8 ): { for ( sal_uLong nX = nXStart; nX < mnWidth; nX+=nXAdd ) *pDest++ = mpAccess->GetPixelIndex( nY, nX ); } break; default : mbStatus = false; break; } } else { if ( mpMaskAccess ) // mpMaskAccess != NULL -> alphachannel is to create { if ( mbTrueAlpha ) { for ( sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd ) { const BitmapColor& rColor = mpAccess->GetPixel( nY, nX ); *pDest++ = rColor.GetRed(); *pDest++ = rColor.GetGreen(); *pDest++ = rColor.GetBlue(); *pDest++ = 255 - mpMaskAccess->GetPixelIndex( nY, nX ); } } else { const BitmapColor aTrans( mpMaskAccess->GetBestMatchingColor( Color( COL_WHITE ) ) ); for ( sal_uLong nX = nXStart; nX < mnWidth; nX+=nXAdd ) { const BitmapColor& rColor = mpAccess->GetPixel( nY, nX ); *pDest++ = rColor.GetRed(); *pDest++ = rColor.GetGreen(); *pDest++ = rColor.GetBlue(); if( mpMaskAccess->GetPixel( nY, nX ) == aTrans ) *pDest++ = 0; else *pDest++ = 0xff; } } } else { for ( sal_uLong nX = nXStart; nX < mnWidth; nX+=nXAdd ) { const BitmapColor& rColor = mpAccess->GetPixel( nY, nX ); *pDest++ = rColor.GetRed(); *pDest++ = rColor.GetGreen(); *pDest++ = rColor.GetBlue(); } } } } // filter type4 ( PAETH ) will be used only for 24bit graphics if ( mnFilterType ) { mnDeflateInSize = pDest - mpCurrentScan; pDest = mpDeflateInBuf; *pDest++ = 4; // filter type sal_uLong na, nb, nc; long np, npa, npb, npc; sal_uInt8* p1 = mpCurrentScan + 1; // Current Pixel sal_uInt8* p2 = p1 - mnBBP; // left pixel sal_uInt8* p3 = mpPreviousScan; // upper pixel sal_uInt8* p4 = p3 - mnBBP; // upperleft Pixel; while ( pDest < mpDeflateInBuf + mnDeflateInSize ) { nb = *p3++; if ( p2 >= mpCurrentScan + 1 ) { na = *p2; nc = *p4; } else na = nc = 0; np = na + nb; np -= nc; npa = np - na; npb = np - nb; npc = np - nc; if ( npa < 0 ) npa =-npa; if ( npb < 0 ) npb =-npb; if ( npc < 0 ) npc =-npc; if ( ( npa <= npb ) && ( npa <= npc ) ) *pDest++ = *p1++ - (sal_uInt8)na; else if ( npb <= npc ) *pDest++ = *p1++ - (sal_uInt8)nb; else *pDest++ = *p1++ - (sal_uInt8)nc; p4++; p2++; } for ( long i = 0; i < (long)( mnDeflateInSize - 1 ); i++ ) mpPreviousScan[ i ] = mpCurrentScan[ i + 1 ]; } else mnDeflateInSize = pDest - mpDeflateInBuf; return ( mnDeflateInSize ); } void PNGWriterImpl::ImplClearFirstScanline() { if ( mnFilterType ) memset( mpPreviousScan, 0, mnDeflateInSize ); } void PNGWriterImpl::ImplOpenChunk ( sal_uLong nChunkType ) { maChunkSeq.resize( maChunkSeq.size() + 1 ); maChunkSeq.back().nType = nChunkType; } void PNGWriterImpl::ImplWriteChunk ( sal_uInt8 nSource ) { maChunkSeq.back().aData.push_back( nSource ); } void PNGWriterImpl::ImplWriteChunk ( sal_uInt32 nSource ) { vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back(); rChunkData.aData.push_back( (sal_uInt8)( nSource >> 24 ) ); rChunkData.aData.push_back( (sal_uInt8)( nSource >> 16 ) ); rChunkData.aData.push_back( (sal_uInt8)( nSource >> 8 ) ); rChunkData.aData.push_back( (sal_uInt8)( nSource ) ); } void PNGWriterImpl::ImplWriteChunk ( unsigned char* pSource, sal_uInt32 nDatSize ) { if ( nDatSize ) { vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back(); sal_uInt32 nSize = rChunkData.aData.size(); rChunkData.aData.resize( nSize + nDatSize ); memcpy( &rChunkData.aData[ nSize ], pSource, nDatSize ); } } // nothing to do void PNGWriterImpl::ImplCloseChunk ( void ) const { } PNGWriter::PNGWriter( const BitmapEx& rBmpEx, const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::PropertyValue >* pFilterData ) : mpImpl( new ::vcl::PNGWriterImpl( rBmpEx, pFilterData ) ) { } PNGWriter::~PNGWriter() { delete mpImpl; } bool PNGWriter::Write( SvStream& rIStm ) { return mpImpl->Write( rIStm ); } std::vector< vcl::PNGWriter::ChunkData >& PNGWriter::GetChunks() { return mpImpl->GetChunks(); } } // namespace vcl /* vim:set shiftwidth=4 softtabstop=4 expandtab: */