/* * 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 #include #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 SkPngRustDecoderDecode(skiatest::Reporter* r, const char* path) { sk_sp data = GetResourceAsData(path); if (!data) { ERRORF(r, "Missing resource: %s", path); return nullptr; } SkCodec::Result result; std::unique_ptr codec = SkPngRustDecoder::Decode(std::make_unique(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 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 DecodeLastFrame(skiatest::Reporter* r, SkCodec* codec) { int frameCount = codec->getFrameCount(); sk_sp 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 DecodeLastFrame(skiatest::Reporter* r, const char* resourcePath) { std::unique_ptr 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 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 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 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(std::move(data), kInitialBytes); HaltingStream* retainedStream = streamForCodec.get(); SkCodec::Result result; std::unique_ptr 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 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 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(std::move(data), kInitialBytes); HaltingStream* retainedStream = streamForCodec.get(); SkCodec::Result result; std::unique_ptr 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 codec = SkPngRustDecoderDecode(r, "images/apng-test-suite--blend-ops--source-on-solid.png"); if (!codec) { return; } sk_sp 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 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 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 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 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 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 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 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 codec = SkPngRustDecoderDecode(r, test.fFilename); if (codec) { REPORTER_ASSERT(r, codec->hasHighBitDepthEncodedData() == test.fEncodedWith16bits); } } } DEF_TEST(Codec_png_cicp, r) { std::unique_ptr 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)); }