1 /************************************************************************* 2 * 3 * OpenOffice.org - a multi-platform office productivity suite 4 * 5 * The Contents of this file are made available subject to 6 * the terms of GNU General Public License Version 2. 7 * 8 * 9 * GNU General Public License, version 2 10 * ============================================= 11 * Copyright 2005 by Sun Microsystems, Inc. 12 * 901 San Antonio Road, Palo Alto, CA 94303, USA 13 * 14 * This program is free software; you can redistribute it and/or 15 * modify it under the terms of the GNU General Public License as 16 * published by the Free Software Foundation; either version 2 of 17 * the License, or (at your option) any later version. 18 * 19 * This program is distributed in the hope that it will be useful, 20 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 * GNU General Public License for more details. 23 * 24 * You should have received a copy of the GNU General Public 25 * License along with this program; if not, write to the Free 26 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 27 * Boston, MA 02110-1301, USA. 28 * 29 ************************************************************************/ 30 31 #include "pnghelper.hxx" 32 33 #ifdef SYSTEM_ZLIB 34 #include "zlib.h" 35 #else 36 #define ZLIB_INTERNAL 1 37 #include <zlib/zlib.h> 38 #endif 39 40 using namespace pdfi; 41 42 // checksum helpers, courtesy of libpng.org 43 44 /* Table of CRCs of all 8-bit messages. */ 45 sal_uInt32 PngHelper::crc_table[256]; 46 47 /* Flag: has the table been computed? Initially false. */ 48 bool PngHelper::bCRCTableInit = true; 49 50 /* Make the table for a fast CRC. */ 51 void PngHelper::initCRCTable() 52 { 53 for (sal_uInt32 n = 0; n < 256; n++) 54 { 55 sal_uInt32 c = n; 56 for (int k = 0; k < 8; k++) 57 { 58 if (c & 1) 59 c = 0xedb88320L ^ (c >> 1); 60 else 61 c = c >> 1; 62 } 63 crc_table[n] = c; 64 } 65 bCRCTableInit = false; 66 } 67 68 /* Update a running CRC with the bytes buf[0..len-1]--the CRC 69 should be initialized to all 1's, and the transmitted value 70 is the 1's complement of the final running CRC (see the 71 crc() routine below)). */ 72 73 void PngHelper::updateCRC( sal_uInt32& io_rCRC, const sal_uInt8* i_pBuf, size_t i_nLen ) 74 { 75 if( bCRCTableInit ) 76 initCRCTable(); 77 78 sal_uInt32 nCRC = io_rCRC; 79 for( size_t n = 0; n < i_nLen; n++ ) 80 nCRC = crc_table[(nCRC ^ i_pBuf[n]) & 0xff] ^ (nCRC >> 8); 81 io_rCRC = nCRC; 82 } 83 84 sal_uInt32 PngHelper::getCRC( const sal_uInt8* i_pBuf, size_t i_nLen ) 85 { 86 sal_uInt32 nCRC = 0xffffffff; 87 updateCRC( nCRC, i_pBuf, i_nLen ); 88 return nCRC ^ 0xffffffff; 89 } 90 91 sal_uInt32 PngHelper::deflateBuffer( const Output_t* i_pBuf, size_t i_nLen, OutputBuffer& o_rOut ) 92 { 93 size_t nOrigSize = o_rOut.size(); 94 95 // prepare z stream 96 z_stream aStream; 97 aStream.zalloc = Z_NULL; 98 aStream.zfree = Z_NULL; 99 aStream.opaque = Z_NULL; 100 deflateInit( &aStream, Z_BEST_COMPRESSION ); 101 aStream.avail_in = uInt(i_nLen); 102 aStream.next_in = (Bytef*)i_pBuf; 103 104 sal_uInt8 aOutBuf[ 32768 ]; 105 do 106 { 107 aStream.avail_out = sizeof( aOutBuf ); 108 aStream.next_out = aOutBuf; 109 110 if( deflate( &aStream, Z_FINISH ) == Z_STREAM_ERROR ) 111 { 112 deflateEnd( &aStream ); 113 // scrao the data of this broken stream 114 o_rOut.resize( nOrigSize ); 115 return 0; 116 } 117 118 // append compressed bytes 119 sal_uInt32 nCompressedBytes = sizeof( aOutBuf ) - aStream.avail_out; 120 if( nCompressedBytes ) 121 o_rOut.insert( o_rOut.end(), aOutBuf, aOutBuf+nCompressedBytes ); 122 123 } while( aStream.avail_out == 0 ); 124 125 // cleanup 126 deflateEnd( &aStream ); 127 128 return sal_uInt32( o_rOut.size() - nOrigSize ); 129 } 130 131 void PngHelper::appendFileHeader( OutputBuffer& o_rOutputBuf ) 132 { 133 static const Output_t aHeader[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; 134 135 o_rOutputBuf.insert( o_rOutputBuf.end(), aHeader, aHeader + sizeof(aHeader)/sizeof(aHeader[0]) ); 136 } 137 138 size_t PngHelper::startChunk( const char* pChunkName, OutputBuffer& o_rOutputBuf ) 139 { 140 size_t nIndex = sal_uInt32( o_rOutputBuf.size() ); 141 o_rOutputBuf.insert( o_rOutputBuf.end(), 4, (Output_t)0 ); 142 o_rOutputBuf.push_back( pChunkName[0] ); 143 o_rOutputBuf.push_back( pChunkName[1] ); 144 o_rOutputBuf.push_back( pChunkName[2] ); 145 o_rOutputBuf.push_back( pChunkName[3] ); 146 return nIndex; 147 } 148 149 void PngHelper::set( sal_uInt32 i_nValue, OutputBuffer& o_rOutputBuf, size_t i_nIndex ) 150 { 151 o_rOutputBuf[ i_nIndex ] = (i_nValue & 0xff000000) >> 24; 152 o_rOutputBuf[ i_nIndex+1 ] = (i_nValue & 0x00ff0000) >> 16; 153 o_rOutputBuf[ i_nIndex+2 ] = (i_nValue & 0x0000ff00) >> 8; 154 o_rOutputBuf[ i_nIndex+3 ] = (i_nValue & 0x000000ff); 155 } 156 157 void PngHelper::endChunk( size_t nStart, OutputBuffer& o_rOutputBuf ) 158 { 159 if( nStart+8 > o_rOutputBuf.size() ) 160 return; // something broken is going on 161 162 // update chunk length 163 size_t nLen = o_rOutputBuf.size() - nStart; 164 sal_uInt32 nDataLen = sal_uInt32(nLen)-8; 165 set( nDataLen, o_rOutputBuf, nStart ); 166 167 // append chunk crc 168 sal_uInt32 nChunkCRC = getCRC( (sal_uInt8*)&o_rOutputBuf[nStart+4], nLen-4 ); 169 append( nChunkCRC, o_rOutputBuf ); 170 } 171 172 void PngHelper::appendIHDR( OutputBuffer& o_rOutputBuf, int width, int height, int depth, int colortype ) 173 { 174 size_t nStart = startChunk( "IHDR", o_rOutputBuf ); 175 append( width, o_rOutputBuf ); 176 append( height, o_rOutputBuf ); 177 o_rOutputBuf.push_back( Output_t(depth) ); 178 o_rOutputBuf.push_back( Output_t(colortype) ); 179 o_rOutputBuf.push_back( 0 ); // compression method deflate 180 o_rOutputBuf.push_back( 0 ); // filtering method 0 (default) 181 o_rOutputBuf.push_back( 0 ); // no interlacing 182 endChunk( nStart, o_rOutputBuf ); 183 } 184 185 void PngHelper::appendIEND( OutputBuffer& o_rOutputBuf ) 186 { 187 size_t nStart = startChunk( "IEND", o_rOutputBuf ); 188 endChunk( nStart, o_rOutputBuf ); 189 } 190 191 void PngHelper::createPng( OutputBuffer& o_rOutputBuf, 192 Stream* str, 193 int width, 194 int height, 195 GfxRGB& zeroColor, 196 GfxRGB& oneColor, 197 bool bIsMask 198 ) 199 { 200 appendFileHeader( o_rOutputBuf ); 201 appendIHDR( o_rOutputBuf, width, height, 1, 3 ); 202 203 // write palette 204 size_t nIdx = startChunk( "PLTE", o_rOutputBuf ); 205 // write colors 0 and 1 206 o_rOutputBuf.push_back(colToByte(zeroColor.r)); 207 o_rOutputBuf.push_back(colToByte(zeroColor.g)); 208 o_rOutputBuf.push_back(colToByte(zeroColor.b)); 209 o_rOutputBuf.push_back(colToByte(oneColor.r)); 210 o_rOutputBuf.push_back(colToByte(oneColor.g)); 211 o_rOutputBuf.push_back(colToByte(oneColor.b)); 212 // end PLTE chunk 213 endChunk( nIdx, o_rOutputBuf ); 214 215 if( bIsMask ) 216 { 217 // write tRNS chunk 218 nIdx = startChunk( "tRNS", o_rOutputBuf ); 219 o_rOutputBuf.push_back( 0xff ); 220 o_rOutputBuf.push_back( 0 ); 221 // end tRNS chunk 222 endChunk( nIdx, o_rOutputBuf ); 223 } 224 225 // create scan line data buffer 226 OutputBuffer aScanlines; 227 int nLineSize = (width + 7)/8; 228 aScanlines.reserve( nLineSize * height + height ); 229 230 str->reset(); 231 for( int y = 0; y < height; y++ ) 232 { 233 // determine filter type (none) for this scanline 234 aScanlines.push_back( 0 ); 235 for( int x = 0; x < nLineSize; x++ ) 236 aScanlines.push_back( str->getChar() ); 237 } 238 239 // begin IDAT chunk for scanline data 240 nIdx = startChunk( "IDAT", o_rOutputBuf ); 241 // compress scanlines 242 deflateBuffer( &aScanlines[0], aScanlines.size(), o_rOutputBuf ); 243 // end IDAT chunk 244 endChunk( nIdx, o_rOutputBuf ); 245 246 // output IEND 247 appendIEND( o_rOutputBuf ); 248 } 249 250 void PngHelper::createPng( OutputBuffer& o_rOutputBuf, 251 Stream* str, 252 int width, int height, GfxImageColorMap* colorMap, 253 Stream* maskStr, 254 int maskWidth, int maskHeight, GfxImageColorMap* maskColorMap ) 255 { 256 appendFileHeader( o_rOutputBuf ); 257 appendIHDR( o_rOutputBuf, width, height, 8, 6 ); // RGBA image 258 259 // initialize stream 260 Guchar *p, *pm; 261 GfxRGB rgb; 262 GfxGray alpha; 263 ImageStream* imgStr = 264 new ImageStream(str, 265 width, 266 colorMap->getNumPixelComps(), 267 colorMap->getBits()); 268 imgStr->reset(); 269 270 // create scan line data buffer 271 OutputBuffer aScanlines; 272 aScanlines.reserve( width*height*4 + height ); 273 274 for( int y=0; y<height; ++y) 275 { 276 aScanlines.push_back( 0 ); 277 p = imgStr->getLine(); 278 for( int x=0; x<width; ++x) 279 { 280 colorMap->getRGB(p, &rgb); 281 aScanlines.push_back(colToByte(rgb.r)); 282 aScanlines.push_back(colToByte(rgb.g)); 283 aScanlines.push_back(colToByte(rgb.b)); 284 aScanlines.push_back( 0xff ); 285 286 p +=colorMap->getNumPixelComps(); 287 } 288 } 289 290 291 // now fill in the mask data 292 293 // CAUTION: originally this was done in one single loop 294 // it caused merry chaos; the reason is that maskStr and str are 295 // not independent streams, it happens that reading one advances 296 // the other, too. Hence the two passes are imperative ! 297 298 // initialize mask stream 299 ImageStream* imgStrMask = 300 new ImageStream(maskStr, 301 maskWidth, 302 maskColorMap->getNumPixelComps(), 303 maskColorMap->getBits()); 304 305 imgStrMask->reset(); 306 for( int y = 0; y < maskHeight; ++y ) 307 { 308 pm = imgStrMask->getLine(); 309 for( int x = 0; x < maskWidth; ++x ) 310 { 311 maskColorMap->getGray(pm,&alpha); 312 pm += maskColorMap->getNumPixelComps(); 313 int nIndex = (y*height/maskHeight) * (width*4+1) + // mapped line 314 (x*width/maskWidth)*4 + 1 + 3 // mapped column 315 ; 316 aScanlines[ nIndex ] = colToByte(alpha); 317 } 318 } 319 320 delete imgStr; 321 delete imgStrMask; 322 323 // begind IDAT chunk for scanline data 324 size_t nIdx = startChunk( "IDAT", o_rOutputBuf ); 325 // compress scanlines 326 deflateBuffer( &aScanlines[0], aScanlines.size(), o_rOutputBuf ); 327 // end IDAT chunk 328 endChunk( nIdx, o_rOutputBuf ); 329 // output IEND 330 appendIEND( o_rOutputBuf ); 331 } 332 333 // one bit mask; 0 bits opaque 334 void PngHelper::createPng( OutputBuffer& o_rOutputBuf, 335 Stream* str, 336 int width, int height, GfxImageColorMap* colorMap, 337 Stream* maskStr, 338 int maskWidth, int maskHeight, 339 bool maskInvert 340 ) 341 { 342 appendFileHeader( o_rOutputBuf ); 343 appendIHDR( o_rOutputBuf, width, height, 8, 6 ); // RGBA image 344 345 // initialize stream 346 Guchar *p; 347 GfxRGB rgb; 348 ImageStream* imgStr = 349 new ImageStream(str, 350 width, 351 colorMap->getNumPixelComps(), 352 colorMap->getBits()); 353 imgStr->reset(); 354 355 // create scan line data buffer 356 OutputBuffer aScanlines; 357 aScanlines.reserve( width*height*4 + height ); 358 359 for( int y=0; y<height; ++y) 360 { 361 aScanlines.push_back( 0 ); 362 p = imgStr->getLine(); 363 for( int x=0; x<width; ++x) 364 { 365 colorMap->getRGB(p, &rgb); 366 aScanlines.push_back(colToByte(rgb.r)); 367 aScanlines.push_back(colToByte(rgb.g)); 368 aScanlines.push_back(colToByte(rgb.b)); 369 aScanlines.push_back( 0xff ); 370 371 p +=colorMap->getNumPixelComps(); 372 } 373 } 374 375 376 // now fill in the mask data 377 378 // CAUTION: originally this was done in one single loop 379 // it caused merry chaos; the reason is that maskStr and str are 380 // not independent streams, it happens that reading one advances 381 // the other, too. Hence the two passes are imperative ! 382 383 // initialize mask stream 384 ImageStream* imgStrMask = 385 new ImageStream(maskStr, maskWidth, 1, 1); 386 387 imgStrMask->reset(); 388 for( int y = 0; y < maskHeight; ++y ) 389 { 390 for( int x = 0; x < maskWidth; ++x ) 391 { 392 Guchar aPixel = 0; 393 imgStrMask->getPixel( &aPixel ); 394 int nIndex = (y*height/maskHeight) * (width*4+1) + // mapped line 395 (x*width/maskWidth)*4 + 1 + 3 // mapped column 396 ; 397 if( maskInvert ) 398 aScanlines[ nIndex ] = aPixel ? 0xff : 0x00; 399 else 400 aScanlines[ nIndex ] = aPixel ? 0x00 : 0xff; 401 } 402 } 403 404 delete imgStr; 405 delete imgStrMask; 406 407 // begind IDAT chunk for scanline data 408 size_t nIdx = startChunk( "IDAT", o_rOutputBuf ); 409 // compress scanlines 410 deflateBuffer( &aScanlines[0], aScanlines.size(), o_rOutputBuf ); 411 // end IDAT chunk 412 endChunk( nIdx, o_rOutputBuf ); 413 // output IEND 414 appendIEND( o_rOutputBuf ); 415 } 416 417