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