| /* |
| * Copyright 2025 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_ico/decoder/SkIcoRustDecoder.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "include/codec/SkCodec.h" |
| #include "include/core/SkBitmap.h" |
| #include "include/core/SkColor.h" |
| #include "include/core/SkColorType.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/ComparePixels.h" |
| #include "tests/FakeStreams.h" |
| #include "tests/Test.h" |
| #include "tools/Resources.h" |
| |
| #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 `SkIcoRustDecoder::Decode`. |
| static std::unique_ptr<SkCodec> decode_ico(skiatest::Reporter* r, const char* path) { |
| skiatest::ReporterContext ctx(r, 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 = |
| SkIcoRustDecoder::Decode(SkMemoryStream::Make(std::move(data)), &result); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result); |
| |
| return codec; |
| } |
| |
| // Test decoding a valid ICO file with multiple BMP images. |
| DEF_TEST(RustIcoCodec_decode_multi_bmp, r) { |
| std::unique_ptr<SkCodec> codec = decode_ico(r, "images/color_wheel.ico"); |
| if (!codec) { |
| return; |
| } |
| |
| // color_wheel.ico has 5 images, largest should be reported |
| SkISize dimensions = codec->dimensions(); |
| REPORTER_ASSERT(r, dimensions.width() > 0, "width=%d", dimensions.width()); |
| REPORTER_ASSERT(r, dimensions.height() > 0, "height=%d", dimensions.height()); |
| |
| auto [image, result] = codec->getImage(); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result); |
| REPORTER_ASSERT(r, image); |
| } |
| |
| // Test decoding a valid ICO file with PNG images (google_chrome.ico has PNG). |
| DEF_TEST(RustIcoCodec_decode_with_png, r) { |
| std::unique_ptr<SkCodec> codec = decode_ico(r, "images/google_chrome.ico"); |
| if (!codec) { |
| return; |
| } |
| |
| // google_chrome.ico has 9 images including 256x256 PNG |
| SkISize dimensions = codec->dimensions(); |
| REPORTER_ASSERT(r, dimensions.width() == 256, |
| "Expected 256, got width=%d", dimensions.width()); |
| REPORTER_ASSERT(r, dimensions.height() == 256, |
| "Expected 256, got height=%d", dimensions.height()); |
| |
| auto [image, result] = codec->getImage(); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result); |
| REPORTER_ASSERT(r, image); |
| REPORTER_ASSERT(r, image->width() == 256); |
| REPORTER_ASSERT(r, image->height() == 256); |
| } |
| |
| // Test that Decode handles nullptr for the Result parameter. |
| DEF_TEST(RustIcoCodec_nullptr_result, r) { |
| sk_sp<SkData> data = GetResourceAsData("images/color_wheel.ico"); |
| REPORTER_ASSERT(r, data); |
| |
| // This should not crash even when result is nullptr |
| std::unique_ptr<SkCodec> codec = |
| SkIcoRustDecoder::Decode(SkMemoryStream::Make(std::move(data)), nullptr); |
| REPORTER_ASSERT(r, codec); |
| } |
| |
| // Test that SkIcoRustDecoder correctly rejects non-ICO data. |
| DEF_TEST(RustIcoCodec_reject_non_ico, r) { |
| sk_sp<SkData> data = GetResourceAsData("images/color_wheel.png"); |
| if (!data) { |
| ERRORF(r, "Missing resource: images/color_wheel.png"); |
| return; |
| } |
| |
| SkCodec::Result result; |
| std::unique_ptr<SkCodec> codec = |
| SkIcoRustDecoder::Decode(SkMemoryStream::Make(std::move(data)), &result); |
| |
| // Should fail to decode PNG data as ICO |
| REPORTER_ASSERT(r, !codec, "SkIcoRustDecoder should reject PNG data"); |
| } |
| |
| // Test codec reuse functionality. |
| DEF_TEST(RustIcoCodec_rewind, r) { |
| std::unique_ptr<SkCodec> codec = decode_ico(r, "images/color_wheel.ico"); |
| if (!codec) { |
| return; |
| } |
| |
| // Decode first time |
| auto [image1, result1] = codec->getImage(); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result1); |
| REPORTER_ASSERT(r, image1); |
| |
| // Decode again using the same codec (tests reuse) |
| auto [image2, result2] = codec->getImage(); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result2); |
| REPORTER_ASSERT(r, image2); |
| |
| // Both images should have the same dimensions |
| REPORTER_ASSERT(r, |
| image1->dimensions() == image2->dimensions(), |
| "Images after rewind should have same dimensions"); |
| |
| // Verify pixel data is identical |
| SkBitmap bm1, bm2; |
| REPORTER_ASSERT(r, bm1.tryAllocPixels(image1->imageInfo())); |
| REPORTER_ASSERT(r, bm2.tryAllocPixels(image2->imageInfo())); |
| REPORTER_ASSERT(r, image1->readPixels(nullptr, bm1.pixmap(), 0, 0)); |
| REPORTER_ASSERT(r, image2->readPixels(nullptr, bm2.pixmap(), 0, 0)); |
| |
| // Use zero tolerance for exact pixel match |
| const float tols[4] = {0, 0, 0, 0}; |
| auto error = std::function<ComparePixmapsErrorReporter>( |
| [&](int x, int y, const float diffs[4]) { |
| ERRORF(r, "Pixels differ at (%d, %d) after rewind. Diffs: (%f, %f, %f, %f)", |
| x, y, diffs[0], diffs[1], diffs[2], diffs[3]); |
| }); |
| ComparePixels(bm1.pixmap(), bm2.pixmap(), tols, error); |
| } |
| |
| // Test that IsIco correctly identifies ICO data. |
| DEF_TEST(RustIcoCodec_IsIco_positive, r) { |
| sk_sp<SkData> data = GetResourceAsData("images/color_wheel.ico"); |
| if (!data) { |
| ERRORF(r, "Missing resource: images/color_wheel.ico"); |
| return; |
| } |
| |
| bool isIco = SkIcoRustDecoder::IsIco(data->data(), data->size()); |
| REPORTER_ASSERT(r, isIco, "IsIco should return true for ICO data"); |
| } |
| |
| // Test that IsIco correctly rejects non-ICO data. |
| DEF_TEST(RustIcoCodec_IsIco_negative, r) { |
| sk_sp<SkData> data = GetResourceAsData("images/color_wheel.png"); |
| if (!data) { |
| ERRORF(r, "Missing resource: images/color_wheel.png"); |
| return; |
| } |
| |
| bool isIco = SkIcoRustDecoder::IsIco(data->data(), data->size()); |
| REPORTER_ASSERT(r, !isIco, "IsIco should return false for PNG data"); |
| } |
| |
| // Test IsIco with insufficient data. |
| DEF_TEST(RustIcoCodec_IsIco_insufficient_data, r) { |
| // ICO signature needs at least 4 bytes (00 00 01 00 or 00 00 02 00) |
| const uint8_t shortData[] = {0x00, 0x00, 0x01}; |
| bool isIco = SkIcoRustDecoder::IsIco(shortData, sizeof(shortData)); |
| REPORTER_ASSERT(r, !isIco, "IsIco should return false for insufficient data"); |
| } |
| |
| // Test IsIco recognizes CUR files (cursor format, similar to ICO). |
| DEF_TEST(RustIcoCodec_IsIco_cursor, r) { |
| // CUR signature: 00 00 02 00 |
| const uint8_t curData[] = {0x00, 0x00, 0x02, 0x00, 0x01, 0x00}; |
| bool isIco = SkIcoRustDecoder::IsIco(curData, sizeof(curData)); |
| REPORTER_ASSERT(r, isIco, "IsIco should return true for CUR data"); |
| } |
| |
| // Table-based test for handling invalid/corrupted ICO files. |
| DEF_TEST(RustIcoCodec_invalid_ico_handling, r) { |
| auto test = [&r](const char* description, const char* file) { |
| skiatest::ReporterContext ctx(r, description); |
| sk_sp<SkData> data = GetResourceAsData(file); |
| if (!data) { |
| ERRORF(r, "Missing resource: %s", file); |
| return; |
| } |
| |
| SkCodec::Result result; |
| std::unique_ptr<SkCodec> codec = |
| SkIcoRustDecoder::Decode(SkMemoryStream::Make(std::move(data)), &result); |
| |
| // If we got a codec, try to decode to ensure we don't crash |
| if (codec) { |
| auto [image, decodeResult] = codec->getImage(); |
| // Any result is acceptable as long as we don't crash |
| (void)image; |
| (void)decodeResult; |
| } |
| }; |
| |
| test("zero embedded", "empty_images/zero-embedded.ico"); |
| test("fuzz0", "invalid_images/ico_fuzz0.ico"); |
| test("fuzz1", "invalid_images/ico_fuzz1.ico"); |
| test("leak01", "invalid_images/ico_leak01.ico"); |
| test("int_overflow", "invalid_images/int_overflow.ico"); |
| test("mask-bmp-ico", "invalid_images/mask-bmp-ico.ico"); |
| test("sigabort_favicon", "invalid_images/sigabort_favicon.ico"); |
| test("sigsegv_favicon", "invalid_images/sigsegv_favicon.ico"); |
| test("sigsegv_favicon_2", "invalid_images/sigsegv_favicon_2.ico"); |
| test("b37623797", "invalid_images/b37623797.ico"); |
| test("b38116746", "invalid_images/b38116746.ico"); |
| } |
| |
| // Test getPixels with a pre-allocated bitmap. |
| DEF_TEST(RustIcoCodec_getPixels, r) { |
| std::unique_ptr<SkCodec> codec = decode_ico(r, "images/color_wheel.ico"); |
| if (!codec) { |
| return; |
| } |
| |
| SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); |
| SkBitmap bitmap; |
| bitmap.allocPixels(info); |
| |
| SkCodec::Result result = codec->getPixels(bitmap.pixmap()); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result); |
| |
| // Verify bitmap dimensions match codec dimensions |
| REPORTER_ASSERT(r, bitmap.width() == codec->dimensions().width()); |
| REPORTER_ASSERT(r, bitmap.height() == codec->dimensions().height()); |
| } |
| |
| // Test explicit rewind through multiple getPixels calls. |
| DEF_TEST(RustIcoCodec_explicit_rewind, r) { |
| std::unique_ptr<SkCodec> codec = decode_ico(r, "images/color_wheel.ico"); |
| if (!codec) { |
| return; |
| } |
| |
| // First decode |
| SkBitmap bitmap1; |
| bitmap1.allocPixels(codec->getInfo()); |
| SkCodec::Result result1 = codec->getPixels(bitmap1.pixmap()); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result1); |
| |
| // Second decode (implicitly rewinds internally) |
| SkBitmap bitmap2; |
| bitmap2.allocPixels(codec->getInfo()); |
| SkCodec::Result result2 = codec->getPixels(bitmap2.pixmap()); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result2); |
| |
| // Verify dimensions match |
| REPORTER_ASSERT(r, bitmap1.dimensions() == bitmap2.dimensions(), |
| "Dimensions should match after rewind"); |
| |
| // Verify pixel data matches |
| REPORTER_ASSERT(r, bitmap1.computeByteSize() == bitmap2.computeByteSize(), |
| "Byte sizes should match"); |
| REPORTER_ASSERT(r, |
| memcmp(bitmap1.getPixels(), bitmap2.getPixels(), |
| bitmap1.computeByteSize()) == 0, |
| "Pixel data should match after rewind"); |
| } |
| |
| // Test decoding ICO files with non-standard (non-square) dimensions. |
| // These files have non-square aspect ratios, which is unusual for ICO files. |
| DEF_TEST(RustIcoCodec_nonstandard_dimensions, r) { |
| struct TestCase { |
| const char* file; |
| int expectedWidth; |
| int expectedHeight; |
| }; |
| const TestCase testCases[] = { |
| {"images/ico_nonsquare_48x32.ico", 48, 32}, |
| {"images/ico_nonsquare_64x48.ico", 64, 48}, |
| {"images/ico_nonsquare_128x96.ico", 128, 96}, |
| {"images/ico_nonsquare_200x150.ico", 200, 150}, |
| }; |
| |
| for (const auto& testCase : testCases) { |
| skiatest::ReporterContext ctx(r, testCase.file); |
| std::unique_ptr<SkCodec> codec = decode_ico(r, testCase.file); |
| if (!codec) { |
| continue; |
| } |
| |
| SkISize dimensions = codec->dimensions(); |
| REPORTER_ASSERT(r, dimensions.width() == testCase.expectedWidth, |
| "Expected width=%d, got width=%d", |
| testCase.expectedWidth, dimensions.width()); |
| REPORTER_ASSERT(r, dimensions.height() == testCase.expectedHeight, |
| "Expected height=%d, got height=%d", |
| testCase.expectedHeight, dimensions.height()); |
| |
| auto [image, result] = codec->getImage(); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result); |
| REPORTER_ASSERT(r, image); |
| REPORTER_ASSERT(r, image->width() == testCase.expectedWidth); |
| REPORTER_ASSERT(r, image->height() == testCase.expectedHeight); |
| } |
| } |
| |
| // Test that ICO files with oversized dimensions (>256, requiring PNG payload) are rejected. |
| // The Rust ICO decoder currently only supports BMP payloads with dimensions <= 256. |
| DEF_TEST(RustIcoCodec_reject_oversized, r) { |
| sk_sp<SkData> data = GetResourceAsData("images/ico_oversized_512x384.ico"); |
| if (!data) { |
| ERRORF(r, "Missing resource: images/ico_oversized_512x384.ico"); |
| return; |
| } |
| |
| SkCodec::Result result; |
| std::unique_ptr<SkCodec> codec = |
| SkIcoRustDecoder::Decode(SkMemoryStream::Make(std::move(data)), &result); |
| |
| // Should fail to decode ICO with PNG payload (oversized dimensions) |
| REPORTER_ASSERT(r, !codec, |
| "SkIcoRustDecoder should reject ICO files with oversized dimensions"); |
| } |
| |
| // Test incremental decoding with partial data using HaltingStream. |
| // This simulates network streaming where data arrives incrementally. |
| // Note: ICO codec reads the entire file upfront to parse the directory, |
| // so it cannot truly resume mid-stream. However, this test verifies that |
| // the codec correctly handles partial data and succeeds when full data is available. |
| DEF_TEST(RustIcoCodec_IncrementalDecode_PartialStreaming, r) { |
| const char* path = "images/color_wheel.ico"; |
| sk_sp<SkData> data = GetResourceAsData(path); |
| if (!data) { |
| ERRORF(r, "Missing resource: %s", path); |
| return; |
| } |
| const size_t fullLength = data->size(); |
| |
| // Start with only 1/4 of the data available |
| const size_t initialBytes = fullLength / 4; |
| auto streamForCodec = std::make_unique<HaltingStream>(data, initialBytes); |
| HaltingStream* retainedStream = streamForCodec.get(); |
| |
| // ICO codec needs the full file to parse the directory and embedded images, |
| // so with partial data we expect the decode to fail initially. |
| SkCodec::Result result; |
| std::unique_ptr<SkCodec> codec = |
| SkIcoRustDecoder::Decode(std::move(streamForCodec), &result); |
| |
| // With only partial data, codec creation should fail |
| if (codec) { |
| // If codec was created with partial data, it means the initial bytes |
| // were enough to parse the directory. Try to decode - it should fail |
| // or return incomplete. |
| SkBitmap bitmap; |
| bitmap.allocPixels(codec->getInfo().makeColorType(kN32_SkColorType)); |
| SkCodec::Result decodeResult = codec->getPixels(bitmap.pixmap()); |
| // The decode might succeed if the initial bytes contained enough data, |
| // or fail with incomplete input |
| (void)decodeResult; |
| } else { |
| // Codec creation failed with partial data - this is expected. |
| REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput || |
| result == SkCodec::kInvalidInput, |
| "Expected kIncompleteInput or kInvalidInput with partial data, got %s", |
| SkCodec::ResultToString(result)); |
| |
| // Now add the remaining data to the stream |
| retainedStream->addNewData(fullLength - initialBytes); |
| REPORTER_ASSERT(r, retainedStream->isAllDataReceived(), |
| "Stream should have all data after addNewData"); |
| |
| // Rewind the stream to start fresh |
| REPORTER_ASSERT(r, retainedStream->rewind(), "Stream should be rewindable"); |
| |
| // Create a new codec with the now-complete stream |
| // Note: We need to wrap the retained stream since Decode takes ownership |
| // We'll create a new stream with the full data instead |
| auto fullStream = SkMemoryStream::Make(data); |
| std::unique_ptr<SkCodec> fullCodec = |
| SkIcoRustDecoder::Decode(std::move(fullStream), &result); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result); |
| REPORTER_ASSERT(r, fullCodec, "Codec creation should succeed with full data"); |
| |
| if (fullCodec) { |
| auto [image, decodeResult] = fullCodec->getImage(); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, decodeResult); |
| REPORTER_ASSERT(r, image, "Decoding should succeed with full data"); |
| } |
| } |
| } |
| |
| // Test that incremental decode API works for ICO codec. |
| // ICO codec delegates to embedded codecs which may or may not support incremental decode. |
| DEF_TEST(RustIcoCodec_IncrementalDecode_API, r) { |
| std::unique_ptr<SkCodec> codec = decode_ico(r, "images/color_wheel.ico"); |
| if (!codec) { |
| return; |
| } |
| |
| SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); |
| SkBitmap bitmap; |
| bitmap.allocPixels(info); |
| |
| SkCodec::Options options; |
| options.fZeroInitialized = SkCodec::kNo_ZeroInitialized; |
| options.fSubset = nullptr; |
| options.fFrameIndex = 0; |
| options.fPriorFrame = SkCodec::kNoFrame; |
| |
| // Try to start incremental decode |
| SkCodec::Result result = codec->startIncrementalDecode( |
| info, bitmap.getPixels(), bitmap.rowBytes(), &options); |
| |
| // ICO codec may return kUnimplemented if the embedded codec doesn't support |
| // incremental decode, or kSuccess if it does. |
| if (result == SkCodec::kSuccess) { |
| // If incremental decode started, complete it |
| int rowsDecoded = -1; |
| result = codec->incrementalDecode(&rowsDecoded); |
| REPORTER_ASSERT(r, result == SkCodec::kSuccess || |
| result == SkCodec::kIncompleteInput, |
| "incrementalDecode should succeed or return incomplete, got %s", |
| SkCodec::ResultToString(result)); |
| } else if (result == SkCodec::kUnimplemented) { |
| // Incremental decode not supported - this is acceptable for BMP-based ICO |
| // Fall back to regular decode |
| result = codec->getPixels(bitmap.pixmap()); |
| REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, result); |
| } else { |
| ERRORF(r, "Unexpected result from startIncrementalDecode: %s", |
| SkCodec::ResultToString(result)); |
| } |
| |
| // Verify we got valid pixel data |
| REPORTER_ASSERT(r, bitmap.width() == codec->dimensions().width()); |
| REPORTER_ASSERT(r, bitmap.height() == codec->dimensions().height()); |
| } |
| |
| // Test that an ICO file truncated after the directory (so all entry data is missing) |
| // fails to create a codec. The directory is intact but none of the embedded images |
| // are present. |
| DEF_TEST(RustIcoCodec_truncated_all_entries_missing, r) { |
| sk_sp<SkData> data = GetResourceAsData("images/color_wheel.ico"); |
| if (!data) { |
| ERRORF(r, "Missing resource: images/color_wheel.ico"); |
| return; |
| } |
| |
| // color_wheel.ico has 5 BMP images. The directory is 6 + 5*16 = 86 bytes. |
| // Truncate to just the directory so no embedded image data is present. |
| const size_t directorySize = 6 + 5 * 16; |
| REPORTER_ASSERT(r, data->size() > directorySize); |
| sk_sp<SkData> truncated = SkData::MakeSubset(data.get(), 0, directorySize); |
| |
| SkCodec::Result result; |
| std::unique_ptr<SkCodec> codec = |
| SkIcoRustDecoder::Decode(SkMemoryStream::Make(std::move(truncated)), &result); |
| |
| // No embedded images can be decoded, so codec creation should fail. |
| REPORTER_ASSERT(r, !codec, |
| "Codec should not be created when all entries are truncated"); |
| REPORTER_ASSERT(r, result == SkCodec::kInvalidInput, |
| "Expected kInvalidInput, got %s", SkCodec::ResultToString(result)); |
| } |
| |
| // Test that an ICO file truncated so that only some embedded images are present |
| // still reports kSuccess (matching SkIcoCodec) and produces a usable codec whose |
| // reported size is stable, derived from the ICO directory rather than from the |
| // subset of frames that happen to be decodable so far. |
| // |
| // This stability is required by progressive/deferred clients such as Blink: they |
| // lock the image size from the first successful parse, so a size that grew as |
| // more data arrived would leave earlier buffers undersized and overflow on a |
| // later decode. Reporting kIncompleteInput here would instead leave such clients |
| // with no size at all. See SkIcoRustCodec::MakeFromStream for details. |
| DEF_TEST(RustIcoCodec_truncated_some_entries_missing, r) { |
| sk_sp<SkData> data = GetResourceAsData("images/color_wheel.ico"); |
| if (!data) { |
| ERRORF(r, "Missing resource: images/color_wheel.ico"); |
| return; |
| } |
| // Reported size with the full, well-formed file, for the stability check. |
| SkCodec::Result fullResult; |
| std::unique_ptr<SkCodec> fullCodec = |
| SkIcoRustDecoder::Decode(SkMemoryStream::Make(data), &fullResult); |
| REPORTER_ASSERT(r, fullCodec, "Codec creation should succeed with full data"); |
| const SkISize fullDims = fullCodec ? fullCodec->dimensions() : SkISize::MakeEmpty(); |
| |
| // color_wheel.ico has 5 BMP images. Truncate to roughly half the file so |
| // some entries are fully present but later ones are cut off. |
| const size_t truncatedSize = data->size() / 2; |
| sk_sp<SkData> truncated = SkData::MakeSubset(data.get(), 0, truncatedSize); |
| |
| SkCodec::Result result; |
| std::unique_ptr<SkCodec> codec = |
| SkIcoRustDecoder::Decode(SkMemoryStream::Make(std::move(truncated)), &result); |
| |
| if (codec) { |
| // As long as we have a usable codec we report kSuccess, mirroring |
| // SkIcoCodec. |
| REPORTER_ASSERT(r, result == SkCodec::kSuccess, |
| "Expected kSuccess for partially truncated ICO with usable " |
| "frames, got %s", |
| SkCodec::ResultToString(result)); |
| |
| // The codec should still be usable for the entries that are present. |
| int frameCount = codec->getFrameCount(); |
| REPORTER_ASSERT(r, frameCount > 0, "Should have at least one decodable frame"); |
| REPORTER_ASSERT(r, frameCount < 5, |
| "Should have fewer than 5 frames (original has 5), got %d", frameCount); |
| |
| // The reported size must match the full-file size even though fewer |
| // frames are decodable -- it comes from the directory, not the decodable |
| // subset. This is the property that prevents progressive-decode overflows. |
| REPORTER_ASSERT(r, codec->dimensions() == fullDims, |
| "Partial-data size (%dx%d) should equal full-data size (%dx%d)", |
| codec->dimensions().width(), codec->dimensions().height(), |
| fullDims.width(), fullDims.height()); |
| |
| // getInfo() must describe a non-empty image so callers can size buffers. |
| REPORTER_ASSERT(r, !codec->getInfo().isEmpty(), |
| "Codec info should describe a non-empty image"); |
| } else { |
| // If no entries decoded at all, kInvalidInput is acceptable. |
| REPORTER_ASSERT(r, result == SkCodec::kInvalidInput || |
| result == SkCodec::kIncompleteInput, |
| "Expected kInvalidInput or kIncompleteInput, got %s", |
| SkCodec::ResultToString(result)); |
| } |
| } |