/************************************************************************* * * OpenOffice.org - a multi-platform office productivity suite * * The Contents of this file are made available subject to * the terms of GNU General Public License Version 2. * * * GNU General Public License, version 2 * ============================================= * Copyright 2005 by Sun Microsystems, Inc. * 901 San Antonio Road, Palo Alto, CA 94303, USA * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ************************************************************************/ #include "pnghelper.hxx" #ifdef SYSTEM_ZLIB #include "zlib.h" #else #define ZLIB_INTERNAL 1 #include #endif using namespace pdfi; // checksum helpers, courtesy of libpng.org /* Table of CRCs of all 8-bit messages. */ sal_uInt32 PngHelper::crc_table[256]; /* Flag: has the table been computed? Initially false. */ bool PngHelper::bCRCTableInit = true; /* Make the table for a fast CRC. */ void PngHelper::initCRCTable() { for (sal_uInt32 n = 0; n < 256; n++) { sal_uInt32 c = n; for (int k = 0; k < 8; k++) { if (c & 1) c = 0xedb88320L ^ (c >> 1); else c = c >> 1; } crc_table[n] = c; } bCRCTableInit = false; } /* Update a running CRC with the bytes buf[0..len-1]--the CRC should be initialized to all 1's, and the transmitted value is the 1's complement of the final running CRC (see the crc() routine below)). */ void PngHelper::updateCRC( sal_uInt32& io_rCRC, const sal_uInt8* i_pBuf, size_t i_nLen ) { if( bCRCTableInit ) initCRCTable(); sal_uInt32 nCRC = io_rCRC; for( size_t n = 0; n < i_nLen; n++ ) nCRC = crc_table[(nCRC ^ i_pBuf[n]) & 0xff] ^ (nCRC >> 8); io_rCRC = nCRC; } sal_uInt32 PngHelper::getCRC( const sal_uInt8* i_pBuf, size_t i_nLen ) { sal_uInt32 nCRC = 0xffffffff; updateCRC( nCRC, i_pBuf, i_nLen ); return nCRC ^ 0xffffffff; } sal_uInt32 PngHelper::deflateBuffer( const Output_t* i_pBuf, size_t i_nLen, OutputBuffer& o_rOut ) { size_t nOrigSize = o_rOut.size(); // prepare z stream z_stream aStream; aStream.zalloc = Z_NULL; aStream.zfree = Z_NULL; aStream.opaque = Z_NULL; deflateInit( &aStream, Z_BEST_COMPRESSION ); aStream.avail_in = uInt(i_nLen); aStream.next_in = (Bytef*)i_pBuf; sal_uInt8 aOutBuf[ 32768 ]; do { aStream.avail_out = sizeof( aOutBuf ); aStream.next_out = aOutBuf; if( deflate( &aStream, Z_FINISH ) == Z_STREAM_ERROR ) { deflateEnd( &aStream ); // scrao the data of this broken stream o_rOut.resize( nOrigSize ); return 0; } // append compressed bytes sal_uInt32 nCompressedBytes = sizeof( aOutBuf ) - aStream.avail_out; if( nCompressedBytes ) o_rOut.insert( o_rOut.end(), aOutBuf, aOutBuf+nCompressedBytes ); } while( aStream.avail_out == 0 ); // cleanup deflateEnd( &aStream ); return sal_uInt32( o_rOut.size() - nOrigSize ); } void PngHelper::appendFileHeader( OutputBuffer& o_rOutputBuf ) { static const Output_t aHeader[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; o_rOutputBuf.insert( o_rOutputBuf.end(), aHeader, aHeader + sizeof(aHeader)/sizeof(aHeader[0]) ); } size_t PngHelper::startChunk( const char* pChunkName, OutputBuffer& o_rOutputBuf ) { size_t nIndex = sal_uInt32( o_rOutputBuf.size() ); o_rOutputBuf.insert( o_rOutputBuf.end(), 4, (Output_t)0 ); o_rOutputBuf.push_back( pChunkName[0] ); o_rOutputBuf.push_back( pChunkName[1] ); o_rOutputBuf.push_back( pChunkName[2] ); o_rOutputBuf.push_back( pChunkName[3] ); return nIndex; } void PngHelper::set( sal_uInt32 i_nValue, OutputBuffer& o_rOutputBuf, size_t i_nIndex ) { o_rOutputBuf[ i_nIndex ] = (i_nValue & 0xff000000) >> 24; o_rOutputBuf[ i_nIndex+1 ] = (i_nValue & 0x00ff0000) >> 16; o_rOutputBuf[ i_nIndex+2 ] = (i_nValue & 0x0000ff00) >> 8; o_rOutputBuf[ i_nIndex+3 ] = (i_nValue & 0x000000ff); } void PngHelper::endChunk( size_t nStart, OutputBuffer& o_rOutputBuf ) { if( nStart+8 > o_rOutputBuf.size() ) return; // something broken is going on // update chunk length size_t nLen = o_rOutputBuf.size() - nStart; sal_uInt32 nDataLen = sal_uInt32(nLen)-8; set( nDataLen, o_rOutputBuf, nStart ); // append chunk crc sal_uInt32 nChunkCRC = getCRC( (sal_uInt8*)&o_rOutputBuf[nStart+4], nLen-4 ); append( nChunkCRC, o_rOutputBuf ); } void PngHelper::appendIHDR( OutputBuffer& o_rOutputBuf, int width, int height, int depth, int colortype ) { size_t nStart = startChunk( "IHDR", o_rOutputBuf ); append( width, o_rOutputBuf ); append( height, o_rOutputBuf ); o_rOutputBuf.push_back( Output_t(depth) ); o_rOutputBuf.push_back( Output_t(colortype) ); o_rOutputBuf.push_back( 0 ); // compression method deflate o_rOutputBuf.push_back( 0 ); // filtering method 0 (default) o_rOutputBuf.push_back( 0 ); // no interlacing endChunk( nStart, o_rOutputBuf ); } void PngHelper::appendIEND( OutputBuffer& o_rOutputBuf ) { size_t nStart = startChunk( "IEND", o_rOutputBuf ); endChunk( nStart, o_rOutputBuf ); } void PngHelper::createPng( OutputBuffer& o_rOutputBuf, Stream* str, int width, int height, GfxRGB& zeroColor, GfxRGB& oneColor, bool bIsMask ) { appendFileHeader( o_rOutputBuf ); appendIHDR( o_rOutputBuf, width, height, 1, 3 ); // write palette size_t nIdx = startChunk( "PLTE", o_rOutputBuf ); // write colors 0 and 1 o_rOutputBuf.push_back(colToByte(zeroColor.r)); o_rOutputBuf.push_back(colToByte(zeroColor.g)); o_rOutputBuf.push_back(colToByte(zeroColor.b)); o_rOutputBuf.push_back(colToByte(oneColor.r)); o_rOutputBuf.push_back(colToByte(oneColor.g)); o_rOutputBuf.push_back(colToByte(oneColor.b)); // end PLTE chunk endChunk( nIdx, o_rOutputBuf ); if( bIsMask ) { // write tRNS chunk nIdx = startChunk( "tRNS", o_rOutputBuf ); o_rOutputBuf.push_back( 0xff ); o_rOutputBuf.push_back( 0 ); // end tRNS chunk endChunk( nIdx, o_rOutputBuf ); } // create scan line data buffer OutputBuffer aScanlines; int nLineSize = (width + 7)/8; aScanlines.reserve( nLineSize * height + height ); str->reset(); for( int y = 0; y < height; y++ ) { // determine filter type (none) for this scanline aScanlines.push_back( 0 ); for( int x = 0; x < nLineSize; x++ ) aScanlines.push_back( str->getChar() ); } // begin IDAT chunk for scanline data nIdx = startChunk( "IDAT", o_rOutputBuf ); // compress scanlines deflateBuffer( &aScanlines[0], aScanlines.size(), o_rOutputBuf ); // end IDAT chunk endChunk( nIdx, o_rOutputBuf ); // output IEND appendIEND( o_rOutputBuf ); } void PngHelper::createPng( OutputBuffer& o_rOutputBuf, Stream* str, int width, int height, GfxImageColorMap* colorMap, Stream* maskStr, int maskWidth, int maskHeight, GfxImageColorMap* maskColorMap ) { appendFileHeader( o_rOutputBuf ); appendIHDR( o_rOutputBuf, width, height, 8, 6 ); // RGBA image // initialize stream Guchar *p, *pm; GfxRGB rgb; GfxGray alpha; ImageStream* imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); imgStr->reset(); // create scan line data buffer OutputBuffer aScanlines; aScanlines.reserve( width*height*4 + height ); for( int y=0; ygetLine(); for( int x=0; xgetRGB(p, &rgb); aScanlines.push_back(colToByte(rgb.r)); aScanlines.push_back(colToByte(rgb.g)); aScanlines.push_back(colToByte(rgb.b)); aScanlines.push_back( 0xff ); p +=colorMap->getNumPixelComps(); } } // now fill in the mask data // CAUTION: originally this was done in one single loop // it caused merry chaos; the reason is that maskStr and str are // not independent streams, it happens that reading one advances // the other, too. Hence the two passes are imperative ! // initialize mask stream ImageStream* imgStrMask = new ImageStream(maskStr, maskWidth, maskColorMap->getNumPixelComps(), maskColorMap->getBits()); imgStrMask->reset(); for( int y = 0; y < maskHeight; ++y ) { pm = imgStrMask->getLine(); for( int x = 0; x < maskWidth; ++x ) { maskColorMap->getGray(pm,&alpha); pm += maskColorMap->getNumPixelComps(); int nIndex = (y*height/maskHeight) * (width*4+1) + // mapped line (x*width/maskWidth)*4 + 1 + 3 // mapped column ; aScanlines[ nIndex ] = colToByte(alpha); } } delete imgStr; delete imgStrMask; // begind IDAT chunk for scanline data size_t nIdx = startChunk( "IDAT", o_rOutputBuf ); // compress scanlines deflateBuffer( &aScanlines[0], aScanlines.size(), o_rOutputBuf ); // end IDAT chunk endChunk( nIdx, o_rOutputBuf ); // output IEND appendIEND( o_rOutputBuf ); } // one bit mask; 0 bits opaque void PngHelper::createPng( OutputBuffer& o_rOutputBuf, Stream* str, int width, int height, GfxImageColorMap* colorMap, Stream* maskStr, int maskWidth, int maskHeight, bool maskInvert ) { appendFileHeader( o_rOutputBuf ); appendIHDR( o_rOutputBuf, width, height, 8, 6 ); // RGBA image // initialize stream Guchar *p; GfxRGB rgb; ImageStream* imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); imgStr->reset(); // create scan line data buffer OutputBuffer aScanlines; aScanlines.reserve( width*height*4 + height ); for( int y=0; ygetLine(); for( int x=0; xgetRGB(p, &rgb); aScanlines.push_back(colToByte(rgb.r)); aScanlines.push_back(colToByte(rgb.g)); aScanlines.push_back(colToByte(rgb.b)); aScanlines.push_back( 0xff ); p +=colorMap->getNumPixelComps(); } } // now fill in the mask data // CAUTION: originally this was done in one single loop // it caused merry chaos; the reason is that maskStr and str are // not independent streams, it happens that reading one advances // the other, too. Hence the two passes are imperative ! // initialize mask stream ImageStream* imgStrMask = new ImageStream(maskStr, maskWidth, 1, 1); imgStrMask->reset(); for( int y = 0; y < maskHeight; ++y ) { for( int x = 0; x < maskWidth; ++x ) { Guchar aPixel = 0; imgStrMask->getPixel( &aPixel ); int nIndex = (y*height/maskHeight) * (width*4+1) + // mapped line (x*width/maskWidth)*4 + 1 + 3 // mapped column ; if( maskInvert ) aScanlines[ nIndex ] = aPixel ? 0xff : 0x00; else aScanlines[ nIndex ] = aPixel ? 0x00 : 0xff; } } delete imgStr; delete imgStrMask; // begind IDAT chunk for scanline data size_t nIdx = startChunk( "IDAT", o_rOutputBuf ); // compress scanlines deflateBuffer( &aScanlines[0], aScanlines.size(), o_rOutputBuf ); // end IDAT chunk endChunk( nIdx, o_rOutputBuf ); // output IEND appendIEND( o_rOutputBuf ); }