Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
image_diff_png.cpp
Go to the documentation of this file.
1// Copyright 2013 The PDFium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This is a duplicate of chromium's src/tools/imagediff/image_diff_png.cc
6// that has been modified to build in a pdfium environment, which itself
7// was duplicated as follows:
8
9// This is a duplicate of ui/gfx/codec/png_codec.cc, after removing code related
10// to Skia, that we can use when running layout tests with minimal dependencies.
11
12#include "testing/image_diff/image_diff_png.h"
13
14#include <stdlib.h>
15#include <string.h>
16
17#include <string>
18
19#include "third_party/base/check_op.h"
20#include "third_party/base/notreached.h"
21
22#ifdef USE_SYSTEM_ZLIB
23#include <zlib.h>
24#else
25#include "third_party/zlib/zlib.h"
26#endif
27
28#ifdef USE_SYSTEM_LIBPNG
29#include <png.h>
30#else
31#include "third_party/libpng/png.h"
32#endif
33
34namespace image_diff_png {
35
36namespace {
37
38enum ColorFormat {
39 // 3 bytes per pixel (packed), in RGB order regardless of endianness.
40 // This is the native JPEG format.
41 FORMAT_RGB,
42
43 // 3 bytes per pixel, in BGR order regardless of endianness.
44 FORMAT_BGR,
45
46 // 4 bytes per pixel, in RGBA order in memory regardless of endianness.
47 FORMAT_RGBA,
48
49 // 4 bytes per pixel, in BGRA order in memory regardless of endianness.
50 // This is the default Windows DIB order.
51 FORMAT_BGRA,
52
53 // 1 byte per pixel.
54 FORMAT_GRAY,
55};
56
57// Represents a comment in the tEXt ancillary chunk of the png.
58struct Comment {
59 std::string key;
60 std::string text;
61};
62
63// Converts BGRA->RGBA and RGBA->BGRA.
64void ConvertBetweenBGRAandRGBA(const uint8_t* input,
65 int pixel_width,
66 uint8_t* output,
67 bool* is_opaque) {
68 for (int x = 0; x < pixel_width; x++) {
69 const uint8_t* pixel_in = &input[x * 4];
70 uint8_t* pixel_out = &output[x * 4];
71 pixel_out[0] = pixel_in[2];
72 pixel_out[1] = pixel_in[1];
73 pixel_out[2] = pixel_in[0];
74 pixel_out[3] = pixel_in[3];
75 }
76}
77
78void ConvertBGRtoRGB(const uint8_t* bgr,
79 int pixel_width,
80 uint8_t* rgb,
81 bool* is_opaque) {
82 for (int x = 0; x < pixel_width; x++) {
83 const uint8_t* pixel_in = &bgr[x * 3];
84 uint8_t* pixel_out = &rgb[x * 3];
85 pixel_out[0] = pixel_in[2];
86 pixel_out[1] = pixel_in[1];
87 pixel_out[2] = pixel_in[0];
88 }
89}
90
91void ConvertRGBAtoRGB(const uint8_t* rgba,
92 int pixel_width,
93 uint8_t* rgb,
94 bool* is_opaque) {
95 const uint8_t* pixel_in = rgba;
96 uint8_t* pixel_out = rgb;
97 for (int x = 0; x < pixel_width; x++) {
98 memcpy(pixel_out, pixel_in, 3);
99 pixel_in += 4;
100 pixel_out += 3;
101 }
102}
103
104// Decoder
105//
106// This code is based on WebKit libpng interface (PNGImageDecoder), which is
107// in turn based on the Mozilla png decoder.
108
109// Gamma constants: We assume we're on Windows which uses a gamma of 2.2.
110constexpr double kDefaultGamma = 2.2;
111
112// Maximum gamma accepted by PNG library.
113constexpr double kMaxGamma = 21474.83;
114
115constexpr double kInverseGamma = 1.0 / kDefaultGamma;
116
117class PngDecoderState {
118 public:
119 PngDecoderState(ColorFormat ofmt, std::vector<uint8_t>* out)
120 : output_format(ofmt), output(out) {}
121
122 const ColorFormat output_format;
124
125 // Used during the reading of an SkBitmap. Defaults to true until we see a
126 // pixel with anything other than an alpha of 255.
127 bool is_opaque = true;
128
129 // An intermediary buffer for decode output.
131
132 // Called to convert a row from the library to the correct output format.
133 // When null, no conversion is necessary.
134 void (*row_converter)(const uint8_t* in,
135 int w,
136 uint8_t* out,
137 bool* is_opaque) = nullptr;
138
139 // Size of the image, set in the info callback.
140 int width = 0;
141 int height = 0;
142
143 // Set to true when we've found the end of the data.
144 bool done = false;
145};
146
147void ConvertRGBtoRGBA(const uint8_t* rgb,
148 int pixel_width,
149 uint8_t* rgba,
150 bool* is_opaque) {
151 const uint8_t* pixel_in = rgb;
152 uint8_t* pixel_out = rgba;
153 for (int x = 0; x < pixel_width; x++) {
154 memcpy(pixel_out, pixel_in, 3);
155 pixel_out[3] = 0xff;
156 pixel_in += 3;
157 pixel_out += 4;
158 }
159}
160
161void ConvertRGBtoBGRA(const uint8_t* rgb,
162 int pixel_width,
163 uint8_t* bgra,
164 bool* is_opaque) {
165 for (int x = 0; x < pixel_width; x++) {
166 const uint8_t* pixel_in = &rgb[x * 3];
167 uint8_t* pixel_out = &bgra[x * 4];
168 pixel_out[0] = pixel_in[2];
169 pixel_out[1] = pixel_in[1];
170 pixel_out[2] = pixel_in[0];
171 pixel_out[3] = 0xff;
172 }
173}
174
175// Called when the png header has been read. This code is based on the WebKit
176// PNGImageDecoder
177void DecodeInfoCallback(png_struct* png_ptr, png_info* info_ptr) {
178 PngDecoderState* state =
179 static_cast<PngDecoderState*>(png_get_progressive_ptr(png_ptr));
180
181 int bit_depth, color_type, interlace_type, compression_type;
182 int filter_type, channels;
183 png_uint_32 w, h;
184 png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type,
185 &interlace_type, &compression_type, &filter_type);
186
187 // Bounds check. When the image is unreasonably big, we'll error out and
188 // end up back at the setjmp call when we set up decoding. "Unreasonably big"
189 // means "big enough that w * h * 32bpp might overflow an int"; we choose this
190 // threshold to match WebKit and because a number of places in code assume
191 // that an image's size (in bytes) fits in a (signed) int.
192 unsigned long long total_size =
193 static_cast<unsigned long long>(w) * static_cast<unsigned long long>(h);
194 if (total_size > ((1 << 29) - 1))
195 longjmp(png_jmpbuf(png_ptr), 1);
196 state->width = static_cast<int>(w);
197 state->height = static_cast<int>(h);
198
199 // Expand to ensure we use 24-bit for RGB and 32-bit for RGBA.
200 if (color_type == PNG_COLOR_TYPE_PALETTE ||
201 (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8))
202 png_set_expand(png_ptr);
203
204 // Transparency for paletted images.
205 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
206 png_set_expand(png_ptr);
207
208 // Convert 16-bit to 8-bit.
209 if (bit_depth == 16)
210 png_set_strip_16(png_ptr);
211
212 // Expand grayscale to RGB.
213 if (color_type == PNG_COLOR_TYPE_GRAY ||
214 color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
215 png_set_gray_to_rgb(png_ptr);
216
217 // Deal with gamma and keep it under our control.
218 double gamma;
219 if (png_get_gAMA(png_ptr, info_ptr, &gamma)) {
220 if (gamma <= 0.0 || gamma > kMaxGamma) {
221 gamma = kInverseGamma;
222 png_set_gAMA(png_ptr, info_ptr, gamma);
223 }
224 png_set_gamma(png_ptr, kDefaultGamma, gamma);
225 } else {
226 png_set_gamma(png_ptr, kDefaultGamma, kInverseGamma);
227 }
228
229 // Tell libpng to send us rows for interlaced pngs.
230 if (interlace_type == PNG_INTERLACE_ADAM7)
231 png_set_interlace_handling(png_ptr);
232
233 // Update our info now
234 png_read_update_info(png_ptr, info_ptr);
235 channels = png_get_channels(png_ptr, info_ptr);
236
237 // Pick our row format converter necessary for this data.
238 if (channels == 3) {
239 switch (state->output_format) {
240 case FORMAT_RGB:
241 state->row_converter = nullptr; // no conversion necessary
242 state->output_channels = 3;
243 break;
244 case FORMAT_RGBA:
245 state->row_converter = &ConvertRGBtoRGBA;
246 state->output_channels = 4;
247 break;
248 case FORMAT_BGRA:
249 state->row_converter = &ConvertRGBtoBGRA;
250 state->output_channels = 4;
251 break;
252 case FORMAT_GRAY:
253 state->row_converter = nullptr;
254 state->output_channels = 1;
255 break;
256 default:
257 NOTREACHED_NORETURN();
258 }
259 } else if (channels == 4) {
260 switch (state->output_format) {
261 case FORMAT_RGB:
262 state->row_converter = &ConvertRGBAtoRGB;
263 state->output_channels = 3;
264 break;
265 case FORMAT_RGBA:
266 state->row_converter = nullptr; // no conversion necessary
267 state->output_channels = 4;
268 break;
269 case FORMAT_BGRA:
270 state->row_converter = &ConvertBetweenBGRAandRGBA;
271 state->output_channels = 4;
272 break;
273 default:
274 NOTREACHED_NORETURN();
275 }
276 } else {
277 NOTREACHED_NORETURN();
278 }
279
280 state->output->resize(state->width * state->output_channels * state->height);
281}
282
283void DecodeRowCallback(png_struct* png_ptr,
284 png_byte* new_row,
285 png_uint_32 row_num,
286 int pass) {
287 PngDecoderState* state =
288 static_cast<PngDecoderState*>(png_get_progressive_ptr(png_ptr));
289 CHECK_LE(static_cast<int>(row_num), state->height);
290
291 uint8_t* base = nullptr;
292 base = &state->output->front();
293
294 uint8_t* dest = &base[state->width * state->output_channels * row_num];
295 if (state->row_converter)
296 state->row_converter(new_row, state->width, dest, &state->is_opaque);
297 else
298 memcpy(dest, new_row, state->width * state->output_channels);
299}
300
301void DecodeEndCallback(png_struct* png_ptr, png_info* info) {
302 PngDecoderState* state =
303 static_cast<PngDecoderState*>(png_get_progressive_ptr(png_ptr));
304
305 // Mark the image as complete, this will tell the Decode function that we
306 // have successfully found the end of the data.
307 state->done = true;
308}
309
310// Automatically destroys the given read structs on destruction to make
311// cleanup and error handling code cleaner.
312class PngReadStructDestroyer {
313 public:
314 PngReadStructDestroyer(png_struct** ps, png_info** pi) : ps_(ps), pi_(pi) {}
315 ~PngReadStructDestroyer() { png_destroy_read_struct(ps_, pi_, nullptr); }
316
317 private:
318 png_struct** ps_;
319 png_info** pi_;
320};
321
322bool BuildPNGStruct(pdfium::span<const uint8_t> input,
323 png_struct** png_ptr,
324 png_info** info_ptr) {
325 if (input.size() < 8)
326 return false; // Input data too small to be a png
327
328 // Have libpng check the signature, it likes the first 8 bytes.
329 if (png_sig_cmp(const_cast<uint8_t*>(input.data()), 0, 8) != 0)
330 return false;
331
332 *png_ptr =
333 png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
334 if (!*png_ptr)
335 return false;
336
337 *info_ptr = png_create_info_struct(*png_ptr);
338 if (!*info_ptr) {
339 png_destroy_read_struct(png_ptr, nullptr, nullptr);
340 return false;
341 }
342
343 return true;
344}
345
346std::vector<uint8_t> Decode(pdfium::span<const uint8_t> input,
347 ColorFormat format,
348 int* w,
349 int* h) {
350 std::vector<uint8_t> output;
351 png_struct* png_ptr = nullptr;
352 png_info* info_ptr = nullptr;
353 if (!BuildPNGStruct(input, &png_ptr, &info_ptr))
354 return output;
355
356 PngReadStructDestroyer destroyer(&png_ptr, &info_ptr);
357 if (setjmp(png_jmpbuf(png_ptr))) {
358 // The destroyer will ensure that the structures are cleaned up in this
359 // case, even though we may get here as a jump from random parts of the
360 // PNG library called below.
361 return output;
362 }
363
364 PngDecoderState state(format, &output);
365
366 png_set_progressive_read_fn(png_ptr, &state, &DecodeInfoCallback,
367 &DecodeRowCallback, &DecodeEndCallback);
368 png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(input.data()),
369 input.size());
370
371 if (!state.done) {
372 // Fed it all the data but the library didn't think we got all the data, so
373 // this file must be truncated.
374 output.clear();
375 return output;
376 }
377
378 *w = state.width;
379 *h = state.height;
380 return output;
381}
382
383// Encoder
384//
385// This section of the code is based on nsPNGEncoder.cpp in Mozilla
386// (Copyright 2005 Google Inc.)
387
388// Passed around as the io_ptr in the png structs so our callbacks know where
389// to write data.
390struct PngEncoderState {
391 explicit PngEncoderState(std::vector<uint8_t>* o) : out(o) {}
393};
394
395// Called by libpng to flush its internal buffer to ours.
396void EncoderWriteCallback(png_structp png, png_bytep data, png_size_t size) {
397 PngEncoderState* state = static_cast<PngEncoderState*>(png_get_io_ptr(png));
398 size_t old_size = state->out->size();
399 state->out->resize(old_size + size);
400 memcpy(&(*state->out)[old_size], data, size);
401}
402
403void FakeFlushCallback(png_structp png) {
404 // We don't need to perform any flushing since we aren't doing real IO, but
405 // we're required to provide this function by libpng.
406}
407
408void ConvertBGRAtoRGB(const uint8_t* bgra,
409 int pixel_width,
410 uint8_t* rgb,
411 bool* is_opaque) {
412 for (int x = 0; x < pixel_width; x++) {
413 const uint8_t* pixel_in = &bgra[x * 4];
414 uint8_t* pixel_out = &rgb[x * 3];
415 pixel_out[0] = pixel_in[2];
416 pixel_out[1] = pixel_in[1];
417 pixel_out[2] = pixel_in[0];
418 }
419}
420
421#ifdef PNG_TEXT_SUPPORTED
422
423inline char* strdup(const char* str) {
424#if BUILDFLAG(IS_WIN)
425 return _strdup(str);
426#else
427 return ::strdup(str);
428#endif
429}
430
431class CommentWriter {
432 public:
433 explicit CommentWriter(const std::vector<Comment>& comments)
434 : comments_(comments), png_text_(new png_text[comments.size()]) {
435 for (size_t i = 0; i < comments.size(); ++i)
436 AddComment(i, comments[i]);
437 }
438
439 ~CommentWriter() {
440 for (size_t i = 0; i < comments_.size(); ++i) {
441 free(png_text_[i].key);
442 free(png_text_[i].text);
443 }
444 delete[] png_text_;
445 }
446
447 bool HasComments() { return !comments_.empty(); }
448
449 png_text* get_png_text() { return png_text_; }
450
451 int size() { return static_cast<int>(comments_.size()); }
452
453 private:
454 void AddComment(size_t pos, const Comment& comment) {
455 png_text_[pos].compression = PNG_TEXT_COMPRESSION_NONE;
456 // A PNG comment's key can only be 79 characters long.
457 if (comment.key.size() > 79)
458 return;
459 png_text_[pos].key = strdup(comment.key.substr(0, 78).c_str());
460 png_text_[pos].text = strdup(comment.text.c_str());
461 png_text_[pos].text_length = comment.text.size();
462#ifdef PNG_iTXt_SUPPORTED
463 png_text_[pos].itxt_length = 0;
464 png_text_[pos].lang = 0;
465 png_text_[pos].lang_key = 0;
466#endif
467 }
468
469 const std::vector<Comment> comments_;
470 png_text* png_text_;
471};
472#endif // PNG_TEXT_SUPPORTED
473
474// The type of functions usable for converting between pixel formats.
475typedef void (*FormatConverter)(const uint8_t* in,
476 int w,
477 uint8_t* out,
478 bool* is_opaque);
479
480// libpng uses a wacky setjmp-based API, which makes the compiler nervous.
481// We constrain all of the calls we make to libpng where the setjmp() is in
482// place to this function.
483// Returns true on success.
484bool DoLibpngWrite(png_struct* png_ptr,
485 png_info* info_ptr,
486 PngEncoderState* state,
487 int width,
488 int height,
489 int row_byte_width,
490 pdfium::span<const uint8_t> input,
491 int compression_level,
492 int png_output_color_type,
493 int output_color_components,
494 FormatConverter converter,
495 const std::vector<Comment>& comments) {
496#ifdef PNG_TEXT_SUPPORTED
497 CommentWriter comment_writer(comments);
498#endif
499 uint8_t* row_buffer = nullptr;
500
501 // Make sure to not declare any locals here -- locals in the presence
502 // of setjmp() in C++ code makes gcc complain.
503
504 if (setjmp(png_jmpbuf(png_ptr))) {
505 delete[] row_buffer;
506 return false;
507 }
508
509 png_set_compression_level(png_ptr, compression_level);
510
511 // Set our callback for libpng to give us the data.
512 png_set_write_fn(png_ptr, state, EncoderWriteCallback, FakeFlushCallback);
513
514 png_set_IHDR(png_ptr, info_ptr, width, height, 8, png_output_color_type,
515 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
516 PNG_FILTER_TYPE_DEFAULT);
517
518#ifdef PNG_TEXT_SUPPORTED
519 if (comment_writer.HasComments()) {
520 png_set_text(png_ptr, info_ptr, comment_writer.get_png_text(),
521 comment_writer.size());
522 }
523#endif
524
525 png_write_info(png_ptr, info_ptr);
526
527 if (!converter) {
528 // No conversion needed, give the data directly to libpng.
529 for (int y = 0; y < height; y++) {
530 png_write_row(png_ptr, const_cast<uint8_t*>(&input[y * row_byte_width]));
531 }
532 } else {
533 // Needs conversion using a separate buffer.
534 row_buffer = new uint8_t[width * output_color_components];
535 for (int y = 0; y < height; y++) {
536 converter(&input[y * row_byte_width], width, row_buffer, nullptr);
537 png_write_row(png_ptr, row_buffer);
538 }
539 delete[] row_buffer;
540 }
541
542 png_write_end(png_ptr, info_ptr);
543 return true;
544}
545
546std::vector<uint8_t> EncodeWithCompressionLevel(
547 pdfium::span<const uint8_t> input,
548 ColorFormat format,
549 const int width,
550 const int height,
551 int row_byte_width,
552 bool discard_transparency,
553 const std::vector<Comment>& comments,
554 int compression_level) {
555 std::vector<uint8_t> output;
556
557 // Run to convert an input row into the output row format, nullptr means no
558 // conversion is necessary.
559 FormatConverter converter = nullptr;
560
561 int input_color_components;
562 int output_color_components;
563 int png_output_color_type;
564 switch (format) {
565 case FORMAT_BGR:
566 converter = ConvertBGRtoRGB;
567 [[fallthrough]];
568
569 case FORMAT_RGB:
570 input_color_components = 3;
571 output_color_components = 3;
572 png_output_color_type = PNG_COLOR_TYPE_RGB;
573 break;
574
575 case FORMAT_RGBA:
576 input_color_components = 4;
577 if (discard_transparency) {
578 output_color_components = 3;
579 png_output_color_type = PNG_COLOR_TYPE_RGB;
580 converter = ConvertRGBAtoRGB;
581 } else {
582 output_color_components = 4;
583 png_output_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
584 converter = nullptr;
585 }
586 break;
587
588 case FORMAT_BGRA:
589 input_color_components = 4;
590 if (discard_transparency) {
591 output_color_components = 3;
592 png_output_color_type = PNG_COLOR_TYPE_RGB;
593 converter = ConvertBGRAtoRGB;
594 } else {
595 output_color_components = 4;
596 png_output_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
597 converter = ConvertBetweenBGRAandRGBA;
598 }
599 break;
600
601 case FORMAT_GRAY:
602 input_color_components = 1;
603 output_color_components = 1;
604 png_output_color_type = PNG_COLOR_TYPE_GRAY;
605 break;
606 }
607
608 // Row stride should be at least as long as the length of the data.
609 if (row_byte_width < input_color_components * width)
610 return output;
611
612 png_struct* png_ptr =
613 png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
614 if (!png_ptr)
615 return output;
616 png_info* info_ptr = png_create_info_struct(png_ptr);
617 if (!info_ptr) {
618 png_destroy_write_struct(&png_ptr, nullptr);
619 return output;
620 }
621
622 PngEncoderState state(&output);
623 bool success =
624 DoLibpngWrite(png_ptr, info_ptr, &state, width, height, row_byte_width,
625 input, compression_level, png_output_color_type,
626 output_color_components, converter, comments);
627 png_destroy_write_struct(&png_ptr, &info_ptr);
628
629 if (!success)
630 output.clear();
631 return output;
632}
633
634std::vector<uint8_t> Encode(pdfium::span<const uint8_t> input,
635 ColorFormat format,
636 const int width,
637 const int height,
638 int row_byte_width,
639 bool discard_transparency,
640 const std::vector<Comment>& comments) {
641 return EncodeWithCompressionLevel(input, format, width, height,
642 row_byte_width, discard_transparency,
643 comments, Z_DEFAULT_COMPRESSION);
644}
645
646} // namespace
647
648std::vector<uint8_t> DecodePNG(pdfium::span<const uint8_t> input,
649 bool reverse_byte_order,
650 int* width,
651 int* height) {
652 ColorFormat format = reverse_byte_order ? FORMAT_BGRA : FORMAT_RGBA;
653 return Decode(input, format, width, height);
654}
655
656std::vector<uint8_t> EncodeBGRPNG(pdfium::span<const uint8_t> input,
657 int width,
658 int height,
659 int row_byte_width) {
660 return Encode(input, FORMAT_BGR, width, height, row_byte_width, false,
661 std::vector<Comment>());
662}
663
664std::vector<uint8_t> EncodeRGBAPNG(pdfium::span<const uint8_t> input,
665 int width,
666 int height,
667 int row_byte_width) {
668 return Encode(input, FORMAT_RGBA, width, height, row_byte_width, false,
669 std::vector<Comment>());
670}
671
672std::vector<uint8_t> EncodeBGRAPNG(pdfium::span<const uint8_t> input,
673 int width,
674 int height,
675 int row_byte_width,
676 bool discard_transparency) {
677 return Encode(input, FORMAT_BGRA, width, height, row_byte_width,
678 discard_transparency, std::vector<Comment>());
679}
680
681std::vector<uint8_t> EncodeGrayPNG(pdfium::span<const uint8_t> input,
682 int width,
683 int height,
684 int row_byte_width) {
685 return Encode(input, FORMAT_GRAY, width, height, row_byte_width, false,
686 std::vector<Comment>());
687}
688
689} // namespace image_diff_png
void(* row_converter)(const uint8_t *in, int w, uint8_t *out, bool *is_opaque)
bool done
std::string text
int width
std::vector< uint8_t > * out
bool is_opaque
std::vector< uint8_t > *const output
int output_channels
const ColorFormat output_format
std::string key
int height
std::vector< uint8_t > EncodeGrayPNG(pdfium::span< const uint8_t > input, int width, int height, int row_byte_width)
std::vector< uint8_t > DecodePNG(pdfium::span< const uint8_t > input, bool reverse_byte_order, int *width, int *height)
std::vector< uint8_t > EncodeBGRPNG(pdfium::span< const uint8_t > input, int width, int height, int row_byte_width)
std::vector< uint8_t > EncodeRGBAPNG(pdfium::span< const uint8_t > input, int width, int height, int row_byte_width)
std::vector< uint8_t > EncodeBGRAPNG(pdfium::span< const uint8_t > input, int width, int height, int row_byte_width, bool discard_transparency)