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. */
initCRCTable()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
updateCRC(sal_uInt32 & io_rCRC,const sal_uInt8 * i_pBuf,size_t i_nLen)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
getCRC(const sal_uInt8 * i_pBuf,size_t i_nLen)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
deflateBuffer(const Output_t * i_pBuf,size_t i_nLen,OutputBuffer & o_rOut)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
appendFileHeader(OutputBuffer & o_rOutputBuf)122 void PngHelper::appendFileHeader( OutputBuffer& o_rOutputBuf )
123 {
124 static const Output_t aHeader[] = { static_cast<char>(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
startChunk(const char * pChunkName,OutputBuffer & o_rOutputBuf)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
set(sal_uInt32 i_nValue,OutputBuffer & o_rOutputBuf,size_t i_nIndex)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
endChunk(size_t nStart,OutputBuffer & o_rOutputBuf)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
appendIHDR(OutputBuffer & o_rOutputBuf,int width,int height,int depth,int colortype)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
appendIEND(OutputBuffer & o_rOutputBuf)176 void PngHelper::appendIEND( OutputBuffer& o_rOutputBuf )
177 {
178 size_t nStart = startChunk( "IEND", o_rOutputBuf );
179 endChunk( nStart, o_rOutputBuf );
180 }
181
createPng(OutputBuffer & o_rOutputBuf,Stream * str,int width,int height,GfxRGB & zeroColor,GfxRGB & oneColor,bool bIsMask)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
createPng(OutputBuffer & o_rOutputBuf,Stream * str,int width,int height,GfxImageColorMap * colorMap,Stream * maskStr,int maskWidth,int maskHeight,GfxImageColorMap * maskColorMap)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
createPng(OutputBuffer & o_rOutputBuf,Stream * str,int width,int height,GfxImageColorMap * colorMap,Stream * maskStr,int maskWidth,int maskHeight,bool maskInvert)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