LibreOffice Module vcl (master)  1
PngImageReader.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  */
10 
12 #include <png.h>
13 #include <rtl/crc.h>
14 #include <tools/stream.hxx>
15 #include <vcl/bitmap.hxx>
16 #include <vcl/alpha.hxx>
17 #include <vcl/BitmapTools.hxx>
18 #include <unotools/configmgr.hxx>
19 
21 #include <svdata.hxx>
22 #include <salinst.hxx>
23 
24 #include "png.hxx"
25 
26 namespace
27 {
28 void lclReadStream(png_structp pPng, png_bytep pOutBytes, png_size_t nBytesToRead)
29 {
30  png_voidp pIO = png_get_io_ptr(pPng);
31 
32  if (pIO == nullptr)
33  return;
34 
35  SvStream* pStream = static_cast<SvStream*>(pIO);
36 
37  sal_Size nBytesRead = pStream->ReadBytes(pOutBytes, nBytesToRead);
38 
39  if (nBytesRead != nBytesToRead)
40  {
41  if (!nBytesRead)
42  png_error(pPng, "Error reading");
43  else
44  {
45  // Make sure to not reuse old data (could cause infinite loop).
46  memset(pOutBytes + nBytesRead, 0, nBytesToRead - nBytesRead);
47  png_warning(pPng, "Short read");
48  }
49  }
50 }
51 
52 constexpr int PNG_SIGNATURE_SIZE = 8;
53 
54 bool isPng(SvStream& rStream)
55 {
56  // Check signature bytes
57  sal_uInt8 aHeader[PNG_SIGNATURE_SIZE];
58  if (rStream.ReadBytes(aHeader, PNG_SIGNATURE_SIZE) != PNG_SIGNATURE_SIZE)
59  return false;
60  return png_sig_cmp(aHeader, 0, PNG_SIGNATURE_SIZE) == 0;
61 }
62 
63 struct PngDestructor
64 {
65  ~PngDestructor() { png_destroy_read_struct(&pPng, &pInfo, nullptr); }
66  png_structp pPng;
67  png_infop pInfo;
68 };
69 
70 #if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__
71 #pragma GCC diagnostic push
72 #pragma GCC diagnostic ignored "-Wclobbered"
73 #endif
74 bool reader(SvStream& rStream, BitmapEx& rBitmapEx,
76  BitmapScopedWriteAccess* pAccess = nullptr,
77  AlphaScopedWriteAccess* pAlphaAccess = nullptr)
78 {
79  if (!isPng(rStream))
80  return false;
81 
82  png_structp pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
83  if (!pPng)
84  return false;
85 
86  png_infop pInfo = png_create_info_struct(pPng);
87  if (!pInfo)
88  {
89  png_destroy_read_struct(&pPng, nullptr, nullptr);
90  return false;
91  }
92 
93  PngDestructor pngDestructor = { pPng, pInfo };
94 
95  // All variables holding resources need to be declared here in order to be
96  // properly cleaned up in case of an error, otherwise libpng's longjmp()
97  // jumps over the destructor calls.
98  Bitmap aBitmap;
99  AlphaMask aBitmapAlpha;
100  Size prefSize;
101  BitmapScopedWriteAccess pWriteAccessInstance;
102  AlphaScopedWriteAccess pWriteAccessAlphaInstance;
103  std::vector<std::vector<png_byte>> aRows;
104  const bool bFuzzing = utl::ConfigManager::IsFuzzing();
105  const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32();
106  const bool bOnlyCreateBitmap
107  = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
108  const bool bUseExistingBitmap
109  = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
110 
111  if (setjmp(png_jmpbuf(pPng)))
112  {
113  if (!bUseExistingBitmap)
114  {
115  // Set the bitmap if it contains something, even on failure. This allows
116  // reading images that are only partially broken.
117  pWriteAccessInstance.reset();
118  pWriteAccessAlphaInstance.reset();
119  if (!aBitmap.IsEmpty() && !aBitmapAlpha.IsEmpty())
120  rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
121  else if (!aBitmap.IsEmpty())
122  rBitmapEx = BitmapEx(aBitmap);
123  if (!rBitmapEx.IsEmpty() && !prefSize.IsEmpty())
124  {
125  rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
126  rBitmapEx.SetPrefSize(prefSize);
127  }
128  }
129  return false;
130  }
131 
132  png_set_option(pPng, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON);
133 
134  png_set_read_fn(pPng, &rStream, lclReadStream);
135 
136  if (!bFuzzing)
137  png_set_crc_action(pPng, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD);
138  else
139  png_set_crc_action(pPng, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
140 
141  png_set_sig_bytes(pPng, PNG_SIGNATURE_SIZE);
142 
143  png_read_info(pPng, pInfo);
144 
145  png_uint_32 width = 0;
146  png_uint_32 height = 0;
147  int bitDepth = 0;
148  int colorType = -1;
149  int interlace = -1;
150 
151  png_uint_32 returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType,
152  &interlace, nullptr, nullptr);
153 
154  if (returnValue != 1)
155  return false;
156 
157  if (colorType == PNG_COLOR_TYPE_PALETTE)
158  png_set_palette_to_rgb(pPng);
159 
160  if (colorType == PNG_COLOR_TYPE_GRAY)
161  png_set_expand_gray_1_2_4_to_8(pPng);
162 
163  if (png_get_valid(pPng, pInfo, PNG_INFO_tRNS))
164  png_set_tRNS_to_alpha(pPng);
165 
166  if (bitDepth == 16)
167  png_set_scale_16(pPng);
168 
169  if (bitDepth < 8)
170  png_set_packing(pPng);
171 
172  // Convert gray+alpha to RGBA, keep gray as gray.
173  if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA
174  || (colorType == PNG_COLOR_TYPE_GRAY && png_get_valid(pPng, pInfo, PNG_INFO_tRNS)))
175  {
176  png_set_gray_to_rgb(pPng);
177  }
178 
179  // Sets the filler byte - if RGB it converts to RGBA
180  // png_set_filler(pPng, 0xFF, PNG_FILLER_AFTER);
181 
182  int nNumberOfPasses = png_set_interlace_handling(pPng);
183 
184  png_read_update_info(pPng, pInfo);
185  returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType, nullptr,
186  nullptr, nullptr);
187 
188  if (returnValue != 1)
189  return false;
190 
191  if (bitDepth != 8
192  || (colorType != PNG_COLOR_TYPE_RGB && colorType != PNG_COLOR_TYPE_RGB_ALPHA
193  && colorType != PNG_COLOR_TYPE_GRAY))
194  {
195  return false;
196  }
197 
198  png_uint_32 res_x = 0;
199  png_uint_32 res_y = 0;
200  int unit_type = 0;
201  if (png_get_pHYs(pPng, pInfo, &res_x, &res_y, &unit_type) != 0
202  && unit_type == PNG_RESOLUTION_METER && res_x && res_y)
203  {
204  // convert into MapUnit::Map100thMM
205  prefSize = Size(static_cast<sal_Int32>((100000.0 * width) / res_x),
206  static_cast<sal_Int32>((100000.0 * height) / res_y));
207  }
208 
209  if (!bUseExistingBitmap)
210  {
211  switch (colorType)
212  {
213  case PNG_COLOR_TYPE_RGB:
214  aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP);
215  break;
216  case PNG_COLOR_TYPE_RGBA:
217  if (bSupportsBitmap32)
218  aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP);
219  else
220  {
221  aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP);
222  aBitmapAlpha = AlphaMask(Size(width, height), nullptr);
223  }
224  break;
225  case PNG_COLOR_TYPE_GRAY:
226  aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N8_BPP,
227  &Bitmap::GetGreyPalette(256));
228  break;
229  default:
230  abort();
231  }
232 
233  if (bOnlyCreateBitmap)
234  {
235  if (!aBitmapAlpha.IsEmpty())
236  rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
237  else
238  rBitmapEx = BitmapEx(aBitmap);
239  if (!prefSize.IsEmpty())
240  {
241  rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
242  rBitmapEx.SetPrefSize(prefSize);
243  }
244  return true;
245  }
246 
247  pWriteAccessInstance = BitmapScopedWriteAccess(aBitmap);
248  if (!pWriteAccessInstance)
249  return false;
250  if (!aBitmapAlpha.IsEmpty())
251  {
252  pWriteAccessAlphaInstance = AlphaScopedWriteAccess(aBitmapAlpha);
253  if (!pWriteAccessAlphaInstance)
254  return false;
255  }
256  }
257  BitmapScopedWriteAccess& pWriteAccess = pAccess ? *pAccess : pWriteAccessInstance;
258  AlphaScopedWriteAccess& pWriteAccessAlpha
259  = pAlphaAccess ? *pAlphaAccess : pWriteAccessAlphaInstance;
260 
261  if (colorType == PNG_COLOR_TYPE_RGB)
262  {
263  ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
264  if (eFormat == ScanlineFormat::N24BitTcBgr)
265  png_set_bgr(pPng);
266 
267  for (int pass = 0; pass < nNumberOfPasses; pass++)
268  {
269  for (png_uint_32 y = 0; y < height; y++)
270  {
271  Scanline pScanline = pWriteAccess->GetScanline(y);
272  png_read_row(pPng, pScanline, nullptr);
273  }
274  }
275  }
276  else if (colorType == PNG_COLOR_TYPE_RGB_ALPHA)
277  {
278  size_t aRowSizeBytes = png_get_rowbytes(pPng, pInfo);
279 
280  if (bSupportsBitmap32)
281  {
282  ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
283  if (eFormat == ScanlineFormat::N32BitTcAbgr || eFormat == ScanlineFormat::N32BitTcBgra)
284  png_set_bgr(pPng);
285 
286  for (int pass = 0; pass < nNumberOfPasses; pass++)
287  {
288  for (png_uint_32 y = 0; y < height; y++)
289  {
290  Scanline pScanline = pWriteAccess->GetScanline(y);
291  png_read_row(pPng, pScanline, nullptr);
292  }
293  }
295  if (eFormat == ScanlineFormat::N32BitTcAbgr || eFormat == ScanlineFormat::N32BitTcArgb)
296  { // alpha first and premultiply
297  for (png_uint_32 y = 0; y < height; y++)
298  {
299  Scanline pScanline = pWriteAccess->GetScanline(y);
300  for (size_t i = 0; i < aRowSizeBytes; i += 4)
301  {
302  const sal_uInt8 alpha = pScanline[i + 3];
303  pScanline[i + 3] = premultiply[alpha][pScanline[i + 2]];
304  pScanline[i + 2] = premultiply[alpha][pScanline[i + 1]];
305  pScanline[i + 1] = premultiply[alpha][pScanline[i]];
306  pScanline[i] = alpha;
307  }
308  }
309  }
310  else
311  { // keep alpha last, only premultiply
312  for (png_uint_32 y = 0; y < height; y++)
313  {
314  Scanline pScanline = pWriteAccess->GetScanline(y);
315  for (size_t i = 0; i < aRowSizeBytes; i += 4)
316  {
317  const sal_uInt8 alpha = pScanline[i + 3];
318  pScanline[i] = premultiply[alpha][pScanline[i]];
319  pScanline[i + 1] = premultiply[alpha][pScanline[i + 1]];
320  pScanline[i + 2] = premultiply[alpha][pScanline[i + 2]];
321  }
322  }
323  }
324  }
325  else
326  {
327  ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
328  if (eFormat == ScanlineFormat::N24BitTcBgr)
329  png_set_bgr(pPng);
330 
331  aRows = std::vector<std::vector<png_byte>>(height);
332  for (auto& rRow : aRows)
333  rRow.resize(aRowSizeBytes, 0);
334 
335  for (int pass = 0; pass < nNumberOfPasses; pass++)
336  {
337  for (png_uint_32 y = 0; y < height; y++)
338  {
339  Scanline pScanline = pWriteAccess->GetScanline(y);
340  Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y);
341  png_bytep pRow = aRows[y].data();
342  png_read_row(pPng, pRow, nullptr);
343  size_t iAlpha = 0;
344  size_t iColor = 0;
345  for (size_t i = 0; i < aRowSizeBytes; i += 4)
346  {
347  pScanline[iColor++] = pRow[i + 0];
348  pScanline[iColor++] = pRow[i + 1];
349  pScanline[iColor++] = pRow[i + 2];
350  pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3];
351  }
352  }
353  }
354  }
355  }
356  else if (colorType == PNG_COLOR_TYPE_GRAY)
357  {
358  for (int pass = 0; pass < nNumberOfPasses; pass++)
359  {
360  for (png_uint_32 y = 0; y < height; y++)
361  {
362  Scanline pScanline = pWriteAccess->GetScanline(y);
363  png_read_row(pPng, pScanline, nullptr);
364  }
365  }
366  }
367 
368  png_read_end(pPng, pInfo);
369 
370  if (!bUseExistingBitmap)
371  {
372  pWriteAccess.reset();
373  pWriteAccessAlpha.reset();
374  if (!aBitmapAlpha.IsEmpty())
375  rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
376  else
377  rBitmapEx = BitmapEx(aBitmap);
378  if (!prefSize.IsEmpty())
379  {
380  rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
381  rBitmapEx.SetPrefSize(prefSize);
382  }
383  }
384 
385  return true;
386 }
387 
388 std::unique_ptr<sal_uInt8[]> getMsGifChunk(SvStream& rStream, sal_Int32* chunkSize)
389 {
390  if (chunkSize)
391  *chunkSize = 0;
392  if (!isPng(rStream))
393  return nullptr;
394  // It's easier to read manually the contents and find the chunk than
395  // try to get it using libpng.
396  // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_format
397  // Each chunk is: 4 bytes length, 4 bytes type, <length> bytes, 4 bytes crc
398  bool ignoreCrc = utl::ConfigManager::IsFuzzing();
399  for (;;)
400  {
401  sal_uInt32 length, type, crc;
402  rStream.ReadUInt32(length);
403  rStream.ReadUInt32(type);
404  if (!rStream.good())
405  return nullptr;
406  constexpr sal_uInt32 PNGCHUNK_msOG = 0x6d734f47; // Microsoft Office Animated GIF
407  constexpr sal_uInt64 MSGifHeaderSize = 11; // "MSOFFICE9.0"
408  if (type == PNGCHUNK_msOG && length > MSGifHeaderSize)
409  {
410  // calculate chunktype CRC (swap it back to original byte order)
411  sal_uInt32 typeForCrc = type;
412 #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
413  typeForCrc = OSL_SWAPDWORD(typeForCrc);
414 #endif
415  sal_uInt32 computedCrc = rtl_crc32(0, &typeForCrc, 4);
416  const sal_uInt64 pos = rStream.Tell();
417  if (pos + length >= rStream.TellEnd())
418  return nullptr; // broken PNG
419 
420  char msHeader[MSGifHeaderSize];
421  if (rStream.ReadBytes(msHeader, MSGifHeaderSize) != MSGifHeaderSize)
422  return nullptr;
423  computedCrc = rtl_crc32(computedCrc, msHeader, MSGifHeaderSize);
424  length -= MSGifHeaderSize;
425 
426  std::unique_ptr<sal_uInt8[]> chunk(new sal_uInt8[length]);
427  if (rStream.ReadBytes(chunk.get(), length) != length)
428  return nullptr;
429  computedCrc = rtl_crc32(computedCrc, chunk.get(), length);
430  rStream.ReadUInt32(crc);
431  if (!ignoreCrc && crc != computedCrc)
432  continue; // invalid chunk, ignore
433  if (chunkSize)
434  *chunkSize = length;
435  return chunk;
436  }
437  if (rStream.remainingSize() < length)
438  return nullptr;
439  rStream.SeekRel(length);
440  rStream.ReadUInt32(crc);
441  constexpr sal_uInt32 PNGCHUNK_IEND = 0x49454e44;
442  if (type == PNGCHUNK_IEND)
443  return nullptr;
444  }
445 }
446 #if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__
447 #pragma GCC diagnostic pop
448 #endif
449 
450 } // anonymous namespace
451 
452 namespace vcl
453 {
455  : mrStream(rStream)
456 {
457 }
458 
459 bool PngImageReader::read(BitmapEx& rBitmapEx) { return reader(mrStream, rBitmapEx); }
460 
462 {
463  BitmapEx bitmap;
464  read(bitmap);
465  return bitmap;
466 }
467 
468 std::unique_ptr<sal_uInt8[]> PngImageReader::getMicrosoftGifChunk(SvStream& rStream,
469  sal_Int32* chunkSize)
470 {
471  sal_uInt64 originalPosition = rStream.Tell();
472  SvStreamEndian originalEndian = rStream.GetEndian();
473  rStream.SetEndian(SvStreamEndian::BIG);
474  std::unique_ptr<sal_uInt8[]> chunk = getMsGifChunk(rStream, chunkSize);
475  rStream.SetEndian(originalEndian);
476  rStream.Seek(originalPosition);
477  return chunk;
478 }
479 
480 bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags,
481  BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess)
482 {
483  // Creating empty bitmaps should be practically a no-op, and thus thread-safe.
484  BitmapEx bitmap;
485  if (reader(rInputStream, bitmap, nImportFlags, pAccess, pAlphaAccess))
486  {
487  if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap))
488  rGraphic = bitmap;
489  return true;
490  }
491  return false;
492 }
493 
494 } // namespace vcl
495 
496 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a)
Only create a bitmap, do not read pixel data.
Read pixel data into an existing bitmap.
virtual sal_uInt64 TellEnd()
void SetPrefMapMode(const MapMode &rPrefMapMode)
Definition: bitmapex.hxx:80
sal_uInt64 Seek(sal_uInt64 nPos)
std::array< std::array< sal_uInt8, 256 >, 256 > lookup_table
Definition: BitmapTools.hxx:29
This template handles BitmapAccess the RAII way.
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap,&Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
sal_uInt64 SeekRel(sal_Int64 nPos)
ScanlineFormat
Definition: Scanline.hxx:29
size_t pos
static bool IsFuzzing()
bool ImportPNG(SvStream &rInputStream, Graphic &rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess *pAccess, AlphaScopedWriteAccess *pAlphaAccess)
sal_uInt64 remainingSize()
float y
SvStream & ReadUInt32(sal_uInt32 &rUInt32)
bool IsEmpty() const
Definition: BitmapEx.cxx:177
ImplSVData * ImplGetSVData()
Definition: svdata.cxx:75
#define PNGCHUNK_IEND
Definition: pngwrite.cxx:41
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
bool IsEmpty() const
int i
static std::unique_ptr< sal_uInt8[]> getMicrosoftGifChunk(SvStream &rStream, sal_Int32 *chunkSize=nullptr)
pass
bool supportsBitmap32() const
Definition: salinst.hxx:89
std::size_t ReadBytes(void *pData, std::size_t nSize)
SvStreamEndian GetEndian() const
vcl::ScopedBitmapAccess< BitmapWriteAccess, AlphaMask,&AlphaMask::AcquireAlphaWriteAccess > AlphaScopedWriteAccess
unsigned char sal_uInt8
lookup_table const & get_premultiply_table()
void SetEndian(SvStreamEndian SvStreamEndian)
void SetPrefSize(const Size &rPrefSize)
Definition: bitmapex.hxx:77
bool IsEmpty() const
sal_uInt64 Tell() const
bool good() const
ResultType type
SvStreamEndian
GraphicFilterImportFlags
PngImageReader(SvStream &rStream)
SalInstance * mpDefInst
Definition: svdata.hxx:385
static const BitmapPalette & GetGreyPalette(int nEntries)