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>
19
21#include <svdata.hxx>
22#include <salinst.hxx>
23
24#include "png.hxx"
25
26namespace
27{
28void 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
52constexpr int PNG_SIGNATURE_SIZE = 8;
53
54bool 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
63struct 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
74bool 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 const bool bFuzzing = utl::ConfigManager::IsFuzzing();
104 const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32();
105 const bool bOnlyCreateBitmap
106 = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
107 const bool bUseExistingBitmap
108 = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
109
110 if (setjmp(png_jmpbuf(pPng)))
111 {
112 if (!bUseExistingBitmap)
113 {
114 // Set the bitmap if it contains something, even on failure. This allows
115 // reading images that are only partially broken.
116 pWriteAccessInstance.reset();
117 pWriteAccessAlphaInstance.reset();
118 if (!aBitmap.IsEmpty() && !aBitmapAlpha.IsEmpty())
119 rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
120 else if (!aBitmap.IsEmpty())
121 rBitmapEx = BitmapEx(aBitmap);
122 if (!rBitmapEx.IsEmpty() && !prefSize.IsEmpty())
123 {
124 rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
125 rBitmapEx.SetPrefSize(prefSize);
126 }
127 }
128 return false;
129 }
130
131 png_set_option(pPng, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON);
132
133 png_set_read_fn(pPng, &rStream, lclReadStream);
134
135 if (!bFuzzing)
136 png_set_crc_action(pPng, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD);
137 else
138 png_set_crc_action(pPng, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
139
140 png_set_sig_bytes(pPng, PNG_SIGNATURE_SIZE);
141
142 png_read_info(pPng, pInfo);
143
144 png_uint_32 width = 0;
145 png_uint_32 height = 0;
146 int bitDepth = 0;
147 int colorType = -1;
148 int interlace = -1;
149
150 png_uint_32 returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType,
151 &interlace, nullptr, nullptr);
152
153 if (returnValue != 1)
154 return false;
155
156 if (colorType == PNG_COLOR_TYPE_PALETTE)
157 png_set_palette_to_rgb(pPng);
158
159 if (colorType == PNG_COLOR_TYPE_GRAY)
160 png_set_expand_gray_1_2_4_to_8(pPng);
161
162 if (png_get_valid(pPng, pInfo, PNG_INFO_tRNS))
163 png_set_tRNS_to_alpha(pPng);
164
165 if (bitDepth == 16)
166 png_set_scale_16(pPng);
167
168 if (bitDepth < 8)
169 png_set_packing(pPng);
170
171 // Convert gray+alpha to RGBA, keep gray as gray.
172 if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA
173 || (colorType == PNG_COLOR_TYPE_GRAY && png_get_valid(pPng, pInfo, PNG_INFO_tRNS)))
174 {
175 png_set_gray_to_rgb(pPng);
176 }
177
178 // Sets the filler byte - if RGB it converts to RGBA
179 // png_set_filler(pPng, 0xFF, PNG_FILLER_AFTER);
180
181 int nNumberOfPasses = png_set_interlace_handling(pPng);
182
183 png_read_update_info(pPng, pInfo);
184 returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType, nullptr,
185 nullptr, nullptr);
186
187 if (returnValue != 1)
188 return false;
189
190 if (bitDepth != 8
191 || (colorType != PNG_COLOR_TYPE_RGB && colorType != PNG_COLOR_TYPE_RGB_ALPHA
192 && colorType != PNG_COLOR_TYPE_GRAY))
193 {
194 return false;
195 }
196
197 png_uint_32 res_x = 0;
198 png_uint_32 res_y = 0;
199 int unit_type = 0;
200 if (png_get_pHYs(pPng, pInfo, &res_x, &res_y, &unit_type) != 0
201 && unit_type == PNG_RESOLUTION_METER && res_x && res_y)
202 {
203 // convert into MapUnit::Map100thMM
204 prefSize = Size(static_cast<sal_Int32>((100000.0 * width) / res_x),
205 static_cast<sal_Int32>((100000.0 * height) / res_y));
206 }
207
208 if (!bUseExistingBitmap)
209 {
210 switch (colorType)
211 {
212 case PNG_COLOR_TYPE_RGB:
213 aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP);
214 break;
215 case PNG_COLOR_TYPE_RGBA:
216 if (bSupportsBitmap32)
217 aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP);
218 else
219 {
220 aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP);
221 aBitmapAlpha = AlphaMask(Size(width, height), nullptr);
222 }
223 break;
224 case PNG_COLOR_TYPE_GRAY:
225 aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N8_BPP,
227 break;
228 default:
229 abort();
230 }
231
232 if (bOnlyCreateBitmap)
233 {
234 if (!aBitmapAlpha.IsEmpty())
235 rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
236 else
237 rBitmapEx = BitmapEx(aBitmap);
238 if (!prefSize.IsEmpty())
239 {
240 rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
241 rBitmapEx.SetPrefSize(prefSize);
242 }
243 return true;
244 }
245
246 pWriteAccessInstance = BitmapScopedWriteAccess(aBitmap);
247 if (!pWriteAccessInstance)
248 return false;
249 if (!aBitmapAlpha.IsEmpty())
250 {
251 pWriteAccessAlphaInstance = AlphaScopedWriteAccess(aBitmapAlpha);
252 if (!pWriteAccessAlphaInstance)
253 return false;
254 }
255 }
256 BitmapScopedWriteAccess& pWriteAccess = pAccess ? *pAccess : pWriteAccessInstance;
257 AlphaScopedWriteAccess& pWriteAccessAlpha
258 = pAlphaAccess ? *pAlphaAccess : pWriteAccessAlphaInstance;
259
260 if (colorType == PNG_COLOR_TYPE_RGB)
261 {
262 ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
263 if (eFormat == ScanlineFormat::N24BitTcBgr)
264 png_set_bgr(pPng);
265
266 for (int pass = 0; pass < nNumberOfPasses; pass++)
267 {
268 for (png_uint_32 y = 0; y < height; y++)
269 {
270 Scanline pScanline = pWriteAccess->GetScanline(y);
271 png_read_row(pPng, pScanline, nullptr);
272 }
273 }
274 }
275 else if (colorType == PNG_COLOR_TYPE_RGB_ALPHA)
276 {
277 size_t aRowSizeBytes = png_get_rowbytes(pPng, pInfo);
278
279 if (bSupportsBitmap32)
280 {
281 ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
283 png_set_bgr(pPng);
284
285 for (int pass = 0; pass < nNumberOfPasses; pass++)
286 {
287 for (png_uint_32 y = 0; y < height; y++)
288 {
289 Scanline pScanline = pWriteAccess->GetScanline(y);
290 png_read_row(pPng, pScanline, nullptr);
291 }
292 }
293#if !ENABLE_WASM_STRIP_PREMULTIPLY
295#endif
297 { // alpha first and premultiply
298 for (png_uint_32 y = 0; y < height; y++)
299 {
300 Scanline pScanline = pWriteAccess->GetScanline(y);
301 for (size_t i = 0; i < aRowSizeBytes; i += 4)
302 {
303 const sal_uInt8 alpha = pScanline[i + 3];
304#if ENABLE_WASM_STRIP_PREMULTIPLY
305 pScanline[i + 3] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]);
306 pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]);
307 pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i]);
308#else
309 pScanline[i + 3] = premultiply[alpha][pScanline[i + 2]];
310 pScanline[i + 2] = premultiply[alpha][pScanline[i + 1]];
311 pScanline[i + 1] = premultiply[alpha][pScanline[i]];
312#endif
313 pScanline[i] = alpha;
314 }
315 }
316 }
317 else
318 { // keep alpha last, only premultiply
319 for (png_uint_32 y = 0; y < height; y++)
320 {
321 Scanline pScanline = pWriteAccess->GetScanline(y);
322 for (size_t i = 0; i < aRowSizeBytes; i += 4)
323 {
324 const sal_uInt8 alpha = pScanline[i + 3];
325#if ENABLE_WASM_STRIP_PREMULTIPLY
326 pScanline[i] = vcl::bitmap::premultiply(alpha, pScanline[i]);
327 pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]);
328 pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]);
329#else
330 pScanline[i] = premultiply[alpha][pScanline[i]];
331 pScanline[i + 1] = premultiply[alpha][pScanline[i + 1]];
332 pScanline[i + 2] = premultiply[alpha][pScanline[i + 2]];
333#endif
334 }
335 }
336 }
337 }
338 else
339 {
340 ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
341 if (eFormat == ScanlineFormat::N24BitTcBgr)
342 png_set_bgr(pPng);
343
344 if (nNumberOfPasses == 1)
345 {
346 // optimise the common case, where we can use a buffer of only a single row
347 std::vector<png_byte> aRow(aRowSizeBytes, 0);
348 for (png_uint_32 y = 0; y < height; y++)
349 {
350 Scanline pScanline = pWriteAccess->GetScanline(y);
351 Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y);
352 png_bytep pRow = aRow.data();
353 png_read_row(pPng, pRow, nullptr);
354 size_t iAlpha = 0;
355 size_t iColor = 0;
356 for (size_t i = 0; i < aRowSizeBytes; i += 4)
357 {
358 pScanline[iColor++] = pRow[i + 0];
359 pScanline[iColor++] = pRow[i + 1];
360 pScanline[iColor++] = pRow[i + 2];
361 pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3];
362 }
363 }
364 }
365 else
366 {
367 std::vector<std::vector<png_byte>> aRows(height);
368 for (auto& rRow : aRows)
369 rRow.resize(aRowSizeBytes, 0);
370 for (int pass = 0; pass < nNumberOfPasses; pass++)
371 {
372 for (png_uint_32 y = 0; y < height; y++)
373 {
374 Scanline pScanline = pWriteAccess->GetScanline(y);
375 Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y);
376 png_bytep pRow = aRows[y].data();
377 png_read_row(pPng, pRow, nullptr);
378 size_t iAlpha = 0;
379 size_t iColor = 0;
380 for (size_t i = 0; i < aRowSizeBytes; i += 4)
381 {
382 pScanline[iColor++] = pRow[i + 0];
383 pScanline[iColor++] = pRow[i + 1];
384 pScanline[iColor++] = pRow[i + 2];
385 pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3];
386 }
387 }
388 }
389 }
390 }
391 }
392 else if (colorType == PNG_COLOR_TYPE_GRAY)
393 {
394 for (int pass = 0; pass < nNumberOfPasses; pass++)
395 {
396 for (png_uint_32 y = 0; y < height; y++)
397 {
398 Scanline pScanline = pWriteAccess->GetScanline(y);
399 png_read_row(pPng, pScanline, nullptr);
400 }
401 }
402 }
403
404 png_read_end(pPng, pInfo);
405
406 if (!bUseExistingBitmap)
407 {
408 pWriteAccess.reset();
409 pWriteAccessAlpha.reset();
410 if (!aBitmapAlpha.IsEmpty())
411 rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
412 else
413 rBitmapEx = BitmapEx(aBitmap);
414 if (!prefSize.IsEmpty())
415 {
416 rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
417 rBitmapEx.SetPrefSize(prefSize);
418 }
419 }
420
421 return true;
422}
423
424BinaryDataContainer getMsGifChunk(SvStream& rStream)
425{
426 if (!isPng(rStream))
427 return {};
428 // It's easier to read manually the contents and find the chunk than
429 // try to get it using libpng.
430 // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_format
431 // Each chunk is: 4 bytes length, 4 bytes type, <length> bytes, 4 bytes crc
432 bool ignoreCrc = utl::ConfigManager::IsFuzzing();
433 for (;;)
434 {
435 sal_uInt32 length(0), type(0), crc(0);
436 rStream.ReadUInt32(length);
437 rStream.ReadUInt32(type);
438 if (!rStream.good())
439 return {};
440 constexpr sal_uInt32 PNGCHUNK_msOG = 0x6d734f47; // Microsoft Office Animated GIF
441 constexpr sal_uInt64 MSGifHeaderSize = 11; // "MSOFFICE9.0"
442 if (type == PNGCHUNK_msOG && length > MSGifHeaderSize)
443 {
444 // calculate chunktype CRC (swap it back to original byte order)
445 sal_uInt32 typeForCrc = type;
446#if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
447 typeForCrc = OSL_SWAPDWORD(typeForCrc);
448#endif
449 sal_uInt32 computedCrc = rtl_crc32(0, &typeForCrc, 4);
450 const sal_uInt64 pos = rStream.Tell();
451 if (pos + length >= rStream.TellEnd())
452 return {}; // broken PNG
453
454 char msHeader[MSGifHeaderSize];
455 if (rStream.ReadBytes(msHeader, MSGifHeaderSize) != MSGifHeaderSize)
456 return {};
457 computedCrc = rtl_crc32(computedCrc, msHeader, MSGifHeaderSize);
458 length -= MSGifHeaderSize;
459
460 BinaryDataContainer chunk(rStream, length);
461 if (chunk.isEmpty())
462 return {};
463 computedCrc = rtl_crc32(computedCrc, chunk.getData(), chunk.getSize());
464 rStream.ReadUInt32(crc);
465 if (!ignoreCrc && crc != computedCrc)
466 continue; // invalid chunk, ignore
467 return chunk;
468 }
469 if (rStream.remainingSize() < length)
470 return {};
471 rStream.SeekRel(length);
472 rStream.ReadUInt32(crc);
473 constexpr sal_uInt32 PNGCHUNK_IEND = 0x49454e44;
474 if (type == PNGCHUNK_IEND)
475 return {};
476 }
477}
478#if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__
479#pragma GCC diagnostic pop
480#endif
481
482} // anonymous namespace
483
484namespace vcl
485{
487 : mrStream(rStream)
488{
489}
490
491bool PngImageReader::read(BitmapEx& rBitmapEx) { return reader(mrStream, rBitmapEx); }
492
494{
495 BitmapEx bitmap;
496 read(bitmap);
497 return bitmap;
498}
499
501{
502 sal_uInt64 originalPosition = rStream.Tell();
503 SvStreamEndian originalEndian = rStream.GetEndian();
504 rStream.SetEndian(SvStreamEndian::BIG);
505 auto chunk = getMsGifChunk(rStream);
506 rStream.SetEndian(originalEndian);
507 rStream.Seek(originalPosition);
508 return chunk;
509}
510
511bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags,
512 BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess)
513{
514 // Creating empty bitmaps should be practically a no-op, and thus thread-safe.
515 BitmapEx bitmap;
516 if (reader(rInputStream, bitmap, nImportFlags, pAccess, pAlphaAccess))
517 {
519 rGraphic = bitmap;
520 return true;
521 }
522 return false;
523}
524
525} // namespace vcl
526
527/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
vcl::ScopedBitmapAccess< BitmapWriteAccess, AlphaMask, &AlphaMask::AcquireAlphaWriteAccess > AlphaScopedWriteAccess
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap, &Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
ScanlineFormat
Definition: Scanline.hxx:29
bool IsEmpty() const
Container for the binary data, whose responsibility is to manage the make it as simple as possible to...
void SetPrefMapMode(const MapMode &rPrefMapMode)
Definition: bitmapex.hxx:79
void SetPrefSize(const Size &rPrefSize)
Definition: bitmapex.hxx:76
bool IsEmpty() const
Definition: BitmapEx.cxx:177
static const BitmapPalette & GetGreyPalette(int nEntries)
bool IsEmpty() const
bool supportsBitmap32() const
Definition: salinst.hxx:90
bool IsEmpty() const
sal_uInt64 Tell() const
void SetEndian(SvStreamEndian SvStreamEndian)
bool good() const
virtual sal_uInt64 TellEnd()
SvStream & ReadUInt32(sal_uInt32 &rUInt32)
SvStreamEndian GetEndian() const
sal_uInt64 Seek(sal_uInt64 nPos)
std::size_t ReadBytes(void *pData, std::size_t nSize)
sal_uInt64 SeekRel(sal_Int64 nPos)
sal_uInt64 remainingSize()
static bool IsFuzzing()
PngImageReader(SvStream &rStream)
static BinaryDataContainer getMicrosoftGifChunk(SvStream &rStream)
This template handles BitmapAccess the RAII way.
Reference< XInputStream > rInputStream
float y
GraphicFilterImportFlags
@ UseExistingBitmap
Read pixel data into an existing bitmap.
@ OnlyCreateBitmap
Only create a bitmap, do not read pixel data.
int i
pass
sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a)
lookup_table const & get_premultiply_table()
std::array< std::array< sal_uInt8, 256 >, 256 > lookup_table
Definition: BitmapTools.hxx:32
bool ImportPNG(SvStream &rInputStream, Graphic &rGraphic, GraphicFilterImportFlags nImportFlags, BitmapScopedWriteAccess *pAccess, AlphaScopedWriteAccess *pAlphaAccess)
SvStreamEndian
SalInstance * mpDefInst
Definition: svdata.hxx:391
ImplSVData * ImplGetSVData()
Definition: svdata.cxx:77
unsigned char sal_uInt8
ResultType type
size_t pos