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