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