blob: 51f068c318a6baf59fcf48c3c80b2b1e7d1cc9e6 [file] [log] [blame]
/*
* Copyright 2024 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "experimental/rust_png/decoder/SkPngRustDecoder.h"
#include "include/codec/SkCodec.h"
#include "include/codec/SkCodecAnimation.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkColor.h"
#include "include/core/SkData.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkStream.h"
#include "tests/FakeStreams.h"
#include "tests/Test.h"
#include "tools/Resources.h"
#include <memory>
#include <utility>
#define REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, actualResult) \
REPORTER_ASSERT(r, \
actualResult == SkCodec::kSuccess, \
"actualResult=\"%s\" != kSuccess", \
SkCodec::ResultToString(actualResult))
// Helper wrapping a call to `SkPngRustDecoder::Decode`.
std::unique_ptr<SkCodec> SkPngRustDecoderDecode(skiatest::Reporter* r, const char* path) {
sk_sp<SkData> data = GetResourceAsData(path);
if (!data) {
ERRORF(r, "Missing resource: %s", path);
return nullptr;
}
SkCodec::Result result;
std::unique_ptr<SkCodec> codec =
SkPngRustDecoder::Decode(std::make_unique<SkMemoryStream>(std::move(data)), &result);
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
return codec;
}
void AssertPixelColor(skiatest::Reporter* r,
const SkPixmap& pixmap,
int x,
int y,
SkColor expectedColor,
const char* description) {
SkASSERT(r);
SkASSERT(x >= 0);
SkASSERT(y >= 0);
SkASSERT(description);
REPORTER_ASSERT(r, x < pixmap.width(), "x=%d >= width=%d", x, pixmap.width());
REPORTER_ASSERT(r, y < pixmap.height(), "y=%d >= height=%d", y, pixmap.height());
REPORTER_ASSERT(r,
kN32_SkColorType == pixmap.colorType(),
"kN32_SkColorType != pixmap.ColorType()=%d",
pixmap.colorType());
SkColor actualColor = pixmap.getColor(x, y);
REPORTER_ASSERT(r,
actualColor == expectedColor,
"actualColor=0x%08X != expectedColor==0x%08X at (%d,%d) (%s)",
actualColor,
expectedColor,
x,
y,
description);
}
void AssertGreenPixel(skiatest::Reporter* r,
const SkPixmap& pixmap,
int x,
int y,
const char* description = "Expecting a green pixel") {
AssertPixelColor(r, pixmap, x, y, SkColorSetRGB(0x00, 0xFF, 0x00), description);
}
void AssertRedPixel(skiatest::Reporter* r,
const SkPixmap& pixmap,
int x,
int y,
const char* description = "Expecting a red pixel") {
AssertPixelColor(r, pixmap, x, y, SkColorSetRGB(0xFF, 0x00, 0x00), description);
}
void AssertBluePixel(skiatest::Reporter* r,
const SkPixmap& pixmap,
int x,
int y,
const char* description = "Expecting a blue pixel") {
AssertPixelColor(r, pixmap, x, y, SkColorSetRGB(0x00, 0x00, 0xFF), description);
}
void AssertSingleGreenFrame(skiatest::Reporter* r,
int expectedWidth,
int expectedHeight,
const char* resourcePath) {
std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, resourcePath);
if (!codec) {
return;
}
REPORTER_ASSERT(r, codec->getFrameCount() == 1);
REPORTER_ASSERT(r, codec->getRepetitionCount() == 0);
SkCodec::FrameInfo info;
REPORTER_ASSERT(r, codec->getFrameInfo(0, &info));
REPORTER_ASSERT(r, info.fBlend == SkCodecAnimation::Blend::kSrc);
REPORTER_ASSERT(r, info.fDisposalMethod == SkCodecAnimation::DisposalMethod::kKeep);
REPORTER_ASSERT(r, info.fFrameRect == SkIRect::MakeWH(expectedWidth, expectedHeight));
REPORTER_ASSERT(r, info.fRequiredFrame == SkCodec::kNoFrame);
auto [image, result] = codec->getImage();
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
REPORTER_ASSERT(r, image);
REPORTER_ASSERT(r,
image->width() == expectedWidth,
"actualWidth=%d != expectedWidth=%d",
image->width(),
expectedWidth);
REPORTER_ASSERT(r,
image->height() == expectedHeight,
"actualHeight=%d != expectedHeight=%d",
image->height(),
expectedHeight);
SkPixmap pixmap;
REPORTER_ASSERT(r, image->peekPixels(&pixmap));
AssertGreenPixel(r, pixmap, 0, 0);
AssertGreenPixel(r, pixmap, expectedWidth / 2, expectedHeight / 2);
}
sk_sp<SkImage> DecodeLastFrame(skiatest::Reporter* r, SkCodec* codec) {
int frameCount = codec->getFrameCount();
sk_sp<SkImage> image;
SkCodec::Result result = SkCodec::kSuccess;
for (int i = 0; i < frameCount; i++) {
SkCodec::FrameInfo info;
REPORTER_ASSERT(r, codec->getFrameInfo(i, &info));
// This test method only supports `kKeep` disposal method.
SkASSERT(info.fDisposalMethod == SkCodecAnimation::DisposalMethod::kKeep);
if (!image) {
std::tie(image, result) = codec->getImage();
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
if (result != SkCodec::kSuccess) {
return nullptr;
}
} else {
SkPixmap pixmap;
REPORTER_ASSERT(r, image->peekPixels(&pixmap));
SkCodec::Options options;
options.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
options.fSubset = nullptr;
options.fFrameIndex = i;
options.fPriorFrame = i - 1;
result = codec->getPixels(pixmap, &options);
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
if (result != SkCodec::kSuccess) {
return nullptr;
}
}
}
return image;
}
sk_sp<SkImage> DecodeLastFrame(skiatest::Reporter* r, const char* resourcePath) {
std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, resourcePath);
if (!codec) {
return nullptr;
}
return DecodeLastFrame(r, codec.get());
}
void AssertAnimationRepetitionCount(skiatest::Reporter* r,
int expectedRepetitionCount,
const char* resourcePath) {
std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, resourcePath);
if (!codec) {
return;
}
int actualRepetitionCount = codec->getRepetitionCount();
REPORTER_ASSERT(r,
actualRepetitionCount == expectedRepetitionCount,
"actualRepetitionCount=%d != expectedRepetitionCount=%d",
actualRepetitionCount,
expectedRepetitionCount);
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#trivial-static-image
DEF_TEST(Codec_apng_basic_trivial_static_image, r) {
AssertSingleGreenFrame(r, 128, 64, "images/apng-test-suite--basic--trivial-static-image.png");
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#trivial-animated-image-one-frame-using-default-image
DEF_TEST(Codec_apng_basic_using_default_image, r) {
AssertSingleGreenFrame(r, 128, 64, "images/apng-test-suite--basic--using-default-image.png");
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#trivial-animated-image-one-frame-ignoring-default-image
//
// The input file contains the following PNG chunks: IHDR, acTL, IDAT, fcTL,
// fdAT, IEND. Presence of acTL chunk + no fcTL chunk before IDAT means that
// the IDAT chunk is *not* part of the animation (i.e. APNG-aware decoders
// should ignore IDAT frame and start with fdAT frame). This test will fail
// with non-APNG-aware decoders (e.g. with `SkPngCodec`), because the `IDAT`
// chunk represents a red image (the `fdAT` chunk represents a green image).
DEF_TEST(Codec_apng_basic_ignoring_default_image, r) {
AssertSingleGreenFrame(r, 128, 64, "images/apng-test-suite--basic--ignoring-default-image.png");
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#apng-dispose-op-none-basic
//
// This test covers two aspects of `SkPngRustCodec` implementation:
//
// * Blink expects that `onGetFrameCount` returns the total frame count when
// the complete image resource is available (i.e. a lower frame count should
// only happen upon `SkCodec::kIncompleteInput`). Before http://review.skia.org/911038
// `SkPngRustCodec::onGetFrameCount` would not discover additional frames if
// previous frames haven't been decoded yet.
// * TODO(https://crbug.com/356922876): Skia client (e.g. Blink; or here the
// testcase) is expected to handle `SkCodecAnimation::DisposalMethod` and
// populate the target buffer (and `SkCodec::Options::fPriorFrame`) with the
// expected pixels. OTOH, `SkPngRustCodec` needs to handle
// `SkCodecAnimation::Blend` - without this the final frame in this test will
// contain red pixels.
DEF_TEST(Codec_apng_dispose_op_none_basic, r) {
std::unique_ptr<SkCodec> codec =
SkPngRustDecoderDecode(r, "images/apng-test-suite--dispose-ops--none-basic.png");
if (!codec) {
return;
}
REPORTER_ASSERT(r, codec->getFrameCount() == 3);
REPORTER_ASSERT(r, codec->getRepetitionCount() == 0);
// We should have `FrameInfo` for all 3 frames.
SkCodec::FrameInfo info[3];
REPORTER_ASSERT(r, codec->getFrameInfo(0, &info[0]));
REPORTER_ASSERT(r, codec->getFrameInfo(1, &info[1]));
REPORTER_ASSERT(r, codec->getFrameInfo(2, &info[2]));
// The codec should realize that the `SkStream` contains all the data of the
// first 2 frames. Currently `SkPngRustCodec::onGetFrameCount` stops after
// parsing the final, 3rd `fcTL` chunk and therefore it can't tell if the
// subsequent `fdAT` chunk has been fully received or not.
REPORTER_ASSERT(r, info[0].fFullyReceived);
REPORTER_ASSERT(r, info[1].fFullyReceived);
REPORTER_ASSERT(r, !info[2].fFullyReceived);
// Spot-check frame metadata.
REPORTER_ASSERT(r, info[1].fAlphaType == kUnpremul_SkAlphaType);
REPORTER_ASSERT(r, info[1].fBlend == SkCodecAnimation::Blend::kSrcOver);
REPORTER_ASSERT(r, info[1].fDisposalMethod == SkCodecAnimation::DisposalMethod::kKeep);
REPORTER_ASSERT(r, info[1].fDuration == 100, "dur = %d", info[1].fDuration);
REPORTER_ASSERT(r, info[1].fFrameRect == SkIRect::MakeWH(128, 64));
REPORTER_ASSERT(r, info[1].fHasAlphaWithinBounds);
REPORTER_ASSERT(r, info[1].fRequiredFrame == 0);
// Validate contents of the first frame.
auto [image, result] = codec->getImage();
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
REPORTER_ASSERT(r, image);
REPORTER_ASSERT(r, image->width() == 128, "width %d != 128", image->width());
REPORTER_ASSERT(r, image->height() == 64, "height %d != 64", image->height());
SkPixmap pixmap;
REPORTER_ASSERT(r, image->peekPixels(&pixmap));
AssertRedPixel(r, pixmap, 0, 0, "Frame #0 should be red");
// Validate contents of the second frame.
SkCodec::Options options;
options.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
options.fSubset = nullptr;
options.fFrameIndex = 1; // We want to decode the second frame.
options.fPriorFrame = 0; // `pixmap` contains the first frame before `getPixels` call.
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, codec->getPixels(pixmap, &options));
AssertGreenPixel(r, pixmap, 0, 0, "Frame #1 should be green");
// Validate contents of the third frame.
options.fFrameIndex = 2; // We want to decode the second frame.
options.fPriorFrame = 1; // `pixmap` contains the second frame before `getPixels` call.
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, codec->getPixels(pixmap, &options));
AssertGreenPixel(r, pixmap, 0, 0, "Frame #2 should be green");
}
// This test covers an incomplete input scenario:
//
// * Only half of 1st frame is available during `onGetFrameCount`.
// In this situation `onGetFrameCount` may consume the whole input in a
// (futile in this case) attempt to discover `fcTL` chunks for 2nd and 3rd
// frame. This will mean that the input stream is in the middle of the 1st
// frame - no longer positioned correctly for decoding the 1st frame.
// * Full input is available when subsequently decoding 1st frame.
DEF_TEST(Codec_apng_dispose_op_none_basic_incomplete_input1, r) {
const char* path = "images/apng-test-suite--dispose-ops--none-basic.png";
sk_sp<SkData> data = GetResourceAsData(path);
if (!data) {
ERRORF(r, "Missing resource: %s", path);
return;
}
size_t fullLength = data->size();
// Initially expose roughly middle of `IDAT` chunk (in this image `fcTL` is
// present before the `IDAT` chunk and therefore the `IDAT` chunk is part of
// the animated image).
constexpr size_t kInitialBytes = 0xAD;
auto streamForCodec = std::make_unique<HaltingStream>(std::move(data), kInitialBytes);
HaltingStream* retainedStream = streamForCodec.get();
SkCodec::Result result;
std::unique_ptr<SkCodec> codec = SkPngRustDecoder::Decode(std::move(streamForCodec), &result);
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
if (!codec) {
return;
}
SkBitmap bitmap;
if (!bitmap.tryAllocN32Pixels(codec->dimensions().width(), codec->dimensions().height())) {
ERRORF(r, "Failed to allocate SkBitmap");
return;
}
// Try to provoke the codec to consume the currently-available part of the
// input stream.
//
// At this point only the metadata for the first frame is available.
int frameCount = codec->getFrameCount();
REPORTER_ASSERT(r, frameCount == 1);
// Make the rest of the input available to the codec.
retainedStream->addNewData(fullLength);
// Try to decode the first frame and check its contents.
sk_sp<SkImage> image;
std::tie(image, result) = codec->getImage();
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
REPORTER_ASSERT(r, image);
REPORTER_ASSERT(r, image->width() == 128, "width %d != 128", image->width());
REPORTER_ASSERT(r, image->height() == 64, "height %d != 64", image->height());
SkPixmap pixmap;
REPORTER_ASSERT(r, image->peekPixels(&pixmap));
AssertRedPixel(r, pixmap, 0, 0, "Frame #0 should be red");
}
// This test covers an incomplete input scenario:
//
// * Only half of 1st frame is available during the initial `incrementalDecode`.
// * Before retrying, `getFrameCount` is called. This should *not* reposition
// the stream while in the middle of an active incremental decode.
// * Then we retry `incrementalDecode`.
DEF_TEST(Codec_apng_dispose_op_none_basic_incomplete_input2, r) {
const char* path = "images/apng-test-suite--dispose-ops--none-basic.png";
sk_sp<SkData> data = GetResourceAsData(path);
if (!data) {
ERRORF(r, "Missing resource: %s", path);
return;
}
size_t fullLength = data->size();
constexpr size_t kInitialBytes = 0x8D; // Roughly middle of IDAT chunk.
auto streamForCodec = std::make_unique<HaltingStream>(std::move(data), kInitialBytes);
HaltingStream* retainedStream = streamForCodec.get();
SkCodec::Result result;
std::unique_ptr<SkCodec> codec = SkPngRustDecoder::Decode(std::move(streamForCodec), &result);
REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result);
if (!codec) {
return;
}
SkBitmap bitmap;
if (!bitmap.tryAllocN32Pixels(codec->dimensions().width(), codec->dimensions().height())) {
ERRORF(r, "Failed to allocate SkBitmap");
return;
}
const SkPixmap& pixmap = bitmap.pixmap();
// Fill the `bitmap` with blue pixels to detect which pixels have been filled
// by the codec during a partially-successful `incrementalDecode`.
//
// (The first frame has all red pixels.)
bitmap.erase(SkColorSetRGB(0, 0, 0xFF), bitmap.bounds());
AssertBluePixel(r, pixmap, 0, 0);
AssertBluePixel(r, pixmap, 40, 29);
AssertBluePixel(r, pixmap, 80, 35);
AssertBluePixel(r, pixmap, 127, 63);
// Decode partially-available, incomplete image.
SkCodec::Options options;
options.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
options.fSubset = nullptr;
options.fFrameIndex = 0;
options.fPriorFrame = SkCodec::kNoFrame;
result = codec->startIncrementalDecode(bitmap.pixmap().info(),
bitmap.pixmap().writable_addr(),
bitmap.pixmap().rowBytes(),
&options);
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
int rowsDecoded = -1;
result = codec->incrementalDecode(&rowsDecoded);
REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
REPORTER_ASSERT(r, rowsDecoded == 10, "actual rowsDecoded = %d", rowsDecoded);
AssertRedPixel(r, pixmap, 0, 0);
AssertBluePixel(r, pixmap, 40, 29);
AssertBluePixel(r, pixmap, 80, 35);
AssertBluePixel(r, pixmap, 127, 63);
// Make the rest of the input available to the codec.
retainedStream->addNewData(fullLength);
// Try to provoke the codec to consume further into the input stream (doing
// this would loose the position inside the currently active incremental
// decode).
//
// At this point metadata of all the frames is available, but the codec
// shouldn't read the other two `fcTL` chunks during an active incremental
// decode.
int frameCount = codec->getFrameCount();
REPORTER_ASSERT(r, frameCount == 1);
// Check that all the pixels of the first frame got decoded.
rowsDecoded = -1;
result = codec->incrementalDecode(&rowsDecoded);
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
REPORTER_ASSERT(r, rowsDecoded == -1); // Not set when `kSuccess`.
AssertRedPixel(r, pixmap, 0, 0);
AssertRedPixel(r, pixmap, 40, 29);
AssertRedPixel(r, pixmap, 80, 35);
AssertRedPixel(r, pixmap, 127, 63);
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#apng-blend-op-source-on-solid-colour
DEF_TEST(Codec_apng_blend_ops_source_on_solid, r) {
std::unique_ptr<SkCodec> codec =
SkPngRustDecoderDecode(r, "images/apng-test-suite--blend-ops--source-on-solid.png");
if (!codec) {
return;
}
sk_sp<SkImage> image = DecodeLastFrame(r, codec.get());
if (!image) {
return;
}
SkPixmap pixmap;
REPORTER_ASSERT(r, image->peekPixels(&pixmap));
AssertGreenPixel(r, pixmap, 0, 0);
SkCodec::FrameInfo info;
REPORTER_ASSERT(r, codec->getFrameInfo(1, &info));
REPORTER_ASSERT(r, info.fBlend == SkCodecAnimation::Blend::kSrc);
REPORTER_ASSERT(r, info.fRequiredFrame == SkCodec::kNoFrame);
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#apng-blend-op-source-on-nearly-transparent-colour
DEF_TEST(Codec_apng_blend_ops_source_on_nearly_transparent, r) {
sk_sp<SkImage> image = DecodeLastFrame(
r, "images/apng-test-suite--blend-ops--source-on-nearly-transparent.png");
if (!image) {
return;
}
SkPixmap pixmap;
REPORTER_ASSERT(r, image->peekPixels(&pixmap));
AssertPixelColor(r,
pixmap,
0,
0,
SkColorSetARGB(0x02, 0x00, 0xFF, 0x00),
"Expecting a nearly transparent pixel");
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#apng-blend-op-over-on-solid-and-transparent-colours
DEF_TEST(Codec_apng_blend_ops_over_on_solid_and_transparent, r) {
sk_sp<SkImage> image = DecodeLastFrame(
r, "images/apng-test-suite--blend-ops--over-on-solid-and-transparent.png");
if (!image) {
return;
}
SkPixmap pixmap;
REPORTER_ASSERT(r, image->peekPixels(&pixmap));
AssertGreenPixel(r, pixmap, 0, 0);
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#apng-blend-op-over-repeatedly-with-nearly-transparent-colours
DEF_TEST(Codec_apng_blend_ops_over_repeatedly, r) {
sk_sp<SkImage> image =
DecodeLastFrame(r, "images/apng-test-suite--blend-ops--over-repeatedly.png");
if (!image) {
return;
}
SkPixmap pixmap;
REPORTER_ASSERT(r, image->peekPixels(&pixmap));
AssertGreenPixel(r, pixmap, 0, 0);
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#apng-dispose-op-none-in-region
DEF_TEST(Codec_apng_regions_dispose_op_none, r) {
sk_sp<SkImage> image =
DecodeLastFrame(r, "images/apng-test-suite--regions--dispose-op-none.png");
if (!image) {
return;
}
// Check all pixels.
//
// * The image (and the first frame) is 128x64
// * The 2nd frame is 64x32 at offset (32,16)
// * The 3rd frame is 1x1 at offset (0,0)
SkPixmap pixmap;
REPORTER_ASSERT(r, image->peekPixels(&pixmap));
for (int y = 0; y < pixmap.height(); y++) {
for (int x = 0; x < pixmap.width(); x++) {
AssertGreenPixel(r, pixmap, x, y);
}
}
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#num-plays-0
DEF_TEST(Codec_apng_num_plays_0, r) {
AssertAnimationRepetitionCount(
r, SkCodec::kRepetitionCountInfinite, "images/apng-test-suite--num-plays--0.png");
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#num-plays-1
DEF_TEST(Codec_apng_num_plays_1, r) {
AssertAnimationRepetitionCount(r, 0, "images/apng-test-suite--num-plays--1.png");
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#num-plays-2
DEF_TEST(Codec_apng_num_plays_2, r) {
AssertAnimationRepetitionCount(r, 1, "images/apng-test-suite--num-plays--2.png");
}
// Test based on
// https://philip.html5.org/tests/apng/tests.html#num-frames-outside-valid-range
//
// In this test the `acTL` chunk sets `num_frames` to `2147483649u` (or `0x80000001u`):
//
// * AFAICT version 1.0 of the APNG spec only says that "0 is not a valid value"
// * The test suite webpage says that at one point the APNG spec said that
// `num_frames` shall be "limited to the range 0 to (2^31)-1"
DEF_TEST(Codec_apng_invalid_num_frames_outside_valid_range, r) {
std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(
r, "images/apng-test-suite--invalid--num-frames-outside-valid-range.png");
if (!codec) {
return;
}
// Calling `codec->getFrameCount` exercises the code used to discover and
// parse `fcTL` chunks. With the initial implementation of
// `SkPngRustCodec::getRawFrameCount` the call below would have failed
// `SkASSERT(fFrameAtCurrentStreamPosition < this->getRawFrameCount())`
// in `SkPngRustCodec::readToStartOfNextFrame`.
//
// Note that `SkPngRustCodec::onGetFrameCount` expectedly returns the number
// of successfully parsed `fcTL` chunks (1 chunk in the test input) rather
// than returning the raw `acTL.num_frames`.
REPORTER_ASSERT(r, codec->getFrameCount() == 1);
}
DEF_TEST(Codec_png_swizzling_target_unimplemented, r) {
std::unique_ptr<SkCodec> codec =
SkPngRustDecoderDecode(r, "images/apng-test-suite--basic--ignoring-default-image.png");
if (!codec) {
return;
}
REPORTER_ASSERT(r, codec->getFrameCount() == 1);
// Ask to decode into an esoteric `SkColorType`:
//
// * Unsupported by `SkSwizzler`.
// * Supported by `SkCodec::conversionSupported`.
SkImageInfo dstInfo = SkImageInfo::Make(
codec->dimensions(), kBGRA_10101010_XR_SkColorType, kPremul_SkAlphaType);
auto [image, result] = codec->getImage(dstInfo);
REPORTER_ASSERT(r, result == SkCodec::kUnimplemented);
REPORTER_ASSERT(r, !image);
}
DEF_TEST(Codec_png_was_encoded_with_16_bits_or_more_per_component, r) {
struct Test {
const char* fFilename;
bool fEncodedWith16bits;
};
const std::array<Test, 4> kTests = {
Test{"images/pngsuite/basn0g04.png", false}, // 4 bit (16 level) grayscale
Test{"images/pngsuite/basn2c08.png", false}, // 3x8 bits rgb color
Test{"images/pngsuite/basn2c16.png", true}, // 3x16 bits rgb color
Test{"images/pngsuite/basn3p01.png", false} // 1 bit (2 color) paletted
};
for (const auto& test : kTests) {
std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, test.fFilename);
if (codec) {
REPORTER_ASSERT(r, codec->hasHighBitDepthEncodedData() == test.fEncodedWith16bits);
}
}
}
DEF_TEST(Codec_png_cicp, r) {
std::unique_ptr<SkCodec> codec = SkPngRustDecoderDecode(r, "images/cicp_pq.png");
if (!codec) {
return;
}
const skcms_ICCProfile* profile = codec->getICCProfile();
REPORTER_ASSERT(r, profile);
if (!profile) {
return;
}
REPORTER_ASSERT(r, skcms_TransferFunction_isPQish(&profile->trc[0].parametric));
}