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