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>
20#include <osl/endian.h>
21
23#include <svdata.hxx>
24#include <salinst.hxx>
25
26#include "png.hxx"
27
28namespace
29{
30void lclReadStream(png_structp pPng, png_bytep pOutBytes, png_size_t nBytesToRead)
31{
32 png_voidp pIO = png_get_io_ptr(pPng);
33
34 if (pIO == nullptr)
35 return;
36
37 SvStream* pStream = static_cast<SvStream*>(pIO);
38
39 sal_Size nBytesRead = pStream->ReadBytes(pOutBytes, nBytesToRead);
40
41 if (nBytesRead != nBytesToRead)
42 {
43 if (!nBytesRead)
44 png_error(pPng, "Error reading");
45 else
46 {
47 // Make sure to not reuse old data (could cause infinite loop).
48 memset(pOutBytes + nBytesRead, 0, nBytesToRead - nBytesRead);
49 png_warning(pPng, "Short read");
50 }
51 }
52}
53
54bool isPng(SvStream& rStream)
55{
56 // Check signature bytes
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
71struct acTLChunk
72{
73 sal_uInt32 num_frames;
74 sal_uInt32 num_plays;
75};
76
78struct FrameDataChunk
79{
80 sal_uInt32 sequence_number;
81 virtual ~FrameDataChunk() = default;
82};
83
85struct fcTLChunk : public FrameDataChunk
86{
87 sal_uInt32 width;
88 sal_uInt32 height;
89 sal_uInt32 x_offset;
90 sal_uInt32 y_offset;
91 sal_uInt16 delay_num;
92 sal_uInt16 delay_den;
93 sal_uInt8 dispose_op;
94 sal_uInt8 blend_op;
95};
96
98struct fdATChunk : public FrameDataChunk
99{
100 std::vector<sal_uInt8> frame_data;
101};
102
104struct APNGInfo
105{
106 bool mbIsApng = false;
107 acTLChunk maACTLChunk;
108 std::vector<std::unique_ptr<FrameDataChunk>> maFrameData;
109};
110
111int handle_unknown_chunk(png_structp png, png_unknown_chunkp chunk)
112{
113 std::string sName(chunk->name, chunk->name + 4);
114 APNGInfo* aAPNGInfo = static_cast<APNGInfo*>(png_get_user_chunk_ptr(png));
115 if (sName == "acTL")
116 {
117 if (chunk->size < sizeof(acTLChunk))
118 return -1;
119 aAPNGInfo->maACTLChunk = *reinterpret_cast<acTLChunk*>(chunk->data);
120 aAPNGInfo->maACTLChunk.num_frames = OSL_SWAPDWORD(aAPNGInfo->maACTLChunk.num_frames);
121 aAPNGInfo->maACTLChunk.num_plays = OSL_SWAPDWORD(aAPNGInfo->maACTLChunk.num_plays);
122 aAPNGInfo->mbIsApng = true;
123 }
124 else
125 {
126 std::unique_ptr<FrameDataChunk> pBaseChunk;
127
128 if (sName == "fcTL")
129 {
130 // Can't check with sizeof(fcTLChunk) because it may not be packed
131 if (chunk->size != 26)
132 {
133 return -1;
134 }
135
136 // byte
137 // 0 sequence_number (unsigned int) Sequence number of the animation chunk, starting from 0
138 // 4 width (unsigned int) Width of the following frame
139 // 8 height (unsigned int) Height of the following frame
140 // 12 x_offset (unsigned int) X position at which to render the following frame
141 // 16 y_offset (unsigned int) Y position at which to render the following frame
142 // 20 delay_num (unsigned short) Frame delay fraction numerator
143 // 22 delay_den (unsigned short) Frame delay fraction denominator
144 // 24 dispose_op (byte) Type of frame area disposal to be done after rendering this frame
145 // 25 blend_op (byte) Type of frame area rendering for this frame
146
147 // memcpy each member instead of reinterpret_cast because struct may not be packed
148 std::unique_ptr<fcTLChunk> aChunk = std::make_unique<fcTLChunk>();
149 std::memcpy(&aChunk->width, chunk->data + 4, 4);
150 std::memcpy(&aChunk->height, chunk->data + 8, 4);
151 std::memcpy(&aChunk->x_offset, chunk->data + 12, 4);
152 std::memcpy(&aChunk->y_offset, chunk->data + 16, 4);
153 std::memcpy(&aChunk->delay_num, chunk->data + 20, 2);
154 std::memcpy(&aChunk->delay_den, chunk->data + 22, 2);
155 std::memcpy(&aChunk->dispose_op, chunk->data + 24, 1);
156 std::memcpy(&aChunk->blend_op, chunk->data + 25, 1);
157 aChunk->width = OSL_SWAPDWORD(aChunk->width);
158 aChunk->height = OSL_SWAPDWORD(aChunk->height);
159 aChunk->x_offset = OSL_SWAPDWORD(aChunk->x_offset);
160 aChunk->y_offset = OSL_SWAPDWORD(aChunk->y_offset);
161 aChunk->delay_num = OSL_SWAPWORD(aChunk->delay_num);
162 aChunk->delay_den = OSL_SWAPWORD(aChunk->delay_den);
163 pBaseChunk = std::move(aChunk);
164 }
165 else if (sName == "fdAT")
166 {
167 size_t nDataSize = chunk->size;
168 if (nDataSize < 4)
169 return -1;
170
171 std::unique_ptr<fdATChunk> aChunk = std::make_unique<fdATChunk>();
172 aChunk->frame_data.resize(nDataSize);
173 // Replace sequence number with the IDAT signature
174 sal_uInt32 nIDATSwapped = OSL_SWAPDWORD(PNG_IDAT_SIGNATURE);
175 std::memcpy(aChunk->frame_data.data(), &nIDATSwapped, 4);
176 // Skip sequence number when copying
177 std::memcpy(aChunk->frame_data.data() + 4, chunk->data + 4, nDataSize - 4);
178 pBaseChunk = std::move(aChunk);
179 }
180 else
181 {
182 // Unknown ancillary chunk
183 return 0;
184 }
185
186 sal_uInt32 nSequenceNumber = 0;
187 std::memcpy(&nSequenceNumber, chunk->data, 4);
188 nSequenceNumber = OSL_SWAPDWORD(nSequenceNumber);
189
190 pBaseChunk->sequence_number = nSequenceNumber;
191 if (pBaseChunk->sequence_number < aAPNGInfo->maFrameData.size())
192 {
193 // Make sure chunks are ordered based on their sequence number because the
194 // png specification does not impose ordering restrictions on ancillary chunks
195 aAPNGInfo->maFrameData.insert(aAPNGInfo->maFrameData.begin()
196 + pBaseChunk->sequence_number,
197 std::move(pBaseChunk));
198 }
199 else
200 {
201 aAPNGInfo->maFrameData.push_back(std::move(pBaseChunk));
202 }
203 }
204 return 1;
205}
206
208void getImportantChunks(SvStream& rInStream, SvStream& rOutStream, sal_uInt32 nWidth,
209 sal_uInt32 nHeight)
210{
211 sal_uInt64 nPos = rInStream.Tell();
212 sal_uInt32 nChunkSize, nChunkType;
213 rInStream.SetEndian(SvStreamEndian::BIG);
214 rOutStream.SetEndian(SvStreamEndian::BIG);
215 rOutStream.WriteUInt64(PNG_SIGNATURE);
216 rOutStream.WriteUInt32(PNG_IHDR_SIZE);
218 rOutStream.WriteUInt32(nWidth);
219 rOutStream.WriteUInt32(nHeight);
220 rInStream.Seek(rOutStream.Tell());
221 sal_uInt32 nIHDRData1;
222 sal_uInt8 nIHDRData2;
223 rInStream.ReadUInt32(nIHDRData1);
224 rInStream.ReadUChar(nIHDRData2);
225 rOutStream.WriteUInt32(nIHDRData1);
226 rOutStream.WriteUChar(nIHDRData2);
227 rOutStream.SeekRel(-PNG_IHDR_SIZE - PNG_CRC_SIZE);
228 std::vector<uint8_t> aIHDRData(PNG_IHDR_SIZE + PNG_CRC_SIZE);
229 rOutStream.ReadBytes(aIHDRData.data(), aIHDRData.size());
230 rOutStream.WriteUInt32(rtl_crc32(0, aIHDRData.data(), aIHDRData.size()));
232 + PNG_CRC_SIZE);
233 while (rInStream.good())
234 {
235 rInStream.ReadUInt32(nChunkSize);
236 rInStream.ReadUInt32(nChunkType);
237 bool bBreakOuter = false;
238 switch (nChunkType)
239 {
243 {
244 // skip apng chunks
245 rInStream.SeekRel(nChunkSize + PNG_CRC_SIZE);
246 continue;
247 }
249 {
250 // IDAT chunk hit, no more important png chunks
251 bBreakOuter = true;
252 break;
253 }
254 default:
255 {
256 // Seek back to start of chunk
257 rInStream.SeekRel(-PNG_TYPE_SIZE - PNG_SIZE_SIZE);
258 // Copy chunk to rOutStream
259 std::vector<uint8_t> aData(nChunkSize + PNG_TYPE_SIZE + PNG_SIZE_SIZE
260 + PNG_CRC_SIZE);
261 rInStream.ReadBytes(aData.data(),
262 PNG_TYPE_SIZE + PNG_SIZE_SIZE + nChunkSize + PNG_CRC_SIZE);
263 rOutStream.WriteBytes(aData.data(),
264 PNG_TYPE_SIZE + PNG_SIZE_SIZE + nChunkSize + PNG_CRC_SIZE);
265 break;
266 }
267 }
268 if (bBreakOuter)
269 {
270 break;
271 }
272 }
273 rInStream.Seek(nPos);
274}
275
276sal_uInt32 NumDenToTime(sal_uInt16 nNumerator, sal_uInt16 nDenominator)
277{
278 if (nDenominator == 0)
279 nDenominator = 100;
280 return (static_cast<double>(nNumerator) / nDenominator) * 100;
281}
282
283bool fcTLbeforeIDAT(SvStream& rStream)
284{
285 sal_uInt64 nPos = rStream.Tell();
286 comphelper::ScopeGuard aGuard([&rStream, nPos]() { rStream.Seek(nPos); });
287 // Skip PNG header and IHDR
288 rStream.SetEndian(SvStreamEndian::BIG);
290 sal_uInt32 nChunkSize, nChunkType;
291 while (rStream.good())
292 {
293 rStream.ReadUInt32(nChunkSize);
294 rStream.ReadUInt32(nChunkType);
295 switch (nChunkType)
296 {
298 return true;
300 return false;
301 default:
302 {
303 rStream.SeekRel(nChunkSize + PNG_CRC_SIZE);
304 break;
305 }
306 }
307 }
308 return false;
309}
310
311#if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__
312#pragma GCC diagnostic push
313#pragma GCC diagnostic ignored "-Wclobbered"
314#endif
315bool reader(SvStream& rStream, Graphic& rGraphic,
317 BitmapScopedWriteAccess* pAccess = nullptr,
318 AlphaScopedWriteAccess* pAlphaAccess = nullptr)
319{
320 if (!isPng(rStream))
321 return false;
322
323 png_structp pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
324 if (!pPng)
325 return false;
326
327 APNGInfo aAPNGInfo;
328 png_set_read_user_chunk_fn(pPng, &aAPNGInfo, &handle_unknown_chunk);
329
330 png_infop pInfo = png_create_info_struct(pPng);
331 if (!pInfo)
332 {
333 png_destroy_read_struct(&pPng, nullptr, nullptr);
334 return false;
335 }
336
337 PngDestructor pngDestructor = { pPng, pInfo };
338
339 // All variables holding resources need to be declared here in order to be
340 // properly cleaned up in case of an error, otherwise libpng's longjmp()
341 // jumps over the destructor calls.
342 BitmapEx aBitmapEx;
343 Bitmap aBitmap;
344 AlphaMask aBitmapAlpha;
345 Size prefSize;
346 BitmapScopedWriteAccess pWriteAccessInstance;
347 AlphaScopedWriteAccess pWriteAccessAlphaInstance;
348 const bool bFuzzing = utl::ConfigManager::IsFuzzing();
349 const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32();
350 const bool bOnlyCreateBitmap
351 = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap);
352 const bool bUseExistingBitmap
353 = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap);
354
355 if (setjmp(png_jmpbuf(pPng)))
356 {
357 if (!bUseExistingBitmap)
358 {
359 // Set the bitmap if it contains something, even on failure. This allows
360 // reading images that are only partially broken.
361 pWriteAccessInstance.reset();
362 pWriteAccessAlphaInstance.reset();
363 if (!aBitmap.IsEmpty() && !aBitmapAlpha.IsEmpty())
364 aBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
365 else if (!aBitmap.IsEmpty())
366 aBitmapEx = BitmapEx(aBitmap);
367 if (!aBitmapEx.IsEmpty() && !prefSize.IsEmpty())
368 {
369 aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
370 aBitmapEx.SetPrefSize(prefSize);
371 }
372 rGraphic = aBitmapEx;
373 }
374 return false;
375 }
376
377 png_set_option(pPng, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON);
378
379 png_set_read_fn(pPng, &rStream, lclReadStream);
380
381 if (!bFuzzing)
382 png_set_crc_action(pPng, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD);
383 else
384 png_set_crc_action(pPng, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
385
386 png_set_sig_bytes(pPng, PNG_SIGNATURE_SIZE);
387
388 png_read_info(pPng, pInfo);
389
390 png_uint_32 width = 0;
391 png_uint_32 height = 0;
392 int bitDepth = 0;
393 int colorType = -1;
394 int interlace = -1;
395
396 png_uint_32 returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType,
397 &interlace, nullptr, nullptr);
398
399 if (returnValue != 1)
400 return false;
401
402 if (colorType == PNG_COLOR_TYPE_PALETTE)
403 png_set_palette_to_rgb(pPng);
404
405 if (colorType == PNG_COLOR_TYPE_GRAY)
406 png_set_expand_gray_1_2_4_to_8(pPng);
407
408 if (png_get_valid(pPng, pInfo, PNG_INFO_tRNS))
409 png_set_tRNS_to_alpha(pPng);
410
411 if (bitDepth == 16)
412 png_set_scale_16(pPng);
413
414 if (bitDepth < 8)
415 png_set_packing(pPng);
416
417 // Convert gray+alpha to RGBA, keep gray as gray.
418 if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA
419 || (colorType == PNG_COLOR_TYPE_GRAY && png_get_valid(pPng, pInfo, PNG_INFO_tRNS)))
420 {
421 png_set_gray_to_rgb(pPng);
422 }
423
424 // Sets the filler byte - if RGB it converts to RGBA
425 // png_set_filler(pPng, 0xFF, PNG_FILLER_AFTER);
426
427 int nNumberOfPasses = png_set_interlace_handling(pPng);
428
429 png_read_update_info(pPng, pInfo);
430 returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType, nullptr,
431 nullptr, nullptr);
432
433 if (returnValue != 1)
434 return false;
435
436 if (bitDepth != 8
437 || (colorType != PNG_COLOR_TYPE_RGB && colorType != PNG_COLOR_TYPE_RGB_ALPHA
438 && colorType != PNG_COLOR_TYPE_GRAY))
439 {
440 return false;
441 }
442
443 png_uint_32 res_x = 0;
444 png_uint_32 res_y = 0;
445 int unit_type = 0;
446 if (png_get_pHYs(pPng, pInfo, &res_x, &res_y, &unit_type) != 0
447 && unit_type == PNG_RESOLUTION_METER && res_x && res_y)
448 {
449 // convert into MapUnit::Map100thMM
450 prefSize = Size(static_cast<sal_Int32>((100000.0 * width) / res_x),
451 static_cast<sal_Int32>((100000.0 * height) / res_y));
452 }
453
454 if (!bUseExistingBitmap)
455 {
456 switch (colorType)
457 {
458 case PNG_COLOR_TYPE_RGB:
459 aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP);
460 break;
461 case PNG_COLOR_TYPE_RGBA:
462 if (bSupportsBitmap32)
463 aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP);
464 else
465 {
466 aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP);
467 aBitmapAlpha = AlphaMask(Size(width, height), nullptr);
468 aBitmapAlpha.Erase(0); // opaque
469 }
470 break;
471 case PNG_COLOR_TYPE_GRAY:
472 aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N8_BPP,
474 break;
475 default:
476 abort();
477 }
478
479 if (bOnlyCreateBitmap)
480 {
481 if (!aBitmapAlpha.IsEmpty())
482 aBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
483 else
484 aBitmapEx = BitmapEx(aBitmap);
485 if (!prefSize.IsEmpty())
486 {
487 aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
488 aBitmapEx.SetPrefSize(prefSize);
489 }
490 rGraphic = aBitmapEx;
491 return true;
492 }
493
494 pWriteAccessInstance = BitmapScopedWriteAccess(aBitmap);
495 if (!pWriteAccessInstance)
496 return false;
497 if (!aBitmapAlpha.IsEmpty())
498 {
499 pWriteAccessAlphaInstance = AlphaScopedWriteAccess(aBitmapAlpha);
500 if (!pWriteAccessAlphaInstance)
501 return false;
502 }
503 }
504 BitmapScopedWriteAccess& pWriteAccess = pAccess ? *pAccess : pWriteAccessInstance;
505 AlphaScopedWriteAccess& pWriteAccessAlpha
506 = pAlphaAccess ? *pAlphaAccess : pWriteAccessAlphaInstance;
507
508 if (colorType == PNG_COLOR_TYPE_RGB)
509 {
510 ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
511 if (eFormat == ScanlineFormat::N24BitTcBgr)
512 png_set_bgr(pPng);
513
514 for (int pass = 0; pass < nNumberOfPasses; pass++)
515 {
516 for (png_uint_32 y = 0; y < height; y++)
517 {
518 Scanline pScanline = pWriteAccess->GetScanline(y);
519 png_read_row(pPng, pScanline, nullptr);
520 }
521 }
522 }
523 else if (colorType == PNG_COLOR_TYPE_RGB_ALPHA)
524 {
525 size_t aRowSizeBytes = png_get_rowbytes(pPng, pInfo);
526
527 if (bSupportsBitmap32)
528 {
529 ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
531 png_set_bgr(pPng);
532
533 for (int pass = 0; pass < nNumberOfPasses; pass++)
534 {
535 for (png_uint_32 y = 0; y < height; y++)
536 {
537 Scanline pScanline = pWriteAccess->GetScanline(y);
538 png_read_row(pPng, pScanline, nullptr);
539 }
540 }
541#if !ENABLE_WASM_STRIP_PREMULTIPLY
543#endif
545 { // alpha first and premultiply
546 for (png_uint_32 y = 0; y < height; y++)
547 {
548 Scanline pScanline = pWriteAccess->GetScanline(y);
549 for (size_t i = 0; i < aRowSizeBytes; i += 4)
550 {
551 const sal_uInt8 alpha = pScanline[i + 3];
552#if ENABLE_WASM_STRIP_PREMULTIPLY
553 pScanline[i + 3] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]);
554 pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]);
555 pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i]);
556#else
557 pScanline[i + 3] = premultiply[alpha][pScanline[i + 2]];
558 pScanline[i + 2] = premultiply[alpha][pScanline[i + 1]];
559 pScanline[i + 1] = premultiply[alpha][pScanline[i]];
560#endif
561 pScanline[i] = alpha;
562 }
563 }
564 }
565 else
566 { // keep alpha last, only premultiply
567 for (png_uint_32 y = 0; y < height; y++)
568 {
569 Scanline pScanline = pWriteAccess->GetScanline(y);
570 for (size_t i = 0; i < aRowSizeBytes; i += 4)
571 {
572 const sal_uInt8 alpha = pScanline[i + 3];
573#if ENABLE_WASM_STRIP_PREMULTIPLY
574 pScanline[i] = vcl::bitmap::premultiply(alpha, pScanline[i]);
575 pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]);
576 pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]);
577#else
578 pScanline[i] = premultiply[alpha][pScanline[i]];
579 pScanline[i + 1] = premultiply[alpha][pScanline[i + 1]];
580 pScanline[i + 2] = premultiply[alpha][pScanline[i + 2]];
581#endif
582 }
583 }
584 }
585 }
586 else
587 {
588 ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat();
589 if (eFormat == ScanlineFormat::N24BitTcBgr)
590 png_set_bgr(pPng);
591
592 if (nNumberOfPasses == 1)
593 {
594 // optimise the common case, where we can use a buffer of only a single row
595 std::vector<png_byte> aRow(aRowSizeBytes, 0);
596 for (png_uint_32 y = 0; y < height; y++)
597 {
598 Scanline pScanline = pWriteAccess->GetScanline(y);
599 Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y);
600 png_bytep pRow = aRow.data();
601 png_read_row(pPng, pRow, nullptr);
602 size_t iAlpha = 0;
603 size_t iColor = 0;
604 for (size_t i = 0; i < aRowSizeBytes; i += 4)
605 {
606 pScanline[iColor++] = pRow[i + 0];
607 pScanline[iColor++] = pRow[i + 1];
608 pScanline[iColor++] = pRow[i + 2];
609 pScanAlpha[iAlpha++] = pRow[i + 3];
610 }
611 }
612 }
613 else
614 {
615 std::vector<std::vector<png_byte>> aRows(height);
616 for (auto& rRow : aRows)
617 rRow.resize(aRowSizeBytes, 0);
618 for (int pass = 0; pass < nNumberOfPasses; pass++)
619 {
620 for (png_uint_32 y = 0; y < height; y++)
621 {
622 Scanline pScanline = pWriteAccess->GetScanline(y);
623 Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y);
624 png_bytep pRow = aRows[y].data();
625 png_read_row(pPng, pRow, nullptr);
626 size_t iAlpha = 0;
627 size_t iColor = 0;
628 for (size_t i = 0; i < aRowSizeBytes; i += 4)
629 {
630 pScanline[iColor++] = pRow[i + 0];
631 pScanline[iColor++] = pRow[i + 1];
632 pScanline[iColor++] = pRow[i + 2];
633 pScanAlpha[iAlpha++] = pRow[i + 3];
634 }
635 }
636 }
637 }
638 }
639 }
640 else if (colorType == PNG_COLOR_TYPE_GRAY)
641 {
642 for (int pass = 0; pass < nNumberOfPasses; pass++)
643 {
644 for (png_uint_32 y = 0; y < height; y++)
645 {
646 Scanline pScanline = pWriteAccess->GetScanline(y);
647 png_read_row(pPng, pScanline, nullptr);
648 }
649 }
650 }
651
652 png_read_end(pPng, pInfo);
653
654 if (!bUseExistingBitmap)
655 {
656 pWriteAccess.reset();
657 pWriteAccessAlpha.reset();
658 if (!aBitmapAlpha.IsEmpty())
659 aBitmapEx = BitmapEx(aBitmap, aBitmapAlpha);
660 else
661 aBitmapEx = BitmapEx(aBitmap);
662 if (!prefSize.IsEmpty())
663 {
664 aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
665 aBitmapEx.SetPrefSize(prefSize);
666 }
667 }
668
669 if (aAPNGInfo.mbIsApng)
670 {
671 Animation aAnimation;
672 // We create new pngs for each frame and use the PngImageReader to create
673 // the BitmapExs for each frame
674 bool bFctlBeforeIDAT = fcTLbeforeIDAT(rStream);
675 size_t nSequenceIndex = static_cast<size_t>(bFctlBeforeIDAT);
676 sal_uInt32 nFrames
677 = aAPNGInfo.maACTLChunk.num_frames - static_cast<sal_uInt32>(bFctlBeforeIDAT);
678 {
679 fcTLChunk* aFctlChunk = dynamic_cast<fcTLChunk*>(aAPNGInfo.maFrameData[0].get());
680 if (!aFctlChunk)
681 return false;
682 Size aCanvasSize(aFctlChunk->width, aFctlChunk->height);
683 aAnimation.SetDisplaySizePixel(aCanvasSize);
684 aAnimation.SetLoopCount(aAPNGInfo.maACTLChunk.num_plays);
685 if (bFctlBeforeIDAT)
686 {
687 Point aFirstPoint(0, 0);
688 auto aDisposal = static_cast<Disposal>(aFctlChunk->dispose_op);
689 auto aBlend = static_cast<Blend>(aFctlChunk->blend_op);
690 if (aDisposal == Disposal::Previous)
691 aDisposal = Disposal::Back;
692 AnimationFrame aAnimationFrame(
693 aBitmapEx, aFirstPoint, aCanvasSize,
694 NumDenToTime(aFctlChunk->delay_num, aFctlChunk->delay_den), aDisposal, aBlend);
695 aAnimation.Insert(aAnimationFrame);
696 }
697 }
698 for (sal_uInt32 i = 0; i < nFrames; i++)
699 {
700 // Guaranteed to be fcTL chunk here because it was checked earlier
701 fcTLChunk* aFctlChunk
702 = static_cast<fcTLChunk*>(aAPNGInfo.maFrameData[nSequenceIndex++].get());
703 Disposal aDisposal = static_cast<Disposal>(aFctlChunk->dispose_op);
704 Blend aBlend = static_cast<Blend>(aFctlChunk->blend_op);
705 if (i == 0 && aDisposal == Disposal::Back)
706 aDisposal = Disposal::Previous;
707 SvMemoryStream aFrameStream;
708 getImportantChunks(rStream, aFrameStream, aFctlChunk->width, aFctlChunk->height);
709 // A single frame can have multiple fdAT chunks
710 while (fdATChunk* pFdatChunk
711 = dynamic_cast<fdATChunk*>(aAPNGInfo.maFrameData[nSequenceIndex].get()))
712 {
713 // Write fdAT chunks as IDAT chunks
714 auto nDataSize = pFdatChunk->frame_data.size();
715 aFrameStream.WriteUInt32(nDataSize - PNG_TYPE_SIZE);
716 aFrameStream.WriteBytes(pFdatChunk->frame_data.data(), nDataSize);
717 sal_uInt32 nCrc = rtl_crc32(0, pFdatChunk->frame_data.data(), nDataSize);
718 aFrameStream.WriteUInt32(nCrc);
719 nSequenceIndex++;
720 if (nSequenceIndex >= aAPNGInfo.maFrameData.size())
721 break;
722 }
723 aFrameStream.WriteUInt32(PNG_IEND_SIZE);
724 aFrameStream.WriteUInt32(PNG_IEND_SIGNATURE);
725 aFrameStream.WriteUInt32(PNG_IEND_CRC);
726 Graphic aFrameGraphic;
727 aFrameStream.Seek(0);
728 bool bSuccess = reader(aFrameStream, aFrameGraphic);
729 if (!bSuccess)
730 return false;
731 BitmapEx aFrameBitmapEx = aFrameGraphic.GetBitmapEx();
732 Point aStartPoint(aFctlChunk->x_offset, aFctlChunk->y_offset);
733 Size aSize(aFctlChunk->width, aFctlChunk->height);
734 AnimationFrame aAnimationFrame(
735 aFrameBitmapEx, aStartPoint, aSize,
736 NumDenToTime(aFctlChunk->delay_num, aFctlChunk->delay_den), aDisposal, aBlend);
737 aAnimation.Insert(aAnimationFrame);
738 }
739 rGraphic = aAnimation;
740 return true;
741 }
742 else
743 {
744 rGraphic = aBitmapEx;
745 }
746
747 return true;
748}
749
750BinaryDataContainer getMsGifChunk(SvStream& rStream)
751{
752 if (!isPng(rStream))
753 return {};
754 // It's easier to read manually the contents and find the chunk than
755 // try to get it using libpng.
756 // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_format
757 // Each chunk is: 4 bytes length, 4 bytes type, <length> bytes, 4 bytes crc
758 bool ignoreCrc = utl::ConfigManager::IsFuzzing();
759 for (;;)
760 {
761 sal_uInt32 length(0), type(0), crc(0);
762 rStream.ReadUInt32(length);
763 rStream.ReadUInt32(type);
764 if (!rStream.good())
765 return {};
766 constexpr sal_uInt32 PNGCHUNK_msOG = 0x6d734f47; // Microsoft Office Animated GIF
767 constexpr sal_uInt64 MSGifHeaderSize = 11; // "MSOFFICE9.0"
768 if (type == PNGCHUNK_msOG && length > MSGifHeaderSize)
769 {
770 // calculate chunktype CRC (swap it back to original byte order)
771 sal_uInt32 typeForCrc = type;
772#if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN)
773 typeForCrc = OSL_SWAPDWORD(typeForCrc);
774#endif
775 sal_uInt32 computedCrc = rtl_crc32(0, &typeForCrc, 4);
776 const sal_uInt64 pos = rStream.Tell();
777 if (pos + length >= rStream.TellEnd())
778 return {}; // broken PNG
779
780 char msHeader[MSGifHeaderSize];
781 if (rStream.ReadBytes(msHeader, MSGifHeaderSize) != MSGifHeaderSize)
782 return {};
783 computedCrc = rtl_crc32(computedCrc, msHeader, MSGifHeaderSize);
784 length -= MSGifHeaderSize;
785
786 BinaryDataContainer chunk(rStream, length);
787 if (chunk.isEmpty())
788 return {};
789 computedCrc = rtl_crc32(computedCrc, chunk.getData(), chunk.getSize());
790 rStream.ReadUInt32(crc);
791 if (!ignoreCrc && crc != computedCrc)
792 continue; // invalid chunk, ignore
793 return chunk;
794 }
795 if (rStream.remainingSize() < length)
796 return {};
797 rStream.SeekRel(length);
798 rStream.ReadUInt32(crc);
799 if (type == PNG_IEND_SIGNATURE)
800 return {};
801 }
802}
803#if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__
804#pragma GCC diagnostic pop
805#endif
806
807} // anonymous namespace
808
809namespace vcl
810{
812 : mrStream(rStream)
813{
814}
815
817{
818 Graphic aGraphic;
819 bool bRet = reader(mrStream, aGraphic);
820 rBitmapEx = aGraphic.GetBitmapEx();
821 return bRet;
822}
823
824bool PngImageReader::read(Graphic& rGraphic) { return reader(mrStream, rGraphic); }
825
827{
828 Graphic aGraphic;
829 read(aGraphic);
830 return aGraphic.GetBitmapEx();
831}
832
834{
835 sal_uInt64 originalPosition = rStream.Tell();
836 SvStreamEndian originalEndian = rStream.GetEndian();
837 rStream.SetEndian(SvStreamEndian::BIG);
838 auto chunk = getMsGifChunk(rStream);
839 rStream.SetEndian(originalEndian);
840 rStream.Seek(originalPosition);
841 return chunk;
842}
843
844bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags,
845 BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess)
846{
847 // Creating empty bitmaps should be practically a no-op, and thus thread-safe.
848 Graphic aGraphic;
849 if (reader(rInputStream, aGraphic, nImportFlags, pAccess, pAlphaAccess))
850 {
852 rGraphic = aGraphic;
853 return true;
854 }
855 return false;
856}
857
859{
860 auto nStmPos = rStream.Tell();
861 comphelper::ScopeGuard aGuard([&rStream, &nStmPos] {
862 rStream.Seek(nStmPos);
863 rStream.SetEndian(SvStreamEndian::LITTLE);
864 });
865 if (!isPng(rStream))
866 return false;
867 rStream.SetEndian(SvStreamEndian::BIG);
868 sal_uInt32 nChunkSize, nChunkType;
869 rStream.ReadUInt32(nChunkSize);
870 rStream.ReadUInt32(nChunkType);
871 if (!rStream.good() || nChunkType != PNG_IHDR_SIGNATURE)
872 return false;
873 rStream.SeekRel(nChunkSize);
874 // Skip IHDR CRC
875 rStream.SeekRel(PNG_CRC_SIZE);
876 // Look for acTL chunk that exists before the first IDAT chunk
877 while (true)
878 {
879 rStream.ReadUInt32(nChunkSize);
880 if (!rStream.good())
881 return false;
882 rStream.ReadUInt32(nChunkType);
883 if (!rStream.good())
884 return false;
885 // Check if it's an IDAT chunk -> regular PNG
886 if (nChunkType == PNG_IDAT_SIGNATURE)
887 return false;
888 else if (nChunkType == PNG_ACTL_SIGNATURE)
889 return true;
890 else
891 {
892 if (!checkSeek(rStream, rStream.Tell() + nChunkSize + PNG_CRC_SIZE))
893 return false;
894 }
895 }
896}
897
898} // namespace vcl
899
900/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Disposal
vcl::ScopedBitmapAccess< BitmapWriteAccess, AlphaMask, &AlphaMask::AcquireAlphaWriteAccess > AlphaScopedWriteAccess
vcl::ScopedBitmapAccess< BitmapWriteAccess, Bitmap, &Bitmap::AcquireWriteAccess > BitmapScopedWriteAccess
constexpr sal_uInt32 PNG_FDAT_SIGNATURE
constexpr int PNG_SIZE_SIZE
constexpr sal_uInt32 PNG_FCTL_SIGNATURE
constexpr int PNG_TYPE_SIZE
constexpr sal_uInt32 PNG_IHDR_SIGNATURE
constexpr sal_uInt32 PNG_ACTL_SIGNATURE
constexpr int PNG_IEND_SIZE
constexpr sal_uInt32 PNG_IEND_SIGNATURE
constexpr sal_uInt32 PNG_IEND_CRC
constexpr sal_uInt64 PNG_SIGNATURE
constexpr sal_uInt32 PNG_IDAT_SIGNATURE
constexpr int PNG_SIGNATURE_SIZE
constexpr int PNG_CRC_SIZE
constexpr int PNG_IHDR_SIZE
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
ScanlineFormat
Definition: Scanline.hxx:29
bool IsEmpty() const
void Erase(sal_uInt8 cTransparency)
Definition: alpha.cxx:82
void SetLoopCount(const sal_uInt32 nLoopCount)
Definition: Animation.cxx:455
void SetDisplaySizePixel(const Size &rSize)
Definition: Animation.hxx:58
bool Insert(const AnimationFrame &rAnimationFrame)
Definition: Animation.cxx:411
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:80
void SetPrefSize(const Size &rPrefSize)
Definition: bitmapex.hxx:77
bool IsEmpty() const
Definition: BitmapEx.cxx:186
static const BitmapPalette & GetGreyPalette(int nEntries)
bool IsEmpty() const
BitmapEx GetBitmapEx(const GraphicConversionParameters &rParameters=GraphicConversionParameters()) const
Definition: graph.cxx:330
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()
std::size_t WriteBytes(const void *pData, std::size_t nSize)
SvStream & WriteUChar(unsigned char nChar)
SvStream & WriteUInt64(sal_uInt64 nuInt64)
SvStream & WriteUInt32(sal_uInt32 nUInt32)
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()
SvStream & ReadUChar(unsigned char &rChar)
static bool IsFuzzing()
PngImageReader(SvStream &rStream)
static BinaryDataContainer getMicrosoftGifChunk(SvStream &rStream)
static bool isAPng(SvStream &rStream)
This template handles BitmapAccess the RAII way.
Reference< XInputStream > rInputStream
float y
OUString sName
GraphicFilterImportFlags
@ UseExistingBitmap
Read pixel data into an existing bitmap.
@ OnlyCreateBitmap
Only create a bitmap, do not read pixel data.
sal_uInt16 nPos
constexpr OUStringLiteral aData
constexpr double alpha[nDetails]
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
TOOLS_DLLPUBLIC bool checkSeek(SvStream &rSt, sal_uInt64 nOffset)
SalInstance * mpDefInst
Definition: svdata.hxx:389
ImplSVData * ImplGetSVData()
Definition: svdata.cxx:77
unsigned char sal_uInt8
ResultType type
size_t pos