/************************************************************** * * 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_vcl.hxx" #include #include #include #include #include #include #include #include #include #include #include #include // ----------- // - Defines - // ----------- #define PNG_DEF_COMPRESSION 6 #define PNGCHUNK_IHDR 0x49484452 #define PNGCHUNK_PLTE 0x504c5445 #define PNGCHUNK_IDAT 0x49444154 #define PNGCHUNK_IEND 0x49454e44 #define PNGCHUNK_bKGD 0x624b4744 #define PNGCHUNK_cHRM 0x6348524d #define PNGCHUNK_gAMA 0x67414d41 #define PNGCHUNK_hIST 0x68495354 #define PNGCHUNK_pHYs 0x70485973 #define PNGCHUNK_sBIT 0x73425420 #define PNGCHUNK_tIME 0x74494d45 #define PNGCHUNK_tEXt 0x74455874 #define PNGCHUNK_tRNS 0x74524e53 #define PNGCHUNK_zTXt 0x7a545874 namespace vcl { // ----------------- // - PNGWriterImplImpl - // ----------------- class PNGWriterImpl { public: PNGWriterImpl( const BitmapEx& BmpEx, const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::PropertyValue >* pFilterData = NULL ); ~PNGWriterImpl(); sal_Bool Write( SvStream& rOStm ); std::vector< vcl::PNGWriter::ChunkData >& GetChunks(); private: std::vector< vcl::PNGWriter::ChunkData > maChunkSeq; sal_Int32 mnCompLevel; sal_Int32 mnInterlaced; sal_uInt32 mnMaxChunkSize; sal_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 ) sal_Bool mbTrueAlpha; sal_uLong mnCRC; long mnChunkDatSize; sal_uLong mnLastPercent; 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(); sal_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 ); }; // ------------------------------------------------------------------------ PNGWriterImpl::PNGWriterImpl( const BitmapEx& rBmpEx, const ::com::sun::star::uno::Sequence< ::com::sun::star::beans::PropertyValue >* pFilterData ) : mnCompLevel ( PNG_DEF_COMPRESSION ), mbStatus ( sal_True ), mpAccess ( NULL ), mpMaskAccess ( NULL ), mpZCodec ( new ZCodec( DEFAULT_IN_BUFSIZE, DEFAULT_OUT_BUFSIZE, MAX_MEM_USAGE ) ), mnCRC(0UL), mnLastPercent ( 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.equalsAscii( "Compression" ) ) (*pFilterData)[ i ].Value >>= mnCompLevel; else if ( (*pFilterData)[ i ].Name.equalsAscii( "Interlaced" ) ) (*pFilterData)[ i ].Value >>= mnInterlaced; else if ( (*pFilterData)[ i ].Name.equalsAscii( "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 ); } else mbStatus = sal_False; } else { mpAccess = aBmp.AcquireReadAccess(); // sal_True RGB with alphachannel if( mpAccess ) { if ( ( mbTrueAlpha = rBmpEx.IsAlpha() ) != sal_False ) { AlphaMask aMask( rBmpEx.GetAlpha() ); mpMaskAccess = aMask.AcquireReadAccess(); if ( mpMaskAccess ) { if ( ImplWriteHeader() ) { ImplWritepHYs( rBmpEx ); ImplWriteIDAT(); } aMask.ReleaseAccess( mpMaskAccess ); } else mbStatus = sal_False; } else { Bitmap aMask( rBmpEx.GetMask() ); mpMaskAccess = aMask.AcquireReadAccess(); if( mpMaskAccess ) { if ( ImplWriteHeader() ) { ImplWritepHYs( rBmpEx ); ImplWriteIDAT(); } aMask.ReleaseAccess( mpMaskAccess ); } else mbStatus = sal_False; } aBmp.ReleaseAccess( mpAccess ); } else mbStatus = sal_False; } } else { mpAccess = aBmp.AcquireReadAccess(); // palette + RGB without alphachannel if( mpAccess ) { if ( ImplWriteHeader() ) { ImplWritepHYs( rBmpEx ); if( mpAccess->HasPalette() ) ImplWritePalette(); ImplWriteIDAT(); } aBmp.ReleaseAccess( mpAccess ); } else mbStatus = sal_False; } if ( mbStatus ) { ImplOpenChunk( PNGCHUNK_IEND ); // create an IEND chunk ImplCloseChunk(); } } } // ------------------------------------------------------------------------ PNGWriterImpl::~PNGWriterImpl() { delete mpZCodec; } // ------------------------------------------------------------------------ sal_Bool PNGWriterImpl::Write( SvStream& rOStm ) { /* png signature is always an array of 8 bytes */ sal_uInt16 nOldMode = rOStm.GetNumberFormatInt(); rOStm.SetNumberFormatInt( NUMBERFORMAT_INT_BIGENDIAN ); rOStm << static_cast(0x89504e47); rOStm << static_cast(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 = SWAPLONG( 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 << nDataSize << aBeg->nType; if ( nDataSize ) rOStm.Write( &aBeg->aData[ 0 ], nDataSize ); rOStm << nCRC; aBeg++; } rOStm.SetNumberFormatInt( nOldMode ); return mbStatus; } // ------------------------------------------------------------------------ std::vector< vcl::PNGWriter::ChunkData >& PNGWriterImpl::GetChunks() { return maChunkSeq; } // ------------------------------------------------------------------------ sal_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 = sal_False; return mbStatus; } // ------------------------------------------------------------------------ void PNGWriterImpl::ImplWritePalette() { const sal_uLong nCount = mpAccess->GetPaletteEntryCount(); sal_uInt8* pTempBuf = new sal_uInt8[ nCount*3 ]; sal_uInt8* pTmp = pTempBuf; 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, nCount*3 ); ImplCloseChunk(); delete[] pTempBuf; } // ------------------------------------------------------------------------ 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() ) { 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( ZCODEC_PNG_DEFAULT + mnCompLevel ); 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 = sal_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 ) rtl_zeroMemory( mpPreviousScan, 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 ); rtl_copyMemory( &rChunkData.aData[ nSize ], pSource, nDatSize ); } } // ------------------------------------------------------------------------ // nothing to do void PNGWriterImpl::ImplCloseChunk ( void ) { } // ------------- // - PNGWriter - // ------------- 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; } // ------------------------------------------------------------------------ sal_Bool PNGWriter::Write( SvStream& rIStm ) { return mpImpl->Write( rIStm ); } // ------------------------------------------------------------------------ std::vector< vcl::PNGWriter::ChunkData >& PNGWriter::GetChunks() { return mpImpl->GetChunks(); } } // namespace vcl