#include "ui/gfx/codec/png_codec.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <optional>
#include "base/base_paths.h"
#include "base/check.h"
#include "base/compiler_specific.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/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.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/SkUnPreMultiply.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/skia_util.h"
namespace gfx {
namespace {
std::vector<uint8_t> MakeRGBImage(int width, int height) {
std::vector<uint8_t> data;
data.resize(width * height * 3);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
size_t base = (y * width + x) * 3;
data[base] = x * 3;
data[base + 1] = x * 3 + 1;
data[base + 2] = x * 3 + 2;
}
}
return data;
}
std::vector<uint8_t> MakeRGBAImage(int width,
int height,
bool use_transparency) {
std::vector<uint8_t> result;
result.resize(width * height * 4);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
size_t base = (y * width + x) * 4;
result[base] = x * 3;
result[base + 1] = x * 3 + 1;
result[base + 2] = x * 3 + 2;
if (use_transparency) {
result[base + 3] = x * 3 + 3;
} else {
result[base + 3] = 0xFF;
}
}
}
return result;
}
struct PaletteImage {
std::vector<uint8_t> data;
std::vector<png_color> palette;
std::vector<uint8_t> trans_chunk;
};
PaletteImage MakePaletteImage(int width, int height) {
PaletteImage image;
image.data.resize(width * height);
image.palette.resize(width);
for (int i = 0; i < width; ++i) {
png_color& color = image.palette[i];
color.red = i * 3;
color.green = color.red + 1;
color.blue = color.red + 2;
}
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
image.data[y * width + x] = x;
}
}
image.trans_chunk.resize(image.palette.size());
for (std::size_t i = 0; i < image.trans_chunk.size(); ++i) {
image.trans_chunk[i] = i % 256;
}
return image;
}
std::vector<uint8_t> MakeGrayscaleImage(int width, int height) {
std::vector<uint8_t> data;
data.resize(width * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
data[y * width + x] = x;
}
}
return data;
}
std::vector<uint8_t> MakeGrayscaleAlphaImage(int width, int height) {
std::vector<uint8_t> data;
data.resize(width * height * 2);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
size_t base = (y * width + x) * 2;
data[base] = x;
data[base + 1] = y;
}
}
return data;
}
void WriteImageData(png_structp png_ptr, png_bytep data, png_size_t length) {
std::vector<uint8_t>& v =
*static_cast<std::vector<uint8_t>*>(png_get_io_ptr(png_ptr));
v.resize(v.size() + length);
UNSAFE_TODO(memcpy(&v[v.size() - length], data, length));
}
void FlushImageData(png_structp ) {}
void LogLibPNGError(png_structp png_ptr, png_const_charp error_msg) {
DLOG(ERROR) << "libpng encode error: " << error_msg;
longjmp(png_jmpbuf(png_ptr), 1);
}
void LogLibPNGWarning(png_structp png_ptr, png_const_charp warning_msg) {
DLOG(ERROR) << "libpng encode warning: " << warning_msg;
}
enum ColorType {
COLOR_TYPE_GRAY = PNG_COLOR_TYPE_GRAY,
COLOR_TYPE_GRAY_ALPHA = PNG_COLOR_TYPE_GRAY_ALPHA,
COLOR_TYPE_PALETTE = PNG_COLOR_TYPE_PALETTE,
COLOR_TYPE_RGB = PNG_COLOR_TYPE_RGB,
COLOR_TYPE_RGBA = PNG_COLOR_TYPE_RGBA,
COLOR_TYPE_BGR,
COLOR_TYPE_BGRA,
COLOR_TYPE_RGBX
};
constexpr size_t PixelBytesForColorType(ColorType color_type) {
switch (color_type) {
case COLOR_TYPE_GRAY:
return 1;
case COLOR_TYPE_GRAY_ALPHA:
return 2;
case COLOR_TYPE_PALETTE:
return 1;
case COLOR_TYPE_RGB:
return 3;
case COLOR_TYPE_RGBA:
return 4;
case COLOR_TYPE_BGR:
return 3;
case COLOR_TYPE_BGRA:
return 4;
case COLOR_TYPE_RGBX:
return 4;
}
NOTREACHED();
}
std::tuple<uint8_t, uint8_t, uint8_t> Read3(const std::vector<uint8_t>& pixels,
size_t base) {
return std::tie(pixels[base], pixels[base + 1], pixels[base + 2]);
}
std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> Read4(
const std::vector<uint8_t>& pixels,
size_t base) {
return std::tie(pixels[base], pixels[base + 1], pixels[base + 2],
pixels[base + 3]);
}
struct ImageSpec {
ImageSpec(int width,
int height,
const std::vector<uint8_t>& bytes,
ColorType type)
: width(width), height(height), bytes(bytes), type(type) {}
ImageSpec(int width,
int height,
const std::vector<uint8_t>& bytes,
ColorType type,
const std::vector<png_color>& palette,
const std::vector<uint8_t>& trans)
: width(width),
height(height),
bytes(bytes),
type(type),
palette(palette),
trans(trans) {}
int width;
int height;
std::vector<uint8_t> bytes;
ColorType type;
std::vector<png_color> palette;
std::vector<uint8_t> trans;
SkColor ReadPixel(int x, int y) const;
};
SkColor ImageSpec::ReadPixel(int x, int y) const {
size_t base = (y * width + x) * PixelBytesForColorType(type);
uint8_t red = 0, green = 0, blue = 0, alpha = SK_AlphaOPAQUE;
switch (type) {
case COLOR_TYPE_GRAY:
red = green = blue = bytes[base];
break;
case COLOR_TYPE_GRAY_ALPHA:
red = green = blue = bytes[base];
alpha = bytes[base + 1];
break;
case COLOR_TYPE_PALETTE:
red = palette[bytes[base]].red;
green = palette[bytes[base]].green;
blue = palette[bytes[base]].blue;
alpha = trans[bytes[base]];
break;
case COLOR_TYPE_RGB:
case COLOR_TYPE_RGBX:
std::tie(red, green, blue) = Read3(bytes, base);
break;
case COLOR_TYPE_RGBA:
std::tie(red, green, blue, alpha) = Read4(bytes, base);
break;
case COLOR_TYPE_BGR:
std::tie(blue, green, red) = Read3(bytes, base);
break;
case COLOR_TYPE_BGRA:
std::tie(blue, green, red, alpha) = Read4(bytes, base);
break;
default:
NOTREACHED();
}
return SkColorSetARGB(alpha, red, green, blue);
}
bool ImagesExactlyEqual(const ImageSpec& a, const ImageSpec& b) {
if (a.width != b.width || a.height != b.height) {
return false;
}
for (int x = 0; x < a.width; ++x) {
for (int y = 0; y < a.height; ++y) {
if (a.ReadPixel(x, y) != b.ReadPixel(x, y)) {
return false;
}
}
}
return true;
}
bool ImageExactlyEqualsSkBitmap(const ImageSpec& a, const SkBitmap& b) {
if (a.width != b.width() || a.height != b.height()) {
return false;
}
for (int x = 0; x < a.width; ++x) {
for (int y = 0; y < a.height; ++y) {
SkColor ca = a.ReadPixel(x, y);
uint32_t cb = *b.getAddr32(x, y);
if (SkPreMultiplyColor(ca) != cb) {
return false;
}
}
}
return true;
}
std::optional<std::vector<uint8_t>> EncodeImage(
base::span<const uint8_t> input,
int width,
int height,
ColorType output_color_type,
int interlace_type = PNG_INTERLACE_NONE,
std::optional<base::span<const png_color>> palette = std::nullopt,
std::optional<base::span<const uint8_t>> palette_alpha = std::nullopt) {
std::vector<uint8_t> output;
int input_rowbytes = width * PixelBytesForColorType(output_color_type);
int transforms = PNG_TRANSFORM_IDENTITY;
if (output_color_type == COLOR_TYPE_PALETTE && !palette) {
return std::nullopt;
}
if (output_color_type == COLOR_TYPE_RGBX) {
output_color_type = COLOR_TYPE_RGB;
transforms |= PNG_TRANSFORM_STRIP_FILLER_AFTER;
} else if (output_color_type == COLOR_TYPE_BGR) {
output_color_type = COLOR_TYPE_RGB;
transforms |= PNG_TRANSFORM_BGR;
} else if (output_color_type == COLOR_TYPE_BGRA) {
output_color_type = COLOR_TYPE_RGBA;
transforms |= PNG_TRANSFORM_BGR;
}
png_struct* png_ptr =
png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr,
nullptr, nullptr);
if (!png_ptr) {
return std::nullopt;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, nullptr);
return std::nullopt;
}
const auto rows = static_cast<size_t>(height);
std::vector<png_bytep> row_pointers(rows);
for (size_t y = 0; y < rows; ++y) {
row_pointers[y] = const_cast<uint8_t*>(&input[y * input_rowbytes]);
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
return std::nullopt;
}
png_set_error_fn(png_ptr, nullptr, LogLibPNGError, LogLibPNGWarning);
png_set_rows(png_ptr, info_ptr, &row_pointers[0]);
png_set_write_fn(png_ptr, &output, WriteImageData, FlushImageData);
png_set_IHDR(png_ptr, info_ptr, width, height, 8, output_color_type,
interlace_type, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
if (output_color_type == COLOR_TYPE_PALETTE) {
png_set_PLTE(png_ptr, info_ptr, palette->data(), palette->size());
if (palette_alpha) {
png_set_tRNS(png_ptr, info_ptr, palette_alpha->data(),
palette_alpha->size(), nullptr);
}
}
png_write_png(png_ptr, info_ptr, transforms, nullptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
return output;
}
}
bool ColorsClose(uint32_t a, uint32_t b) {
return abs(static_cast<int>(SkColorGetB(a) - SkColorGetB(b))) < 2 &&
abs(static_cast<int>(SkColorGetG(a) - SkColorGetG(b))) < 2 &&
abs(static_cast<int>(SkColorGetR(a) - SkColorGetR(b))) < 2 &&
abs(static_cast<int>(SkColorGetA(a) - SkColorGetA(b))) < 2;
}
bool NonAlphaColorsClose(uint32_t a, uint32_t b) {
return abs(static_cast<int>(SkColorGetB(a) - SkColorGetB(b))) < 2 &&
abs(static_cast<int>(SkColorGetG(a) - SkColorGetG(b))) < 2 &&
abs(static_cast<int>(SkColorGetR(a) - SkColorGetR(b))) < 2;
}
bool BGRAGrayEqualsA8Gray(uint32_t a, uint8_t b) {
return SkColorGetB(a) == b && SkColorGetG(a) == b && SkColorGetR(a) == b &&
SkColorGetA(a) == 255;
}
void MakeTestBGRASkBitmap(int width, int height, SkBitmap* bmp) {
bmp->allocN32Pixels(width, height);
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
int i = y * width + x;
*bmp->getAddr32(x, y) =
SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240);
}
}
}
void MakeTestA8SkBitmap(int width, int height, SkBitmap* bmp) {
bmp->allocPixels(SkImageInfo::MakeA8(width, height));
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
*bmp->getAddr8(x, y) = y * width + x;
}
}
}
TEST(PNGCodecTest, EncodeDecodeRGBA) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original =
MakeRGBAImage(kWidth, kHeight, true);
std::optional<std::vector<uint8_t>> encoded = PNGCodec::Encode(
original.data(), PNGCodec::FORMAT_RGBA, Size(kWidth, kHeight), kWidth * 4,
false, std::vector<PNGCodec::Comment>());
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output;
{
base::HistogramTester histograms;
output = PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_RGBA);
ASSERT_TRUE(output);
std::vector<base::Bucket> buckets =
histograms.GetAllSamples("ImageDecoder.Png.UiGfxIntoVector");
ASSERT_EQ(buckets.size(), 1u);
ASSERT_GE(buckets[0].min, 0);
}
EXPECT_TRUE(
ImagesExactlyEqual(ImageSpec(kWidth, kHeight, original, COLOR_TYPE_RGBA),
ImageSpec(output->width, output->height,
output->output, COLOR_TYPE_RGBA)));
}
TEST(PNGCodecTest, EncodeDecodeBGRA) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original =
MakeRGBAImage(kWidth, kHeight, true);
std::optional<std::vector<uint8_t>> encoded = PNGCodec::Encode(
original.data(), PNGCodec::FORMAT_BGRA, Size(kWidth, kHeight), kWidth * 4,
false, std::vector<PNGCodec::Comment>());
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_BGRA);
ASSERT_TRUE(output);
EXPECT_TRUE(
ImagesExactlyEqual(ImageSpec(kWidth, kHeight, original, COLOR_TYPE_BGRA),
ImageSpec(output->width, output->height,
output->output, COLOR_TYPE_BGRA)));
}
TEST(PNGCodecTest, DecodePalette) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
PaletteImage original = MakePaletteImage(kWidth, kHeight);
std::optional<std::vector<uint8_t>> encoded =
EncodeImage(original.data, kWidth, kHeight, COLOR_TYPE_PALETTE,
PNG_INTERLACE_NONE, original.palette, original.trans_chunk);
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_RGBA);
ASSERT_TRUE(output);
EXPECT_TRUE(ImagesExactlyEqual(
ImageSpec(kWidth, kHeight, original.data, COLOR_TYPE_PALETTE,
original.palette, original.trans_chunk),
ImageSpec(output->width, output->height, output->output,
COLOR_TYPE_RGBA)));
}
TEST(PNGCodecTest, DecodeInterlacedPalette) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
PaletteImage original = MakePaletteImage(kWidth, kHeight);
std::optional<std::vector<uint8_t>> encoded =
EncodeImage(original.data, kWidth, kHeight, COLOR_TYPE_PALETTE,
PNG_INTERLACE_ADAM7, original.palette, original.trans_chunk);
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_RGBA);
ASSERT_TRUE(output);
EXPECT_TRUE(ImagesExactlyEqual(
ImageSpec(kWidth, kHeight, original.data, COLOR_TYPE_PALETTE,
original.palette, original.trans_chunk),
ImageSpec(output->width, output->height, output->output,
COLOR_TYPE_RGBA)));
}
TEST(PNGCodecTest, DecodeGrayscale) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original = MakeGrayscaleImage(kWidth, kHeight);
std::optional<std::vector<uint8_t>> encoded =
EncodeImage(original, kWidth, kHeight, COLOR_TYPE_GRAY);
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_RGBA);
ASSERT_TRUE(output);
EXPECT_TRUE(
ImagesExactlyEqual(ImageSpec(kWidth, kHeight, original, COLOR_TYPE_GRAY),
ImageSpec(output->width, output->height,
output->output, COLOR_TYPE_RGBA)));
}
TEST(PNGCodecTest, DecodeGrayscaleWithAlpha) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original = MakeGrayscaleAlphaImage(kWidth, kHeight);
std::optional<std::vector<uint8_t>> encoded =
EncodeImage(original, kWidth, kHeight, COLOR_TYPE_GRAY_ALPHA);
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_RGBA);
ASSERT_TRUE(output);
EXPECT_TRUE(ImagesExactlyEqual(
ImageSpec(kWidth, kHeight, original, COLOR_TYPE_GRAY_ALPHA),
ImageSpec(output->width, output->height, output->output,
COLOR_TYPE_RGBA)));
}
TEST(PNGCodecTest, DecodeInterlacedGrayscale) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original = MakeGrayscaleImage(kWidth, kHeight);
std::optional<std::vector<uint8_t>> encoded =
EncodeImage(original, kWidth, kHeight, COLOR_TYPE_GRAY);
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_RGBA);
ASSERT_TRUE(output);
EXPECT_TRUE(
ImagesExactlyEqual(ImageSpec(kWidth, kHeight, original, COLOR_TYPE_GRAY),
ImageSpec(output->width, output->height,
output->output, COLOR_TYPE_RGBA)));
}
TEST(PNGCodecTest, DecodeInterlacedGrayscaleWithAlpha) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original = MakeGrayscaleAlphaImage(kWidth, kHeight);
std::optional<std::vector<uint8_t>> encoded = EncodeImage(
original, kWidth, kHeight, COLOR_TYPE_GRAY_ALPHA, PNG_INTERLACE_ADAM7);
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_RGBA);
ASSERT_TRUE(output);
EXPECT_TRUE(ImagesExactlyEqual(
ImageSpec(kWidth, kHeight, original, COLOR_TYPE_GRAY_ALPHA),
ImageSpec(output->width, output->height, output->output,
COLOR_TYPE_RGBA)));
}
TEST(PNGCodecTest, DecodeInterlacedRGBA) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original =
MakeRGBAImage(kWidth, kHeight, false);
std::optional<std::vector<uint8_t>> encoded = EncodeImage(
original, kWidth, kHeight, COLOR_TYPE_RGBA, PNG_INTERLACE_ADAM7);
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_RGBA);
ASSERT_TRUE(output);
EXPECT_TRUE(
ImagesExactlyEqual(ImageSpec(kWidth, kHeight, original, COLOR_TYPE_RGBA),
ImageSpec(output->width, output->height,
output->output, COLOR_TYPE_RGBA)));
}
TEST(PNGCodecTest, DecodeInterlacedBGR) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original = MakeRGBImage(kWidth, kHeight);
std::optional<std::vector<uint8_t>> encoded = EncodeImage(
original, kWidth, kHeight, COLOR_TYPE_BGR, PNG_INTERLACE_ADAM7);
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_BGRA);
ASSERT_TRUE(output);
EXPECT_TRUE(
ImagesExactlyEqual(ImageSpec(kWidth, kHeight, original, COLOR_TYPE_BGR),
ImageSpec(output->width, output->height,
output->output, COLOR_TYPE_BGRA)));
}
TEST(PNGCodecTest, DecodeInterlacedBGRA) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original = MakeRGBAImage(kWidth, kHeight, false);
std::optional<std::vector<uint8_t>> encoded = EncodeImage(
original, kWidth, kHeight, COLOR_TYPE_BGRA, PNG_INTERLACE_ADAM7);
ASSERT_TRUE(encoded);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(encoded.value(), PNGCodec::FORMAT_BGRA);
ASSERT_TRUE(output);
EXPECT_TRUE(ImagesExactlyEqual(
ImageSpec(kWidth, kHeight, original, COLOR_TYPE_BGRA),
ImageSpec(output->width, output->height, original, COLOR_TYPE_BGRA)));
}
TEST(PNGCodecTest, DecodeInterlacedRGBtoSkBitmap) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original = MakeRGBImage(kWidth, kHeight);
std::optional<std::vector<uint8_t>> encoded = EncodeImage(
original, kWidth, kHeight, COLOR_TYPE_RGB, PNG_INTERLACE_ADAM7);
ASSERT_TRUE(encoded);
SkBitmap decoded_bitmap;
{
base::HistogramTester histograms;
decoded_bitmap = PNGCodec::Decode(encoded.value());
ASSERT_FALSE(decoded_bitmap.isNull());
std::vector<base::Bucket> buckets =
histograms.GetAllSamples("ImageDecoder.Png.UiGfxIntoSkBitmap");
ASSERT_EQ(buckets.size(), 1u);
ASSERT_GE(buckets[0].min, 0);
}
EXPECT_EQ(decoded_bitmap.alphaType(), kOpaque_SkAlphaType);
EXPECT_TRUE(ImageExactlyEqualsSkBitmap(
ImageSpec(kWidth, kHeight, original, COLOR_TYPE_RGB), decoded_bitmap));
}
void DecodeInterlacedRGBAtoSkBitmap(bool use_transparency) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
const ColorType color_type =
use_transparency ? COLOR_TYPE_RGBA : COLOR_TYPE_RGBX;
std::vector<uint8_t> original =
MakeRGBAImage(kWidth, kHeight, use_transparency);
std::optional<std::vector<uint8_t>> encoded =
EncodeImage(original, kWidth, kHeight, color_type, PNG_INTERLACE_ADAM7);
ASSERT_TRUE(encoded);
SkBitmap decoded_bitmap = PNGCodec::Decode(encoded.value());
ASSERT_FALSE(decoded_bitmap.isNull());
EXPECT_EQ(decoded_bitmap.alphaType(),
use_transparency ? kPremul_SkAlphaType : kOpaque_SkAlphaType);
EXPECT_TRUE(ImageExactlyEqualsSkBitmap(
ImageSpec(kWidth, kHeight, original, color_type), decoded_bitmap));
}
TEST(PNGCodecTest, DecodeInterlacedRGBAtoSkBitmap_Opaque) {
DecodeInterlacedRGBAtoSkBitmap(false);
}
TEST(PNGCodecTest, DecodeInterlacedRGBAtoSkBitmap_Transparent) {
DecodeInterlacedRGBAtoSkBitmap(true);
}
TEST(PNGCodecTest, EncoderSavesImagesWithAllOpaquePixelsAsOpaque) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original =
MakeRGBAImage(kWidth, kHeight, false);
std::optional<std::vector<uint8_t>> png_data = PNGCodec::Encode(
original.data(), PNGCodec::FORMAT_RGBA, gfx::Size(kWidth, kHeight),
kWidth * 4,
false, std::vector<PNGCodec::Comment>{});
ASSERT_TRUE(png_data);
SkBitmap bitmap = PNGCodec::Decode(png_data.value());
ASSERT_FALSE(bitmap.isNull());
EXPECT_EQ(bitmap.info().alphaType(), kOpaque_SkAlphaType);
}
TEST(PNGCodecTest, DecodeCorrupted) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
std::vector<uint8_t> original = MakeRGBAImage(kWidth, kHeight, false);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(original, PNGCodec::FORMAT_RGBA);
ASSERT_FALSE(output);
std::optional<std::vector<uint8_t>> compressed = PNGCodec::Encode(
&original[0], PNGCodec::FORMAT_RGBA, Size(kWidth, kHeight), kWidth * 4,
false, std::vector<PNGCodec::Comment>());
ASSERT_TRUE(compressed);
output = PNGCodec::Decode(
base::span(compressed.value()).first(compressed.value().size() / 2),
PNGCodec::FORMAT_RGBA);
ASSERT_FALSE(output);
for (int i = 10; i < 30; i++) {
compressed.value()[i] = i;
}
output = PNGCodec::Decode(compressed.value(), PNGCodec::FORMAT_RGBA);
ASSERT_FALSE(output);
}
TEST(PNGCodecTest, DecodeGamma) {
base::FilePath root_dir;
ASSERT_TRUE(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &root_dir));
base::FilePath data_dir = root_dir.AppendASCII("ui")
.AppendASCII("gfx")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("codec");
struct SourceFile {
double gamma;
uint8_t expected;
std::string filename;
};
const SourceFile kSourceFiles[] = {
{1.0, 188, "checkerboard.gamma1dot0.png"},
{1.8, 146, "checkerboard.gamma1dot8.png"},
{2.2, 128, "checkerboard.gamma2dot2.png"},
};
for (const auto& sf : kSourceFiles) {
base::FilePath filename = data_dir.AppendASCII(sf.filename);
std::optional<const std::vector<uint8_t>> opt_input =
base::ReadFileToBytes(filename);
ASSERT_TRUE(opt_input.has_value()) << "failed to load: " << filename;
const std::vector<uint8_t>& input = opt_input.value();
ASSERT_GT(input.size(), 0u);
std::optional<PNGCodec::DecodeOutput> output =
PNGCodec::Decode(input, PNGCodec::FORMAT_RGBA);
ASSERT_TRUE(output);
ASSERT_GT(output->output.size(), 0u);
EXPECT_EQ(sf.expected, output->output[0]) << "gamma: " << sf.gamma;
}
}
TEST(PNGCodecTest, EncodeBGRASkBitmapStridePadded) {
const int kWidth = 20;
const int kHeight = 20;
const int kPaddedWidth = 32;
const int kBytesPerPixel = 4;
const int kRowBytes = kPaddedWidth * kBytesPerPixel;
std::vector<uint32_t> original_pixels(kHeight * kPaddedWidth);
SkImageInfo info = SkImageInfo::MakeN32Premul(kWidth, kHeight);
SkBitmap original_bitmap;
original_bitmap.setInfo(info, kRowBytes);
original_bitmap.setPixels(original_pixels.data());
for (size_t i = 0; i < original_pixels.size(); i++) {
original_pixels[i] = SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240);
}
std::optional<std::vector<uint8_t>> encoded = PNGCodec::EncodeBGRASkBitmap(
original_bitmap, false);
ASSERT_TRUE(encoded);
SkBitmap decoded_bitmap = PNGCodec::Decode(encoded.value());
ASSERT_FALSE(decoded_bitmap.isNull());
for (int x = 0; x < kWidth; x++) {
for (int y = 0; y < kHeight; y++) {
uint32_t original_pixel = *original_bitmap.getAddr32(x, y);
uint32_t decoded_pixel = *decoded_bitmap.getAddr32(x, y);
ASSERT_TRUE(ColorsClose(original_pixel, decoded_pixel))
<< "; original_pixel = " << std::hex << std::setw(8) << original_pixel
<< "; decoded_pixel = " << std::hex << std::setw(8) << decoded_pixel;
}
}
}
TEST(PNGCodecTest, EncodeBGRASkBitmap) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
SkBitmap original_bitmap;
MakeTestBGRASkBitmap(kWidth, kHeight, &original_bitmap);
std::optional<std::vector<uint8_t>> encoded = PNGCodec::EncodeBGRASkBitmap(
original_bitmap, false);
ASSERT_TRUE(encoded);
SkBitmap decoded_bitmap = PNGCodec::Decode(encoded.value());
ASSERT_FALSE(decoded_bitmap.isNull());
for (int x = 0; x < kWidth; x++) {
for (int y = 0; y < kHeight; y++) {
uint32_t original_pixel = *original_bitmap.getAddr32(x, y);
uint32_t decoded_pixel = *decoded_bitmap.getAddr32(x, y);
ASSERT_TRUE(ColorsClose(original_pixel, decoded_pixel))
<< "; original_pixel = " << std::hex << std::setw(8) << original_pixel
<< "; decoded_pixel = " << std::hex << std::setw(8) << decoded_pixel;
}
}
}
TEST(PNGCodecTest, EncodeBGRASkBitmapDiscardTransparency) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
SkBitmap original_bitmap;
MakeTestBGRASkBitmap(kWidth, kHeight, &original_bitmap);
std::optional<std::vector<uint8_t>> encoded = PNGCodec::EncodeBGRASkBitmap(
original_bitmap, true);
ASSERT_TRUE(encoded);
SkBitmap decoded_bitmap = PNGCodec::Decode(encoded.value());
ASSERT_FALSE(decoded_bitmap.isNull());
for (int x = 0; x < kWidth; x++) {
for (int y = 0; y < kHeight; y++) {
uint32_t original_pixel = *original_bitmap.getAddr32(x, y);
uint32_t unpremultiplied =
SkUnPreMultiply::PMColorToColor(original_pixel);
uint32_t decoded_pixel = *decoded_bitmap.getAddr32(x, y);
uint32_t unpremultiplied_decoded =
SkUnPreMultiply::PMColorToColor(decoded_pixel);
EXPECT_TRUE(NonAlphaColorsClose(unpremultiplied, unpremultiplied_decoded))
<< "Original_pixel: (" << SkColorGetR(unpremultiplied) << ", "
<< SkColorGetG(unpremultiplied) << ", "
<< SkColorGetB(unpremultiplied) << "), "
<< "Decoded pixel: (" << SkColorGetR(unpremultiplied_decoded) << ", "
<< SkColorGetG(unpremultiplied_decoded) << ", "
<< SkColorGetB(unpremultiplied_decoded) << ")";
}
}
}
TEST(PNGCodecTest, EncodeWithComment) {
constexpr int kWidth = 10;
constexpr int kHeight = 10;
std::vector<uint8_t> original =
MakeRGBAImage(kWidth, kHeight, true);
std::vector<PNGCodec::Comment> comments;
comments.emplace_back("key", "text");
comments.emplace_back("test", "something");
comments.emplace_back("have some", "spaces in both");
std::optional<std::vector<uint8_t>> encoded =
PNGCodec::Encode(original.data(), PNGCodec::FORMAT_RGBA,
Size(kWidth, kHeight), kWidth * 4, false, comments);
ASSERT_TRUE(encoded);
const uint8_t kExpected1[] =
"\x00\x00\x00\x08tEXtkey\x00text\x9e\xe7\x66\x51";
const uint8_t kExpected2[] =
"\x00\x00\x00\x0etEXttest\x00something\x29\xba\xef\xac";
const uint8_t kExpected3[] =
"\x00\x00\x00\x18tEXthave some\x00spaces in both\x8d\x69\x34\x2d";
EXPECT_NE(std::ranges::search(encoded.value(), kExpected1).begin(),
encoded.value().end());
EXPECT_NE(std::ranges::search(encoded.value(), kExpected2).begin(),
encoded.value().end());
EXPECT_NE(std::ranges::search(encoded.value(), kExpected3).begin(),
encoded.value().end());
}
TEST(PNGCodecTest, EncodeDecodeWithVaryingCompressionLevels) {
constexpr int kWidth = 20;
constexpr int kHeight = 20;
SkBitmap original_bitmap;
MakeTestBGRASkBitmap(kWidth, kHeight, &original_bitmap);
std::optional<std::vector<uint8_t>> encoded_normal =
PNGCodec::EncodeBGRASkBitmap(original_bitmap,
false);
ASSERT_TRUE(encoded_normal);
std::optional<std::vector<uint8_t>> encoded_fast =
PNGCodec::FastEncodeBGRASkBitmap(original_bitmap,
false);
ASSERT_TRUE(encoded_fast);
EXPECT_NE(encoded_normal->size(), encoded_fast->size());
SkBitmap decoded = PNGCodec::Decode(encoded_normal.value());
EXPECT_FALSE(decoded.isNull());
EXPECT_TRUE(BitmapsAreEqual(decoded, original_bitmap));
decoded = PNGCodec::Decode(encoded_fast.value());
EXPECT_FALSE(decoded.isNull());
EXPECT_TRUE(BitmapsAreEqual(decoded, original_bitmap));
}
TEST(PNGCodecTest, DecodingTruncatedEXIFChunkIsSafe) {
static constexpr png_byte kPNGData[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0,
0x08, 0x06, 0x00, 0x00, 0x00, 0x3e, 0x55, 0xe9, 0x92, 0x00, 0x00, 0x00,
0x95, 0x65, 0x58, 0x49, 0x66, 0x89, 0x47, 0x50, 0x4e, 0x0d, 0x0a, 0x1a,
0x0a, 0x00, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x61, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x26, 0x0b, 0x13, 0x01,
0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45,
0x07, 0x7d, 0x01, 0x1a, 0x16, 0x3b, 0x05, 0xc3, 0xff, 0x6f, 0x00, 0x00,
0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0xb2, 0x43, 0x6f, 0x6d, 0x2d, 0x65,
0xa0, 0x6e, 0x74, 0x00, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x00, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20,
0x47, 0x49, 0x4d, 0xe2, 0x35, 0x87, 0xc3, 0xa1, 0x00, 0x00, 0x00, 0x49,
0x45, 0x4e, 0x44, 0xef, 0x04, 0x3e, 0x00, 0xbf, 0x00, 0xae, 0x49, 0x44,
0x41, 0x54, 0x68, 0x81, 0xed, 0xd5, 0x6b, 0x99, 0x25, 0x2e, 0xff, 0xff,
0x00, 0xae, 0x79, 0x79, 0x79, 0x42, 0x60, 0x69, 0x82, 0x79, 0x79, 0x79,
0xf0, 0x7e,
};
SkBitmap bitmap = PNGCodec::Decode(kPNGData);
EXPECT_TRUE(bitmap.isNull());
}
}