Add SkCodec methods for individual frames
Add a version of getFrameInfo that returns information about a single
frame, allowing a client to skip creating the entire vector.
Add getFrameCount, for determining the number of frames in the image.
Reimplement std::vector<FrameInfo> getFrameInfo with the new methods.
Updates to the test:
- getFrameInfo(size_t, FrameInfo*) fails before parsing
- Test both versions of getFrameInfo
- Recreate the codec between tests, to test parsing
Change-Id: I77c19087f2f8dcf2c536d80167b18ad1ca96ae94
Reviewed-on: https://skia-review.googlesource.com/13190
Reviewed-by: Matt Sarett <msarett@google.com>
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Chris Blume <cblume@google.com>
Commit-Queue: Leon Scroggins <scroggo@google.com>
diff --git a/tests/CodecAnimTest.cpp b/tests/CodecAnimTest.cpp
index dea4adb..02253ef 100644
--- a/tests/CodecAnimTest.cpp
+++ b/tests/CodecAnimTest.cpp
@@ -94,20 +94,25 @@
#undef kUnpremul
for (const auto& rec : gRecs) {
- std::unique_ptr<SkStream> stream(GetResourceAsStream(rec.fName));
- if (!stream) {
+ sk_sp<SkData> data(GetResourceAsData(rec.fName));
+ if (!data) {
// Useful error statement, but sometimes people run tests without
// resources, and they do not want to see these messages.
//ERRORF(r, "Missing resources? Could not find '%s'", rec.fName);
continue;
}
- std::unique_ptr<SkCodec> codec(SkCodec::NewFromStream(stream.release()));
+ std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
if (!codec) {
ERRORF(r, "Failed to create an SkCodec from '%s'", rec.fName);
continue;
}
+ {
+ SkCodec::FrameInfo frameInfo;
+ REPORTER_ASSERT(r, !codec->getFrameInfo(0, &frameInfo));
+ }
+
const int repetitionCount = codec->getRepetitionCount();
if (repetitionCount != rec.fRepetitionCount) {
ERRORF(r, "%s repetition count does not match! expected: %i\tactual: %i",
@@ -115,112 +120,153 @@
}
const size_t expected = rec.fFrameCount;
- const auto frameInfos = codec->getFrameInfo();
- // getFrameInfo returns empty set for non-animated.
- const size_t frameCount = frameInfos.size() == 0 ? 1 : frameInfos.size();
- if (frameCount != expected) {
- ERRORF(r, "'%s' expected frame count: %i\tactual: %i", rec.fName, expected, frameCount);
- continue;
- }
-
if (rec.fRequiredFrames.size() + 1 != expected) {
ERRORF(r, "'%s' has wrong number entries in fRequiredFrames; expected: %i\tactual: %i",
rec.fName, expected, rec.fRequiredFrames.size() + 1);
continue;
}
- if (1 == frameCount) {
- continue;
- }
-
- auto to_string = [](SkAlphaType type) {
- switch (type) {
- case kUnpremul_SkAlphaType:
- return "unpremul";
- case kOpaque_SkAlphaType:
- return "opaque";
- default:
- return "other";
- }
- };
- // From here on, we are only concerned with animated images.
- REPORTER_ASSERT(r, frameInfos[0].fRequiredFrame == SkCodec::kNone);
- REPORTER_ASSERT(r, frameInfos[0].fAlphaType == codec->getInfo().alphaType());
- for (size_t i = 1; i < frameCount; i++) {
- if (rec.fRequiredFrames[i-1] != frameInfos[i].fRequiredFrame) {
- ERRORF(r, "%s's frame %i has wrong dependency! expected: %i\tactual: %i",
- rec.fName, i, rec.fRequiredFrames[i-1], frameInfos[i].fRequiredFrame);
- }
- auto expectedAlpha = rec.fAlphaTypes[i-1];
- auto alpha = frameInfos[i].fAlphaType;
- if (expectedAlpha != alpha) {
- ERRORF(r, "%s's frame %i has wrong alpha type! expected: %s\tactual: %s",
- rec.fName, i, to_string(expectedAlpha), to_string(alpha));
- }
- }
-
- // Compare decoding in two ways:
- // 1. Provide the frame that a frame depends on, so the codec just has to blend.
- // (in the array cachedFrames)
- // 2. Do not provide the frame that a frame depends on, so the codec has to decode all the
- // way back to a key-frame. (in a local variable uncachedFrame)
- // The two should look the same.
- std::vector<SkBitmap> cachedFrames(frameCount);
- const auto& info = codec->getInfo().makeColorType(kN32_SkColorType);
-
- auto decode = [&](SkBitmap* bm, bool cached, size_t index) {
- bm->allocPixels(info);
- if (cached) {
- // First copy the pixels from the cached frame
- const size_t requiredFrame = frameInfos[index].fRequiredFrame;
- if (requiredFrame != SkCodec::kNone) {
- const bool success = cachedFrames[requiredFrame].copyTo(bm);
- REPORTER_ASSERT(r, success);
- }
- }
- SkCodec::Options opts;
- opts.fFrameIndex = index;
- opts.fHasPriorFrame = cached;
- const SkCodec::Result result = codec->getPixels(info, bm->getPixels(), bm->rowBytes(),
- &opts, nullptr, nullptr);
- REPORTER_ASSERT(r, result == SkCodec::kSuccess);
- };
-
- for (size_t i = 0; i < frameCount; i++) {
- SkBitmap& cachedFrame = cachedFrames[i];
- decode(&cachedFrame, true, i);
- SkBitmap uncachedFrame;
- decode(&uncachedFrame, false, i);
-
- // Now verify they're equal.
- const size_t rowLen = info.bytesPerPixel() * info.width();
- for (int y = 0; y < info.height(); y++) {
- const void* cachedAddr = cachedFrame.getAddr(0, y);
- SkASSERT(cachedAddr != nullptr);
- const void* uncachedAddr = uncachedFrame.getAddr(0, y);
- SkASSERT(uncachedAddr != nullptr);
- const bool lineMatches = memcmp(cachedAddr, uncachedAddr, rowLen) == 0;
- if (!lineMatches) {
- SkString name = SkStringPrintf("cached_%i", i);
- write_bm(name.c_str(), cachedFrame);
- name = SkStringPrintf("uncached_%i", i);
- write_bm(name.c_str(), uncachedFrame);
- ERRORF(r, "%s's frame %i is different depending on caching!", rec.fName, i);
- break;
- }
- }
- }
-
if (rec.fDurations.size() != expected) {
ERRORF(r, "'%s' has wrong number entries in fDurations; expected: %i\tactual: %i",
rec.fName, expected, rec.fDurations.size());
continue;
}
- for (size_t i = 0; i < frameCount; i++) {
- if (rec.fDurations[i] != frameInfos[i].fDuration) {
- ERRORF(r, "%s frame %i's durations do not match! expected: %i\tactual: %i",
- rec.fName, i, rec.fDurations[i], frameInfos[i].fDuration);
+ enum class TestMode {
+ kVector,
+ kIndividual,
+ };
+
+ for (auto mode : { TestMode::kVector, TestMode::kIndividual }) {
+ // Re-create the codec to reset state and test parsing.
+ codec.reset(SkCodec::NewFromData(data));
+
+ size_t frameCount;
+ std::vector<SkCodec::FrameInfo> frameInfos;
+ switch (mode) {
+ case TestMode::kVector:
+ frameInfos = codec->getFrameInfo();
+ // getFrameInfo returns empty set for non-animated.
+ frameCount = frameInfos.empty() ? 1 : frameInfos.size();
+ break;
+ case TestMode::kIndividual:
+ frameCount = codec->getFrameCount();
+ break;
+ }
+
+ if (frameCount != expected) {
+ ERRORF(r, "'%s' expected frame count: %i\tactual: %i",
+ rec.fName, expected, frameCount);
+ continue;
+ }
+
+ // From here on, we are only concerned with animated images.
+ if (1 == frameCount) {
+ continue;
+ }
+
+ for (size_t i = 0; i < frameCount; i++) {
+ SkCodec::FrameInfo frameInfo;
+ switch (mode) {
+ case TestMode::kVector:
+ frameInfo = frameInfos[i];
+ break;
+ case TestMode::kIndividual:
+ REPORTER_ASSERT(r, codec->getFrameInfo(i, nullptr));
+ REPORTER_ASSERT(r, codec->getFrameInfo(i, &frameInfo));
+ break;
+ }
+
+ if (rec.fDurations[i] != frameInfo.fDuration) {
+ ERRORF(r, "%s frame %i's durations do not match! expected: %i\tactual: %i",
+ rec.fName, i, rec.fDurations[i], frameInfo.fDuration);
+ }
+
+ if (0 == i) {
+ REPORTER_ASSERT(r, frameInfo.fRequiredFrame == SkCodec::kNone);
+ REPORTER_ASSERT(r, frameInfo.fAlphaType == codec->getInfo().alphaType());
+ continue;
+ }
+
+ if (rec.fRequiredFrames[i-1] != frameInfo.fRequiredFrame) {
+ ERRORF(r, "%s's frame %i has wrong dependency! expected: %i\tactual: %i",
+ rec.fName, i, rec.fRequiredFrames[i-1], frameInfo.fRequiredFrame);
+ }
+
+ auto to_string = [](SkAlphaType type) {
+ switch (type) {
+ case kUnpremul_SkAlphaType:
+ return "unpremul";
+ case kOpaque_SkAlphaType:
+ return "opaque";
+ default:
+ return "other";
+ }
+ };
+
+ auto expectedAlpha = rec.fAlphaTypes[i-1];
+ auto alpha = frameInfo.fAlphaType;
+ if (expectedAlpha != alpha) {
+ ERRORF(r, "%s's frame %i has wrong alpha type! expected: %s\tactual: %s",
+ rec.fName, i, to_string(expectedAlpha), to_string(alpha));
+ }
+ }
+
+ if (TestMode::kIndividual == mode) {
+ // No need to test decoding twice.
+ return;
+ }
+
+ // Compare decoding in two ways:
+ // 1. Provide the frame that a frame depends on, so the codec just has to blend.
+ // (in the array cachedFrames)
+ // 2. Do not provide the frame that a frame depends on, so the codec has to decode
+ // all the way back to a key-frame. (in a local variable uncachedFrame)
+ // The two should look the same.
+ std::vector<SkBitmap> cachedFrames(frameCount);
+ const auto& info = codec->getInfo().makeColorType(kN32_SkColorType);
+
+ auto decode = [&](SkBitmap* bm, bool cached, size_t index) {
+ bm->allocPixels(info);
+ if (cached) {
+ // First copy the pixels from the cached frame
+ const size_t requiredFrame = frameInfos[index].fRequiredFrame;
+ if (requiredFrame != SkCodec::kNone) {
+ const bool success = cachedFrames[requiredFrame].copyTo(bm);
+ REPORTER_ASSERT(r, success);
+ }
+ }
+ SkCodec::Options opts;
+ opts.fFrameIndex = index;
+ opts.fHasPriorFrame = cached;
+ auto result = codec->getPixels(info, bm->getPixels(), bm->rowBytes(),
+ &opts, nullptr, nullptr);
+ REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+ };
+
+ for (size_t i = 0; i < frameCount; i++) {
+ SkBitmap& cachedFrame = cachedFrames[i];
+ decode(&cachedFrame, true, i);
+ SkBitmap uncachedFrame;
+ decode(&uncachedFrame, false, i);
+
+ // Now verify they're equal.
+ const size_t rowLen = info.bytesPerPixel() * info.width();
+ for (int y = 0; y < info.height(); y++) {
+ const void* cachedAddr = cachedFrame.getAddr(0, y);
+ SkASSERT(cachedAddr != nullptr);
+ const void* uncachedAddr = uncachedFrame.getAddr(0, y);
+ SkASSERT(uncachedAddr != nullptr);
+ const bool lineMatches = memcmp(cachedAddr, uncachedAddr, rowLen) == 0;
+ if (!lineMatches) {
+ SkString name = SkStringPrintf("cached_%i", i);
+ write_bm(name.c_str(), cachedFrame);
+ name = SkStringPrintf("uncached_%i", i);
+ write_bm(name.c_str(), uncachedFrame);
+ ERRORF(r, "%s's frame %i is different depending on caching!", rec.fName, i);
+ break;
+ }
+ }
}
}
}