| // 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 |