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