| /* |
| * 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); |
| } |