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  deflateInit( &aStream, Z_BEST_COMPRESSION );
86  aStream.avail_in = uInt(i_nLen);
87  aStream.next_in = const_cast<Bytef*>(i_pBuf);
88 
89  sal_uInt8 aOutBuf[ 32768 ];
90  do
91  {
92  aStream.avail_out = sizeof( aOutBuf );
93  aStream.next_out = aOutBuf;
94 
95  if( deflate( &aStream, Z_FINISH ) == Z_STREAM_ERROR )
96  {
97  deflateEnd( &aStream );
98  // scrap the data of this broken stream
99  o_rOut.resize( nOrigSize );
100  return 0;
101  }
102 
103  // append compressed bytes
104  sal_uInt32 nCompressedBytes = sizeof( aOutBuf ) - aStream.avail_out;
105  if( nCompressedBytes )
106  o_rOut.insert( o_rOut.end(), aOutBuf, aOutBuf+nCompressedBytes );
107 
108  } while( aStream.avail_out == 0 );
109 
110  // cleanup
111  deflateEnd( &aStream );
112 
113  return sal_uInt32( o_rOut.size() - nOrigSize );
114 }
115 
117 {
118  static const unsigned char aHeader[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
119 
120  o_rOutputBuf.insert( o_rOutputBuf.end(), aHeader, aHeader + SAL_N_ELEMENTS(aHeader) );
121 }
122 
123 size_t PngHelper::startChunk( const char* pChunkName, OutputBuffer& o_rOutputBuf )
124 {
125  size_t nIndex = sal_uInt32( o_rOutputBuf.size() );
126  o_rOutputBuf.insert( o_rOutputBuf.end(), 4, Output_t(0) );
127  o_rOutputBuf.push_back( pChunkName[0] );
128  o_rOutputBuf.push_back( pChunkName[1] );
129  o_rOutputBuf.push_back( pChunkName[2] );
130  o_rOutputBuf.push_back( pChunkName[3] );
131  return nIndex;
132 }
133 
134 void PngHelper::set( sal_uInt32 i_nValue, OutputBuffer& o_rOutputBuf, size_t i_nIndex )
135 {
136  o_rOutputBuf[ i_nIndex ] = (i_nValue & 0xff000000) >> 24;
137  o_rOutputBuf[ i_nIndex+1 ] = (i_nValue & 0x00ff0000) >> 16;
138  o_rOutputBuf[ i_nIndex+2 ] = (i_nValue & 0x0000ff00) >> 8;
139  o_rOutputBuf[ i_nIndex+3 ] = (i_nValue & 0x000000ff);
140 }
141 
142 void PngHelper::endChunk( size_t nStart, OutputBuffer& o_rOutputBuf )
143 {
144  if( nStart+8 > o_rOutputBuf.size() )
145  return; // something broken is going on
146 
147  // update chunk length
148  size_t nLen = o_rOutputBuf.size() - nStart;
149  sal_uInt32 nDataLen = sal_uInt32(nLen)-8;
150  set( nDataLen, o_rOutputBuf, nStart );
151 
152  // append chunk crc
153  sal_uInt32 nChunkCRC = getCRC( reinterpret_cast<sal_uInt8*>(&o_rOutputBuf[nStart+4]), nLen-4 );
154  append( nChunkCRC, o_rOutputBuf );
155 }
156 
157 void PngHelper::appendIHDR( OutputBuffer& o_rOutputBuf, int width, int height, int depth, int colortype )
158 {
159  size_t nStart = startChunk( "IHDR", o_rOutputBuf );
160  append( width, o_rOutputBuf );
161  append( height, o_rOutputBuf );
162  o_rOutputBuf.push_back( Output_t(depth) );
163  o_rOutputBuf.push_back( Output_t(colortype) );
164  o_rOutputBuf.push_back( 0 ); // compression method deflate
165  o_rOutputBuf.push_back( 0 ); // filtering method 0 (default)
166  o_rOutputBuf.push_back( 0 ); // no interlacing
167  endChunk( nStart, o_rOutputBuf );
168 }
169 
170 void PngHelper::appendIEND( OutputBuffer& o_rOutputBuf )
171 {
172  size_t nStart = startChunk( "IEND", o_rOutputBuf );
173  endChunk( nStart, o_rOutputBuf );
174 }
175 
176 void PngHelper::createPng( OutputBuffer& o_rOutputBuf,
177  Stream* str,
178  int width,
179  int height,
180  GfxRGB const & zeroColor,
181  GfxRGB const & oneColor,
182  bool bIsMask
183  )
184 {
185  appendFileHeader( o_rOutputBuf );
186  appendIHDR( o_rOutputBuf, width, height, 1, 3 );
187 
188  // write palette
189  size_t nIdx = startChunk( "PLTE", o_rOutputBuf );
190  // write colors 0 and 1
191  o_rOutputBuf.push_back(colToByte(zeroColor.r));
192  o_rOutputBuf.push_back(colToByte(zeroColor.g));
193  o_rOutputBuf.push_back(colToByte(zeroColor.b));
194  o_rOutputBuf.push_back(colToByte(oneColor.r));
195  o_rOutputBuf.push_back(colToByte(oneColor.g));
196  o_rOutputBuf.push_back(colToByte(oneColor.b));
197  // end PLTE chunk
198  endChunk( nIdx, o_rOutputBuf );
199 
200  if( bIsMask )
201  {
202  // write tRNS chunk
203  nIdx = startChunk( "tRNS", o_rOutputBuf );
204  o_rOutputBuf.push_back( 0xff );
205  o_rOutputBuf.push_back( 0 );
206  // end tRNS chunk
207  endChunk( nIdx, o_rOutputBuf );
208  }
209 
210  // create scan line data buffer
211  OutputBuffer aScanlines;
212  int nLineSize = (width + 7)/8;
213  aScanlines.reserve( nLineSize * height + height );
214 
215  str->reset();
216  for( int y = 0; y < height; y++ )
217  {
218  // determine filter type (none) for this scanline
219  aScanlines.push_back( 0 );
220  for( int x = 0; x < nLineSize; x++ )
221  aScanlines.push_back( str->getChar() );
222  }
223 
224  // begin IDAT chunk for scanline data
225  nIdx = startChunk( "IDAT", o_rOutputBuf );
226  // compress scanlines
227  deflateBuffer( aScanlines.data(), aScanlines.size(), o_rOutputBuf );
228  // end IDAT chunk
229  endChunk( nIdx, o_rOutputBuf );
230 
231  // output IEND
232  appendIEND( o_rOutputBuf );
233 }
234 
235 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  appendFileHeader( o_rOutputBuf );
242  appendIHDR( o_rOutputBuf, width, height, 8, 6 ); // RGBA image
243 
244  // initialize stream
245  unsigned char *p, *pm;
246  GfxRGB rgb;
247  GfxGray alpha;
248  std::unique_ptr<ImageStream> imgStr(
249  new ImageStream(str,
250  width,
251  colorMap->getNumPixelComps(),
252  colorMap->getBits()));
253  imgStr->reset();
254 
255  // create scan line data buffer
256  OutputBuffer aScanlines;
257  aScanlines.reserve( width*height*4 + height );
258 
259  for( int y=0; y<height; ++y)
260  {
261  aScanlines.push_back( 0 );
262  p = imgStr->getLine();
263  for( int x=0; x<width; ++x)
264  {
265  colorMap->getRGB(p, &rgb);
266  aScanlines.push_back(colToByte(rgb.r));
267  aScanlines.push_back(colToByte(rgb.g));
268  aScanlines.push_back(colToByte(rgb.b));
269  aScanlines.push_back( 0xff );
270 
271  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  std::unique_ptr<ImageStream> imgStrMask(
285  new ImageStream(maskStr,
286  maskWidth,
287  maskColorMap->getNumPixelComps(),
288  maskColorMap->getBits()));
289 
290  imgStrMask->reset();
291  for( int y = 0; y < maskHeight; ++y )
292  {
293  pm = imgStrMask->getLine();
294  for( int x = 0; x < maskWidth; ++x )
295  {
296  maskColorMap->getGray(pm,&alpha);
297  pm += maskColorMap->getNumPixelComps();
298  int nIndex = (y*height/maskHeight) * (width*4+1) + // mapped line
299  (x*width/maskWidth)*4 + 1 + 3 // mapped column
300  ;
301  aScanlines[ nIndex ] = colToByte(alpha);
302  }
303  }
304 
305  imgStr.reset();
306  imgStrMask.reset();
307 
308  // begind IDAT chunk for scanline data
309  size_t nIdx = startChunk( "IDAT", o_rOutputBuf );
310  // compress scanlines
311  deflateBuffer( aScanlines.data(), aScanlines.size(), o_rOutputBuf );
312  // end IDAT chunk
313  endChunk( nIdx, o_rOutputBuf );
314  // output IEND
315  appendIEND( o_rOutputBuf );
316 }
317 
318 // one bit mask; 0 bits opaque
319 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  appendFileHeader( o_rOutputBuf );
328  appendIHDR( o_rOutputBuf, width, height, 8, 6 ); // RGBA image
329 
330  // initialize stream
331  unsigned char *p;
332  GfxRGB rgb;
333  std::unique_ptr<ImageStream> imgStr(
334  new ImageStream(str,
335  width,
336  colorMap->getNumPixelComps(),
337  colorMap->getBits()));
338  imgStr->reset();
339 
340  // create scan line data buffer
341  OutputBuffer aScanlines;
342  aScanlines.reserve( width*height*4 + height );
343 
344  for( int y=0; y<height; ++y)
345  {
346  aScanlines.push_back( 0 );
347  p = imgStr->getLine();
348  for( int x=0; x<width; ++x)
349  {
350  colorMap->getRGB(p, &rgb);
351  aScanlines.push_back(colToByte(rgb.r));
352  aScanlines.push_back(colToByte(rgb.g));
353  aScanlines.push_back(colToByte(rgb.b));
354  aScanlines.push_back( 0xff );
355 
356  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  std::unique_ptr<ImageStream> imgStrMask(
370  new ImageStream(maskStr, maskWidth, 1, 1));
371 
372  imgStrMask->reset();
373  for( int y = 0; y < maskHeight; ++y )
374  {
375  for( int x = 0; x < maskWidth; ++x )
376  {
377  unsigned char aPixel = 0;
378  imgStrMask->getPixel( &aPixel );
379  int nIndex = (y*height/maskHeight) * (width*4+1) + // mapped line
380  (x*width/maskWidth)*4 + 1 + 3 // mapped column
381  ;
382  if( maskInvert )
383  aScanlines[ nIndex ] = aPixel ? 0xff : 0x00;
384  else
385  aScanlines[ nIndex ] = aPixel ? 0x00 : 0xff;
386  }
387  }
388 
389  imgStr.reset();
390  imgStrMask.reset();
391 
392  // begind IDAT chunk for scanline data
393  size_t nIdx = startChunk( "IDAT", o_rOutputBuf );
394  // compress scanlines
395  deflateBuffer( aScanlines.data(), aScanlines.size(), o_rOutputBuf );
396  // end IDAT chunk
397  endChunk( nIdx, o_rOutputBuf );
398  // output IEND
399  appendIEND( o_rOutputBuf );
400 }
401 
402 /* 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:134
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:157
static void endChunk(size_t nStart, OutputBuffer &o_rOut)
Definition: pnghelper.cxx:142
float y
#define SAL_N_ELEMENTS(arr)
static void appendIEND(OutputBuffer &o_rOutputBuf)
Definition: pnghelper.cxx:170
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:176
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:123
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:116
static sal_uInt32 crc_table[256]
Definition: pnghelper.hxx:31