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