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>
This commit is contained in:
Leon Scroggins III 2017-04-12 10:49:52 -04:00 committed by Skia Commit-Bot
parent e5efa51b2a
commit e132e7be5f
5 changed files with 215 additions and 116 deletions

View File

@ -593,6 +593,15 @@ public:
*/
int outputScanline(int inputScanline) const;
/**
* Return the number of frames in the image.
*
* May require reading through the stream.
*/
size_t getFrameCount() {
return this->onGetFrameCount();
}
// The required frame for an independent frame is marked as
// kNone.
static constexpr size_t kNone = static_cast<size_t>(-1);
@ -628,7 +637,18 @@ public:
};
/**
* Return info about the frames in the image.
* Return info about a single frame.
*
* Only supported by multi-frame images. Does not read through the stream,
* so it should be called after getFrameCount() to parse any frames that
* have not already been parsed.
*/
bool getFrameInfo(size_t index, FrameInfo* info) const {
return this->onGetFrameInfo(index, info);
}
/**
* Return info about all the frames in the image.
*
* May require reading through the stream to determine info about the
* frames (including the count).
@ -637,9 +657,7 @@ public:
*
* For single-frame images, this will return an empty vector.
*/
std::vector<FrameInfo> getFrameInfo() {
return this->onGetFrameInfo();
}
std::vector<FrameInfo> getFrameInfo();
static constexpr int kRepetitionCountInfinite = -1;
@ -800,9 +818,12 @@ protected:
SkTransferFunctionBehavior premulBehavior);
SkColorSpaceXform* colorXform() const { return fColorXform.get(); }
virtual std::vector<FrameInfo> onGetFrameInfo() {
// empty vector - this is not animated.
return std::vector<FrameInfo>{};
virtual size_t onGetFrameCount() {
return 1;
}
virtual bool onGetFrameInfo(size_t, FrameInfo*) const {
return false;
}
virtual int onGetRepetitionCount() {

View File

@ -489,3 +489,24 @@ bool SkCodec::initializeColorXform(const SkImageInfo& dstInfo,
return true;
}
std::vector<SkCodec::FrameInfo> SkCodec::getFrameInfo() {
const size_t frameCount = this->getFrameCount();
switch (frameCount) {
case 0:
return std::vector<FrameInfo>{};
case 1:
if (!this->onGetFrameInfo(0, nullptr)) {
// Not animated.
return std::vector<FrameInfo>{};
}
// fall through
default: {
std::vector<FrameInfo> result(frameCount);
for (size_t i = 0; i < frameCount; ++i) {
SkAssertResult(this->onGetFrameInfo(i, &result[i]));
}
return result;
}
}
}

View File

@ -132,19 +132,29 @@ SkGifCodec::SkGifCodec(const SkEncodedInfo& encodedInfo, const SkImageInfo& imag
reader->setClient(this);
}
std::vector<SkCodec::FrameInfo> SkGifCodec::onGetFrameInfo() {
size_t SkGifCodec::onGetFrameCount() {
fReader->parse(SkGifImageReader::SkGIFFrameCountQuery);
const size_t size = fReader->imagesCount();
std::vector<FrameInfo> result(size);
for (size_t i = 0; i < size; i++) {
const SkGIFFrameContext* frameContext = fReader->frameContext(i);
result[i].fDuration = frameContext->delayTime();
result[i].fRequiredFrame = frameContext->getRequiredFrame();
result[i].fFullyReceived = frameContext->isComplete();
result[i].fAlphaType = frameContext->hasAlpha() ? kUnpremul_SkAlphaType
: kOpaque_SkAlphaType;
return fReader->imagesCount();
}
bool SkGifCodec::onGetFrameInfo(size_t i, SkCodec::FrameInfo* frameInfo) const {
if (i >= fReader->imagesCount()) {
return false;
}
return result;
const SkGIFFrameContext* frameContext = fReader->frameContext(i);
if (!frameContext->reachedStartOfData()) {
return false;
}
if (frameInfo) {
frameInfo->fDuration = frameContext->delayTime();
frameInfo->fRequiredFrame = frameContext->getRequiredFrame();
frameInfo->fFullyReceived = frameContext->isComplete();
frameInfo->fAlphaType = frameContext->hasAlpha() ? kUnpremul_SkAlphaType
: kOpaque_SkAlphaType;
}
return true;
}
int SkGifCodec::onGetRepetitionCount() {

View File

@ -50,7 +50,8 @@ protected:
uint64_t onGetFillValue(const SkImageInfo&) const override;
std::vector<FrameInfo> onGetFrameInfo() override;
size_t onGetFrameCount() override;
bool onGetFrameInfo(size_t, FrameInfo*) const override;
int onGetRepetitionCount() override;
Result onStartIncrementalDecode(const SkImageInfo& /*dstInfo*/, void*, size_t,

View File

@ -94,20 +94,25 @@ DEF_TEST(Codec_frames, r) {
#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 @@ DEF_TEST(Codec_frames, r) {
}
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;
}
}
}
}
}