// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/gfx/codec/png_codec.h" #include <stddef.h> #include <stdint.h> #include <cmath> #include <iomanip> #include "base/base_paths.h" #include "base/check.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/notreached.h" #include "base/path_service.h" #include "base/ranges/algorithm.h" #include "base/test/metrics/histogram_tester.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/libpng/png.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkColorPriv.h" #include "third_party/skia/include/core/SkUnPreMultiply.h" #include "third_party/zlib/zlib.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/skia_util.h" namespace gfx { namespace { void MakeRGBImage(int w, int h, std::vector<unsigned char>* data) { … } // Set use_transparency to write data into the alpha channel, otherwise it will // be filled with 0xff. With the alpha channel stripped, this should yield the // same image as MakeRGBImage above, so the code below can make reference // images for conversion testing. void MakeRGBAImage(int w, int h, bool use_transparency, std::vector<unsigned char>* data) { … } // Creates a palette-based image. void MakePaletteImage(int w, int h, std::vector<unsigned char>* data, std::vector<png_color>* palette, std::vector<unsigned char>* trans_chunk = 0) { … } // Creates a grayscale image without an alpha channel. void MakeGrayscaleImage(int w, int h, std::vector<unsigned char>* data) { … } // Creates a grayscale image with an alpha channel. void MakeGrayscaleAlphaImage(int w, int h, std::vector<unsigned char>* data) { … } // User write function (to be passed to libpng by EncodeImage) which writes // into a buffer instead of to a file. void WriteImageData(png_structp png_ptr, png_bytep data, png_size_t length) { … } // User flush function; goes with WriteImageData, above. void FlushImageData(png_structp /*png_ptr*/) { … } // Libpng user error function which allows us to print libpng errors using // Chrome's logging facilities instead of stderr. void LogLibPNGError(png_structp png_ptr, png_const_charp error_msg) { … } // Goes with LogLibPNGError, above. void LogLibPNGWarning(png_structp png_ptr, png_const_charp warning_msg) { … } // Color types supported by EncodeImage. Required because neither libpng nor // PNGCodec::Encode supports all of the required values. enum ColorType { … }; constexpr size_t PixelBytesForColorType(ColorType color_type) { … } std::tuple<uint8_t, uint8_t, uint8_t> Read3(const std::vector<uint8_t>& pixels, size_t base) { … } std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> Read4( const std::vector<uint8_t>& pixels, size_t base) { … } struct ImageSpec { … }; SkColor ImageSpec::ReadPixel(int x, int y) const { … } bool ImagesExactlyEqual(const ImageSpec& a, const ImageSpec& b) { … } bool ImageExactlyEqualsSkBitmap(const ImageSpec& a, const SkBitmap& b) { … } // PNG encoder used for testing. Required because PNGCodec::Encode doesn't do // interlaced, palette-based, or grayscale images, but PNGCodec::Decode is // actually asked to decode these types of images by Chrome. bool EncodeImage(const std::vector<unsigned char>& input, const int width, const int height, ColorType output_color_type, std::vector<unsigned char>* output, const int interlace_type = PNG_INTERLACE_NONE, std::vector<png_color>* palette = 0, std::vector<unsigned char>* palette_alpha = 0) { … } } // namespace // Returns true if each channel of the given two colors are "close." This is // used for comparing colors where rounding errors may cause off-by-one. bool ColorsClose(uint32_t a, uint32_t b) { … } // Returns true if the RGB components are "close." bool NonAlphaColorsClose(uint32_t a, uint32_t b) { … } // Returns true if the BGRA 32-bit SkColor specified by |a| is equivalent to the // 8-bit Gray color specified by |b|. bool BGRAGrayEqualsA8Gray(uint32_t a, uint8_t b) { … } void MakeTestBGRASkBitmap(int w, int h, SkBitmap* bmp) { … } void MakeTestA8SkBitmap(int w, int h, SkBitmap* bmp) { … } TEST(PNGCodec, EncodeDecodeRGBA) { … } TEST(PNGCodec, EncodeDecodeBGRA) { … } TEST(PNGCodec, DecodePalette) { … } TEST(PNGCodec, DecodeInterlacedPalette) { … } TEST(PNGCodec, DecodeGrayscale) { … } TEST(PNGCodec, DecodeGrayscaleWithAlpha) { … } TEST(PNGCodec, DecodeInterlacedGrayscale) { … } TEST(PNGCodec, DecodeInterlacedGrayscaleWithAlpha) { … } TEST(PNGCodec, DecodeInterlacedRGBA) { … } TEST(PNGCodec, DecodeInterlacedBGR) { … } TEST(PNGCodec, DecodeInterlacedBGRA) { … } // Not encoding an interlaced PNG from SkBitmap because we don't do it // anywhere, and the ability to do that requires more code changes. TEST(PNGCodec, DecodeInterlacedRGBtoSkBitmap) { … } void DecodeInterlacedRGBAtoSkBitmap(bool use_transparency) { … } TEST(PNGCodec, DecodeInterlacedRGBAtoSkBitmap_Opaque) { … } TEST(PNGCodec, DecodeInterlacedRGBAtoSkBitmap_Transparent) { … } TEST(PNGCodec, EncoderSavesImagesWithAllOpaquePixelsAsOpaque) { … } // Test that corrupted data decompression causes failures. TEST(PNGCodec, DecodeCorrupted) { … } // Test decoding three PNG images, identical except for different gAMA chunks // (with gamma values of 1.0, 1.8 and 2.2). All images are 256 x 256 pixels and // 8-bit grayscale. The left half of the image is a solid block of medium gray // (128 out of 255). The right half of the image alternates between black (0 // out of 255) and white (255 out of 255) in a checkerboard pattern. // // For the first file (gamma 1.0, linear), if you squint, the 128/255 left half // should look about as bright as the checkerboard right half. PNGCodec::Decode // applies gamma correction (assuming a default display gamma of 2.2), so the // top left pixel value should be corrected from 128 to 186. // // The second file (gamma 1.8)'s correction is not as strong: from 128 to 145. // // The third file (gamma 2.2) matches the default display gamma and so the 128 // nominal value is unchanged. If you squint, the 128/255 left half should look // darker than the right half. // // The "as used by libpng" formula for calculating these expected 186, 145 or // (unchanged) 128 values can be seen in the diff at // https://crrev.com/c/5402327/13/ui/gfx/codec/png_codec_unittest.cc // and the same formula is at // https://www.w3.org/TR/2003/REC-PNG-20031110/#13Decoder-gamma-handling but // note the spec's caveat: "Viewers capable of full colour management... will // perform more sophisticated calculations than those described here." // // Being corrected to 186, 145 or 128 assumes that, like libpng, the PNG // decoder honors the gAMA chunk in the checkerboard.gamma*.png files. Those // files don't have an iCCP color profile chunk, but since the PNGCodec::Decode // API fills in a bag of RGBA pixels (without an associated colorspace), the // PNGCodec::Decode implementation nonetheless applies sRGB color correction // (approximately exponential) instead of basic gamma correction (literally // exponential). This produces slightly different numbers: 188, 146 or 129. The // code review in https://crrev.com/c/5402327 gives a little more context. // // When viewing these images in a browser, make sure to apply the "img { // image-rendering: pixelated }" CSS. Otherwise, browsers will often blur when // up-scaling (e.g. on high DPI displays), trumping the "two halves should have // roughly equal / different brightness" effect. You can view the images at // https://nigeltao.github.io/blog/2022/gamma-aware-pixelated-images.html TEST(PNGCodec, DecodeGamma) { … } TEST(PNGCodec, EncodeBGRASkBitmapStridePadded) { … } TEST(PNGCodec, EncodeBGRASkBitmap) { … } TEST(PNGCodec, EncodeA8SkBitmap) { … } TEST(PNGCodec, EncodeBGRASkBitmapDiscardTransparency) { … } TEST(PNGCodec, EncodeWithComment) { … } TEST(PNGCodec, EncodeDecodeWithVaryingCompressionLevels) { … } TEST(PNGCodec, DecodingTruncatedEXIFChunkIsSafe) { … } } // namespace gfx