Expose more info in SkCodec::FrameInfo
Bug: b/160984428
Add more fields to SkCodec::FrameInfo, which describes the properties of
an individual frame in an animated image. This allows a client that
wishes to seek to determine frame dependencies so that they can decode
an arbitrary frame, which in turn will allow SkCodec to remove
SkCodec::FrameInfo::fRequiredFrame. Currently, SkCodec seeks through the
stream to determine frame dependencies, but this is unnecessary work
(and storage) for a client that does not want to seek.
These fields also support the proposed APIs in go/animated-ndk.
Move SkCodecAnimation::Blend from SkCodecAnimationPriv (and delete that
file) into SkCodecAnimation.h. Rename its values to be more clear.
Merge common code for populating SkCodec::FrameInfo.
Add a test for a GIF with offsets outside the range of the image. Note
that libwebp rejects such an image.
Update libgifcodec.
Change-Id: Ie27e0531e7d62eaae153eccb3105bf2121b5aac4
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/339857
Commit-Queue: Leon Scroggins <scroggo@google.com>
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Reviewed-by: Nigel Tao <nigeltao@google.com>
diff --git a/tests/CodecAnimTest.cpp b/tests/CodecAnimTest.cpp
index b0ccb99..8498d88 100644
--- a/tests/CodecAnimTest.cpp
+++ b/tests/CodecAnimTest.cpp
@@ -67,6 +67,31 @@
return info.fDisposalMethod == SkCodecAnimation::DisposalMethod::kRestorePrevious;
}
+namespace {
+SkString to_string(bool boolean) { return boolean ? SkString("true") : SkString("false"); }
+SkString to_string(SkCodecAnimation::Blend blend) {
+ switch (blend) {
+ case SkCodecAnimation::Blend::kSrcOver:
+ return SkString("kSrcOver");
+ case SkCodecAnimation::Blend::kSrc:
+ return SkString("kSrc");
+ default:
+ return SkString();
+ }
+}
+SkString to_string(SkIRect rect) {
+ return SkStringPrintf("{ %i, %i, %i, %i }", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+}
+
+template <typename T>
+void reporter_assert_equals(skiatest::Reporter* r, const char* name, int i, const char* prop,
+ T expected, T actual) {
+ REPORTER_ASSERT(r, expected == actual, "%s's frame %i has wrong %s! expected:"
+ " %s\tactual: %s", name, i, prop, to_string(expected).c_str(),
+ to_string(actual).c_str());
+}
+} // namespace
+
DEF_TEST(Codec_frames, r) {
constexpr int kNoFrame = SkCodec::kNoFrame;
constexpr SkAlphaType kOpaque = kOpaque_SkAlphaType;
@@ -77,6 +102,8 @@
SkCodecAnimation::DisposalMethod::kRestoreBGColor;
constexpr SkCodecAnimation::DisposalMethod kRestorePrev =
SkCodecAnimation::DisposalMethod::kRestorePrevious;
+ constexpr auto kSrcOver = SkCodecAnimation::Blend::kSrcOver;
+ constexpr auto kSrc = SkCodecAnimation::Blend::kSrc;
static const struct {
const char* fName;
@@ -91,13 +118,22 @@
std::vector<int> fDurations;
int fRepetitionCount;
std::vector<SkCodecAnimation::DisposalMethod> fDisposalMethods;
+ std::vector<bool> fAlphaWithinBounds;
+ std::vector<SkCodecAnimation::Blend> fBlends;
+ std::vector<SkIRect> fFrameRects;
} gRecs[] = {
{ "images/required.gif", 7,
{ 0, 1, 2, 3, 4, 5 },
{ kOpaque, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul },
{ 100, 100, 100, 100, 100, 100, 100 },
0,
- { kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep } },
+ { kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep },
+ { false, true, true, true, true, true, true },
+ { kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver,
+ kSrcOver },
+ { {0, 0, 100, 100}, {0, 0, 75, 75}, {0, 0, 50, 50}, {0, 0, 60, 60},
+ {0, 0, 100, 100}, {0, 0, 50, 50}, {0, 0, 75, 75}},
+ },
{ "images/alphabetAnim.gif", 13,
{ kNoFrame, 0, 0, 0, 0, 5, 6, kNoFrame, kNoFrame, 9, 10, 11 },
{ kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul,
@@ -106,7 +142,16 @@
0,
{ kKeep, kRestorePrev, kRestorePrev, kRestorePrev, kRestorePrev,
kRestoreBG, kKeep, kRestoreBG, kRestoreBG, kKeep, kKeep,
- kRestoreBG, kKeep } },
+ kRestoreBG, kKeep },
+ { true, false, true, false, true, true, true, true, true, true, true, true, true },
+ { kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver,
+ kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver,
+ kSrcOver },
+ { {25, 25, 75, 75}, {25, 25, 75, 75}, {25, 25, 75, 75}, {37, 37, 62, 62},
+ {37, 37, 62, 62}, {25, 25, 75, 75}, {0, 0, 50, 50}, {0, 0, 100, 100},
+ {25, 25, 75, 75}, {25, 25, 75, 75}, {0, 0, 100, 100}, {25, 25, 75, 75},
+ {37, 37, 62, 62}},
+ },
{ "images/randPixelsAnim2.gif", 4,
// required frames
{ 0, 0, 1 },
@@ -116,7 +161,11 @@
{ 0, 1000, 170, 40 },
// repetition count
0,
- { kKeep, kKeep, kRestorePrev, kKeep } },
+ { kKeep, kKeep, kRestorePrev, kKeep },
+ { false, true, false, false },
+ { kSrcOver, kSrcOver, kSrcOver, kSrcOver },
+ { {0, 0, 8, 8}, {6, 6, 8, 8}, {4, 4, 8, 8}, {7, 0, 8, 8} },
+ },
{ "images/randPixelsAnim.gif", 13,
// required frames
{ 0, 1, 2, 3, 4, 3, 6, 7, 7, 7, 9, 9 },
@@ -128,41 +177,70 @@
0,
{ kKeep, kKeep, kKeep, kKeep, kRestoreBG, kRestoreBG, kRestoreBG,
kRestoreBG, kRestorePrev, kRestoreBG, kRestorePrev, kRestorePrev,
- kRestorePrev, } },
- { "images/box.gif", 1, {}, {}, {}, 0, { kKeep } },
- { "images/color_wheel.gif", 1, {}, {}, {}, 0, { kKeep } },
+ kRestorePrev, },
+ { false, true, true, false, true, true, false, false, true, true, false, false,
+ true },
+ { kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver,
+ kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver, kSrcOver,
+ kSrcOver },
+ { {4, 4, 12, 12}, {4, 4, 12, 12}, {4, 4, 12, 12}, {0, 0, 8, 8}, {8, 8, 16, 16},
+ {8, 8, 16, 16}, {8, 8, 16, 16}, {2, 2, 10, 10}, {7, 7, 15, 15}, {7, 7, 15, 15},
+ {7, 7, 15, 15}, {0, 0, 8, 8}, {14, 14, 16, 16} },
+ },
+ { "images/box.gif", 1, {}, {}, {}, 0, { kKeep }, {}, {}, {} },
+ { "images/color_wheel.gif", 1, {}, {}, {}, 0, { kKeep }, {}, {}, {} },
{ "images/test640x479.gif", 4, { 0, 1, 2 },
{ kOpaque, kOpaque, kOpaque },
{ 200, 200, 200, 200 },
SkCodec::kRepetitionCountInfinite,
- { kKeep, kKeep, kKeep, kKeep } },
+ { kKeep, kKeep, kKeep, kKeep },
+ { false, true, true, true },
+ { kSrcOver, kSrcOver, kSrcOver, kSrcOver },
+ { {0, 0, 640, 479}, {0, 0, 640, 479}, {0, 0, 640, 479}, {0, 0, 640, 479} },
+ },
{ "images/colorTables.gif", 2, { 0 }, { kOpaque }, { 1000, 1000 }, 5,
- { kKeep, kKeep } },
+ { kKeep, kKeep }, {false, true}, { kSrcOver, kSrcOver },
+ { {0, 0, 640, 400}, {0, 0, 640, 200}},
+ },
- { "images/arrow.png", 1, {}, {}, {}, 0, {} },
- { "images/google_chrome.ico", 1, {}, {}, {}, 0, {} },
- { "images/brickwork-texture.jpg", 1, {}, {}, {}, 0, {} },
+ { "images/arrow.png", 1, {}, {}, {}, 0, {}, {}, {}, {} },
+ { "images/google_chrome.ico", 1, {}, {}, {}, 0, {}, {}, {}, {} },
+ { "images/brickwork-texture.jpg", 1, {}, {}, {}, 0, {}, {}, {}, {} },
#if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32))
- { "images/dng_with_preview.dng", 1, {}, {}, {}, 0, {} },
+ { "images/dng_with_preview.dng", 1, {}, {}, {}, 0, {}, {}, {}, {} },
#endif
- { "images/mandrill.wbmp", 1, {}, {}, {}, 0, {} },
- { "images/randPixels.bmp", 1, {}, {}, {}, 0, {} },
- { "images/yellow_rose.webp", 1, {}, {}, {}, 0, {} },
+ { "images/mandrill.wbmp", 1, {}, {}, {}, 0, {}, {}, {}, {} },
+ { "images/randPixels.bmp", 1, {}, {}, {}, 0, {}, {}, {}, {} },
+ { "images/yellow_rose.webp", 1, {}, {}, {}, 0, {}, {}, {}, {} },
{ "images/stoplight.webp", 3, { 0, 1 }, { kOpaque, kOpaque },
{ 1000, 500, 1000 }, SkCodec::kRepetitionCountInfinite,
- { kKeep, kKeep, kKeep } },
+ { kKeep, kKeep, kKeep }, {false, false, false},
+ {kSrcOver, kSrcOver, kSrcOver},
+ { {0, 0, 11, 29}, {2, 10, 9, 27}, {2, 2, 9, 18}},
+ },
{ "images/blendBG.webp", 7,
{ 0, kNoFrame, kNoFrame, kNoFrame, 4, 4 },
{ kOpaque, kOpaque, kUnpremul, kOpaque, kUnpremul, kUnpremul },
{ 525, 500, 525, 437, 609, 729, 444 },
6,
- { kKeep, kKeep, kKeep, kKeep, kKeep, kKeep, kKeep } },
+ { kKeep, kKeep, kKeep, kKeep, kKeep, kKeep, kKeep },
+ { false, true, false, true, false, true, true },
+ { kSrc, kSrcOver, kSrc, kSrc, kSrc, kSrc, kSrc },
+ { {0, 0, 200, 200}, {0, 0, 200, 200}, {0, 0, 200, 200}, {0, 0, 200, 200},
+ {0, 0, 200, 200}, {100, 100, 200, 200}, {100, 100, 200, 200} },
+ },
{ "images/required.webp", 7,
{ 0, 1, 1, kNoFrame, 4, 4 },
{ kOpaque, kUnpremul, kUnpremul, kOpaque, kOpaque, kOpaque },
{ 100, 100, 100, 100, 100, 100, 100 },
0,
- { kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep } },
+ { kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep },
+ { false, false, false, false, false, false, false },
+ { kSrc, kSrcOver, kSrcOver, kSrcOver, kSrc, kSrcOver,
+ kSrcOver },
+ { {0, 0, 100, 100}, {0, 0, 75, 75}, {0, 0, 50, 50}, {0, 0, 60, 60},
+ {0, 0, 100, 100}, {0, 0, 50, 50}, {0, 0, 75, 75}},
+ },
};
for (const auto& rec : gRecs) {
@@ -296,6 +374,16 @@
}
REPORTER_ASSERT(r, frameInfo.fDisposalMethod == rec.fDisposalMethods[i]);
+
+ reporter_assert_equals<bool>(r, rec.fName, i, "alpha within bounds",
+ rec.fAlphaWithinBounds[i],
+ frameInfo.fHasAlphaWithinBounds);
+
+ reporter_assert_equals(r, rec.fName, i, "blend mode", rec.fBlends[i],
+ frameInfo.fBlend);
+
+ reporter_assert_equals(r, rec.fName, i, "frame rect", rec.fFrameRects[i],
+ frameInfo.fFrameRect);
}
if (TestMode::kIndividual == mode) {
diff --git a/tests/GifTest.cpp b/tests/GifTest.cpp
index 12d8108..7ae65a5 100644
--- a/tests/GifTest.cpp
+++ b/tests/GifTest.cpp
@@ -601,3 +601,46 @@
}
}
}
+
+// This test verifies that a GIF frame outside the image dimensions is handled
+// as desired:
+// - The image reports a size of 0 x 0, but the first frame is 100 x 90. The
+// image (or "canvas") is expanded to fit the first frame. The first frame is red.
+// - The second frame is a green 75 x 75 rectangle, reporting its x-offset and
+// y-offset to be 105, placing it off screen. The decoder interprets this as no
+// change from the first frame.
+DEF_TEST(Codec_xOffsetTooBig, r) {
+ const char* path = "images/xOffsetTooBig.gif";
+ auto data = GetResourceAsData(path);
+ if (!data) {
+ ERRORF(r, "failed to find %s", path);
+ return;
+ }
+
+ auto codec = SkCodec::MakeFromData(std::move(data));
+ if (!codec) {
+ ERRORF(r, "Could not create codec from %s", path);
+ return;
+ }
+
+ REPORTER_ASSERT(r, codec->getFrameCount() == 2);
+
+ auto info = codec->getInfo();
+ REPORTER_ASSERT(r, info.width() == 100 && info.height() == 90);
+
+ SkBitmap bm;
+ bm.allocPixels(info);
+ for (int i = 0; i < 2; i++) {
+ SkCodec::FrameInfo frameInfo;
+ REPORTER_ASSERT(r, codec->getFrameInfo(i, &frameInfo));
+
+ SkIRect expectedRect = i == 0 ? SkIRect{0, 0, 100, 90} : SkIRect{100, 90, 100, 90};
+ REPORTER_ASSERT(r, expectedRect == frameInfo.fFrameRect);
+
+ SkCodec::Options options;
+ options.fFrameIndex = i;
+ REPORTER_ASSERT(r, SkCodec::kSuccess == codec->getPixels(bm.pixmap(), &options));
+
+ REPORTER_ASSERT(r, bm.getColor(0, 0) == SK_ColorRED);
+ }
+}