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