blob: 64ce821cc679cb5ef1d975ec2d9d5984772c8d5a [file] [log] [blame]
// Copyright 2025 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "testing/image_diff/image_diff_png.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/numerics/checked_math.h"
#include "core/fxcrt/span.h"
#include "core/fxcrt/span_util.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkColorType.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkStream.h"
#ifdef PDF_ENABLE_RUST_PNG
#include "third_party/skia/include/codec/SkPngRustDecoder.h"
#include "third_party/skia/include/encode/SkPngRustEncoder.h"
#else
#include "third_party/skia/include/codec/SkPngDecoder.h"
#include "third_party/skia/include/encode/SkPngEncoder.h"
#endif
namespace image_diff_png {
namespace {
std::vector<uint8_t> EncodePNG(pdfium::span<const uint8_t> input,
SkColorType color,
SkAlphaType alpha,
int width,
int height,
size_t row_byte_width) {
SkImageInfo info =
SkImageInfo::Make(width, height, color, alpha, SkColorSpace::MakeSRGB());
CHECK_NE(info.minRowBytes(), 0); // 0 means conversion problems.
CHECK_LE(info.minRowBytes(), row_byte_width);
CHECK_NE(info.computeMinByteSize(), 0); // 0 means conversion problems.
CHECK_LE(info.computeMinByteSize(), input.size());
SkPixmap pixmap(info, input.data(), row_byte_width);
SkDynamicMemoryWStream output;
#ifdef PDF_ENABLE_RUST_PNG
bool success = SkPngRustEncoder::Encode(&output, pixmap, {});
#else
bool success = SkPngEncoder::Encode(&output, pixmap, {});
#endif
if (!success) {
return {};
}
return output.detachAsVector();
}
} // namespace
std::vector<uint8_t> DecodePNG(pdfium::span<const uint8_t> input,
bool reverse_byte_order,
int* width,
int* height) {
CHECK(width);
CHECK(height);
auto stream = std::make_unique<SkMemoryStream>(input.data(), input.size(),
/*copyData=*/false);
#ifdef PDF_ENABLE_RUST_PNG
std::unique_ptr<SkCodec> codec =
SkPngRustDecoder::Decode(std::move(stream), nullptr);
#else
std::unique_ptr<SkCodec> codec =
SkPngDecoder::Decode(std::move(stream), nullptr);
#endif
if (!codec) {
return {};
}
SkColorType format =
reverse_byte_order ? kBGRA_8888_SkColorType : kRGBA_8888_SkColorType;
SkImageInfo info = codec->getInfo();
info = info.makeColorType(format);
info = info.makeColorSpace(SkColorSpace::MakeSRGB());
std::vector<uint8_t> output;
output.resize(info.computeMinByteSize());
SkCodec::Result result =
codec->getPixels(info, output.data(), info.minRowBytes());
if (result != SkCodec::kSuccess) {
return {};
}
*width = info.width();
*height = info.height();
return output;
}
std::vector<uint8_t> EncodeBGRPNG(pdfium::span<const uint8_t> bgr_input,
int width,
int height,
int row_byte_width) {
// Check inputs. Expected values are manually calculated (instead of using
// `SkImageInfo`'s `computeMinByteSize` and/or `minRowBytes`), because
// `SkColorType` doesn't cover a format with 3 bytes per pixel (bpp) - e.g.
// `kRGB_565_SkColorType` is 2 bpp and `kRGB_888x_SkColorType` is 4 bpp.
size_t row_byte_width_as_size_t =
pdfium::checked_cast<size_t>(row_byte_width);
FX_SAFE_SIZE_T expected_minimum_row_byte_width = 3;
expected_minimum_row_byte_width *= width;
CHECK_LE(expected_minimum_row_byte_width.ValueOrDie(),
row_byte_width_as_size_t);
FX_SAFE_SIZE_T expected_minimum_input_size = row_byte_width_as_size_t;
expected_minimum_input_size *= height;
CHECK_LE(expected_minimum_input_size.ValueOrDie(), bgr_input.size());
// Convert `bgr_input` into `intermediate_bgra_buf` (because Skia doesn't
// allow encoding BGR pixels - see the comment at the top of the function
// that talks about limitations of `SkColorType`).
SkImageInfo intermediate_bgra_info =
SkImageInfo::Make(width, height, kBGRA_8888_SkColorType,
kOpaque_SkAlphaType, SkColorSpace::MakeSRGB());
size_t intermediate_bgra_row_byte_width =
intermediate_bgra_info.minRowBytes();
CHECK_NE(0,
intermediate_bgra_row_byte_width); // 0 means conversion problems.
std::vector<uint8_t> intermediate_bgra_buf;
intermediate_bgra_buf.resize(intermediate_bgra_info.computeMinByteSize());
CHECK_NE(0, intermediate_bgra_buf.size()); // 0 means conversion problems.
{
pdfium::span<const uint8_t> src = bgr_input;
pdfium::span<uint8_t> dst = intermediate_bgra_buf;
size_t height_as_size_t = pdfium::checked_cast<size_t>(height);
size_t width_as_size_t = pdfium::checked_cast<size_t>(width);
for (size_t y = 0; y < height_as_size_t; y++) {
for (size_t x = 0; x < width_as_size_t; x++) {
// If `computeMinByteSize` didn't report an error (`0`), then integer
// overflow won't happen in the `x * N` expressions below.
pdfium::span<uint8_t> dst_pixel = dst.subspan(x * 4u).first(4u);
pdfium::span<const uint8_t> src_pixel = src.subspan(x * 3u).first(3u);
fxcrt::spancpy(dst_pixel.first(3u), src_pixel); // Copy BGR channels.
dst_pixel[3] = 0xFF; // Set alpha channel to "opaque".
}
src = src.subspan(row_byte_width_as_size_t);
dst = dst.subspan(intermediate_bgra_row_byte_width);
}
}
return EncodePNG(intermediate_bgra_buf, kBGRA_8888_SkColorType,
kOpaque_SkAlphaType, width, height,
intermediate_bgra_row_byte_width);
}
std::vector<uint8_t> EncodeRGBAPNG(pdfium::span<const uint8_t> input,
int width,
int height,
int row_byte_width) {
return EncodePNG(input, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, width,
height, pdfium::checked_cast<size_t>(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) {
return EncodePNG(
input, kBGRA_8888_SkColorType,
discard_transparency ? kOpaque_SkAlphaType : kUnpremul_SkAlphaType, width,
height, pdfium::checked_cast<size_t>(row_byte_width));
}
std::vector<uint8_t> EncodeGrayPNG(pdfium::span<const uint8_t> input,
int width,
int height,
int row_byte_width) {
return EncodePNG(input, kGray_8_SkColorType, kOpaque_SkAlphaType, width,
height, pdfium::checked_cast<size_t>(row_byte_width));
}
} // namespace image_diff_png