| /************************************************************** |
| * |
| * 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. |
| * |
| *************************************************************/ |
| |
| #include "pnghelper.hxx" |
| |
| #ifdef SYSTEM_ZLIB |
| #include "zlib.h" |
| #else |
| #define ZLIB_INTERNAL 1 |
| #include <zlib/zlib.h> |
| #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; y<height; ++y) |
| { |
| aScanlines.push_back( 0 ); |
| p = imgStr->getLine(); |
| for( int x=0; x<width; ++x) |
| { |
| colorMap->getRGB(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; y<height; ++y) |
| { |
| aScanlines.push_back( 0 ); |
| p = imgStr->getLine(); |
| for( int x=0; x<width; ++x) |
| { |
| colorMap->getRGB(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 ); |
| } |
| |