blob: 61cf3192bbad147b533b396b4f6771c23e605fa3 [file]
/*
* 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/SkPngRustDecoder.h"
#include "include/core/SkColor.h"
#include "include/core/SkData.h"
#include "include/core/SkImage.h"
#include "include/core/SkPixmap.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkStream.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 SkImage& image,
int x,
int y,
SkColor expectedColor,
const char* description) {
SkASSERT(r);
SkASSERT(x >= 0);
SkASSERT(y >= 0);
SkASSERT(description);
REPORTER_ASSERT(r, x < image.width(), "x=%d >= width=%d", x, image.width());
REPORTER_ASSERT(r, y < image.height(), "y=%d >= height=%d", y, image.height());
REPORTER_ASSERT(r,
kN32_SkColorType == image.colorType(),
"kN32_SkColorType != image.ColorType()=%d",
image.colorType());
SkPixmap pixmap;
REPORTER_ASSERT(r, image.peekPixels(&pixmap));
SkColor actualColor = pixmap.getColor(x, y);
REPORTER_ASSERT(r,
actualColor == expectedColor,
"actualColor=0x%08X != expectedColor==0x%08X (%s)",
actualColor,
expectedColor,
description);
}
void AssertGreenPixel(skiatest::Reporter* r,
const SkImage& image,
int x,
int y,
const char* description = "Expecting a green pixel") {
AssertPixelColor(r, image, x, y, SkColorSetRGB(0x00, 0xFF, 0x00), description);
}
void AssertRedPixel(skiatest::Reporter* r,
const SkImage& image,
int x,
int y,
const char* description = "Expecting a red pixel") {
AssertPixelColor(r, image, x, y, SkColorSetRGB(0xFF, 0x00, 0x00), description);
}
// 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) {
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);
REPORTER_ASSERT(r, codec->getRepetitionCount() == 0);
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());
AssertGreenPixel(r, *image, 0, 0);
}
// 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);
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());
AssertRedPixel(r, *image, 0, 0, "Frame #0 should be red");
// TODO(https://crbug.com/356922876): Add frame1 and frame2 assertions after
// implementing blending support in `SkPngRustCodec`.
}
// 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);
}