From 469d67e7d9bc312b33b01846be1285f7cfdba63c Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 11 Nov 2020 12:45:40 -0500 Subject: [PATCH] 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 Reviewed-by: Derek Sollenberger Reviewed-by: Nigel Tao --- DEPS | 2 +- RELEASE_NOTES.txt | 3 + include/codec/SkCodec.h | 23 ++++++ include/codec/SkCodecAnimation.h | 18 +++++ resources/images/xOffsetTooBig.gif | Bin 0 -> 258 bytes src/codec/SkCodec.cpp | 16 +++- src/codec/SkCodecAnimationPriv.h | 32 -------- src/codec/SkFrameHolder.h | 9 ++- src/codec/SkHeifCodec.cpp | 6 +- src/codec/SkWebpCodec.cpp | 10 +-- src/codec/SkWuffsCodec.cpp | 17 +--- src/utils/SkAnimCodecPlayer.cpp | 2 +- tests/CodecAnimTest.cpp | 124 ++++++++++++++++++++++++----- tests/GifTest.cpp | 43 ++++++++++ 14 files changed, 223 insertions(+), 82 deletions(-) create mode 100644 resources/images/xOffsetTooBig.gif delete mode 100644 src/codec/SkCodecAnimationPriv.h diff --git a/DEPS b/DEPS index fd0e25c704..1c7799ac97 100644 --- a/DEPS +++ b/DEPS @@ -24,7 +24,7 @@ deps = { "third_party/externals/harfbuzz" : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@3a74ee528255cc027d84b204a87b5c25e47bff79", "third_party/externals/icu" : "https://chromium.googlesource.com/chromium/deps/icu.git@dbd3825b31041d782c5b504c59dcfb5ac7dda08c", "third_party/externals/imgui" : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@9418dcb69355558f70de260483424412c5ca2fce", - "third_party/externals/libgifcodec" : "https://skia.googlesource.com/libgifcodec@b89e0a4edd6c0158b24730845e0e498969e22a16", + "third_party/externals/libgifcodec" : "https://skia.googlesource.com/libgifcodec@e13b82fac077383d9f93631a177573509be44f38", "third_party/externals/libjpeg-turbo" : "https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@64fc43d52351ed52143208ce6a656c03db56462b", "third_party/externals/libpng" : "https://skia.googlesource.com/third_party/libpng.git@386707c6d19b974ca2e3db7f5c61873813c6fe44", "third_party/externals/libwebp" : "https://chromium.googlesource.com/webm/libwebp.git@55a080e50af655d1fbe0a5c22954835cdd59ff92", diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 3dc29b4aa5..c2b4bbd8d4 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -6,6 +6,9 @@ This file includes a list of high level updates for each milestone release. Milestone 89 ------------ + * Expose more info in SkCodec::FrameInfo + https://review.skia.org/339857 + * Added dither control to the SkImageFilters::Shader factory. https://review.skia.org/338156 diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h index 88fd07b5ce..f756585ada 100644 --- a/include/codec/SkCodec.h +++ b/include/codec/SkCodec.h @@ -644,10 +644,33 @@ public: */ SkAlphaType fAlphaType; + /** + * Whether the updated rectangle contains alpha. + * + * This is conservative; it will still be set to true if e.g. a color + * index-based frame has a color with alpha but does not use it. In + * addition, it may be set to true, even if the final frame, after + * blending, is opaque. + */ + bool fHasAlphaWithinBounds; + /** * How this frame should be modified before decoding the next one. */ SkCodecAnimation::DisposalMethod fDisposalMethod; + + /** + * How this frame should blend with the prior frame. + */ + SkCodecAnimation::Blend fBlend; + + /** + * The rectangle updated by this frame. + * + * It may be empty, if the frame does not change the image. It will + * always be contained by SkCodec::dimensions(). + */ + SkIRect fFrameRect; }; /** diff --git a/include/codec/SkCodecAnimation.h b/include/codec/SkCodecAnimation.h index 490c636bdc..c5883e2af2 100644 --- a/include/codec/SkCodecAnimation.h +++ b/include/codec/SkCodecAnimation.h @@ -39,5 +39,23 @@ namespace SkCodecAnimation { */ kRestorePrevious = 3, }; + + /** + * How to blend the current frame. + */ + enum class Blend { + /** + * Blend with the prior frame as if using SkBlendMode::kSrcOver. + */ + kSrcOver, + + /** + * Blend with the prior frame as if using SkBlendMode::kSrc. + * + * This frame's pixels replace the destination pixels. + */ + kSrc, + }; + } // namespace SkCodecAnimation #endif // SkCodecAnimation_DEFINED diff --git a/resources/images/xOffsetTooBig.gif b/resources/images/xOffsetTooBig.gif new file mode 100644 index 0000000000000000000000000000000000000000..bb77163df0f8572f64243661c4dfa614260d722a GIT binary patch literal 258 zcmZ?wbhEHbWB`JN%nbh-KnRF|Q1K@V3m1@~1CmQ&h+<%1%4z9edHOB?;yGJxb#K16 z=Qn@bBadm%I+wjVwe21E@lQV2zx6)0 b08X$2G8r-%yn&E`DQF6H9bmSBfx#L8sk((! literal 0 HcmV?d00001 diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp index 533cef5309..98a6846eb9 100644 --- a/src/codec/SkCodec.cpp +++ b/src/codec/SkCodec.cpp @@ -765,6 +765,20 @@ const char* SkCodec::ResultToString(Result result) { } } +void SkFrame::fillIn(SkCodec::FrameInfo* frameInfo, bool fullyReceived) const { + SkASSERT(frameInfo); + + frameInfo->fRequiredFrame = fRequiredFrame; + frameInfo->fDuration = fDuration; + frameInfo->fFullyReceived = fullyReceived; + frameInfo->fAlphaType = fHasAlpha ? kUnpremul_SkAlphaType + : kOpaque_SkAlphaType; + frameInfo->fHasAlphaWithinBounds = this->reportedAlpha() != SkEncodedInfo::kOpaque_Alpha; + frameInfo->fDisposalMethod = fDisposalMethod; + frameInfo->fBlend = fBlend; + frameInfo->fFrameRect = fRect; +} + static bool independent(const SkFrame& frame) { return frame.getRequiredFrame() == SkCodec::kNoFrame; } @@ -828,7 +842,7 @@ void SkFrameHolder::setAlphaAndRequiredFrame(SkFrame* frame) { } - const bool blendWithPrevFrame = frame->getBlend() == SkCodecAnimation::Blend::kPriorFrame; + const bool blendWithPrevFrame = frame->getBlend() == SkCodecAnimation::Blend::kSrcOver; if ((!reportsAlpha || !blendWithPrevFrame) && frameRect == screenRect) { frame->setHasAlpha(reportsAlpha); frame->setRequiredFrame(SkCodec::kNoFrame); // IND2 diff --git a/src/codec/SkCodecAnimationPriv.h b/src/codec/SkCodecAnimationPriv.h deleted file mode 100644 index e326919835..0000000000 --- a/src/codec/SkCodecAnimationPriv.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2016 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#ifndef SkCodecAnimationPriv_DEFINED -#define SkCodecAnimationPriv_DEFINED - -namespace SkCodecAnimation { - /** - * How to blend the current frame. - */ - enum class Blend { - /** - * Blend with the prior frame. This is the typical case, supported - * by all animated image types. - */ - kPriorFrame, - - /** - * Do not blend. - * - * This frames pixels overwrite previous pixels "blending" with - * the background color of transparent. - */ - kBG, - }; - -} // namespace SkCodecAnimation -#endif // SkCodecAnimationPriv_DEFINED diff --git a/src/codec/SkFrameHolder.h b/src/codec/SkFrameHolder.h index c44d2e048c..7b4031ea1b 100644 --- a/src/codec/SkFrameHolder.h +++ b/src/codec/SkFrameHolder.h @@ -8,12 +8,12 @@ #ifndef SkFrameHolder_DEFINED #define SkFrameHolder_DEFINED +#include "include/codec/SkCodec.h" #include "include/codec/SkCodecAnimation.h" #include "include/core/SkRect.h" #include "include/core/SkTypes.h" #include "include/private/SkEncodedInfo.h" #include "include/private/SkNoncopyable.h" -#include "src/codec/SkCodecAnimationPriv.h" /** * Base class for a single frame of an animated image. @@ -29,7 +29,7 @@ public: , fRequiredFrame(kUninitialized) , fDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep) , fDuration(0) - , fBlend(SkCodecAnimation::Blend::kPriorFrame) + , fBlend(SkCodecAnimation::Blend::kSrcOver) { fRect.setEmpty(); } @@ -142,6 +142,11 @@ public: return fBlend; } + /** + * Fill in the FrameInfo with details from this object. + */ + void fillIn(SkCodec::FrameInfo*, bool fullyReceived) const; + protected: virtual SkEncodedInfo::Alpha onReportedAlpha() const = 0; diff --git a/src/codec/SkHeifCodec.cpp b/src/codec/SkHeifCodec.cpp index 1c834813c9..2a162e8409 100644 --- a/src/codec/SkHeifCodec.cpp +++ b/src/codec/SkHeifCodec.cpp @@ -348,11 +348,7 @@ bool SkHeifCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const { } if (frameInfo) { - frameInfo->fRequiredFrame = SkCodec::kNoFrame; - frameInfo->fDuration = frame->getDuration(); - frameInfo->fFullyReceived = true; - frameInfo->fAlphaType = kOpaque_SkAlphaType; - frameInfo->fDisposalMethod = SkCodecAnimation::DisposalMethod::kKeep; + frame->fillIn(frameInfo, true); } return true; diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp index b66d57647c..e8b1bb4202 100644 --- a/src/codec/SkWebpCodec.cpp +++ b/src/codec/SkWebpCodec.cpp @@ -12,7 +12,6 @@ #include "include/core/SkCanvas.h" #include "include/private/SkTemplates.h" #include "include/private/SkTo.h" -#include "src/codec/SkCodecAnimationPriv.h" #include "src/codec/SkCodecPriv.h" #include "src/codec/SkParseEncodedOrigin.h" #include "src/codec/SkSampler.h" @@ -266,7 +265,7 @@ int SkWebpCodec::onGetFrameCount() { SkCodecAnimation::DisposalMethod::kKeep); frame->setDuration(iter.duration); if (WEBP_MUX_BLEND != iter.blend_method) { - frame->setBlend(SkCodecAnimation::Blend::kBG); + frame->setBlend(SkCodecAnimation::Blend::kSrc); } fFrameHolder.setAlphaAndRequiredFrame(frame); } @@ -295,14 +294,9 @@ bool SkWebpCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const { } if (frameInfo) { - frameInfo->fRequiredFrame = frame->getRequiredFrame(); - frameInfo->fDuration = frame->getDuration(); // libwebp only reports fully received frames for an // animated image. - frameInfo->fFullyReceived = true; - frameInfo->fAlphaType = frame->hasAlpha() ? kUnpremul_SkAlphaType - : kOpaque_SkAlphaType; - frameInfo->fDisposalMethod = frame->getDisposalMethod(); + frame->fillIn(frameInfo, true); } return true; diff --git a/src/codec/SkWuffsCodec.cpp b/src/codec/SkWuffsCodec.cpp index 4fd2a0447f..8b828b26a8 100644 --- a/src/codec/SkWuffsCodec.cpp +++ b/src/codec/SkWuffsCodec.cpp @@ -156,7 +156,6 @@ class SkWuffsFrame final : public SkFrame { public: SkWuffsFrame(wuffs_base__frame_config* fc); - SkCodec::FrameInfo frameInfo(bool fullyReceived) const; uint64_t ioPosition() const; // SkFrame overrides. @@ -332,18 +331,8 @@ SkWuffsFrame::SkWuffsFrame(wuffs_base__frame_config* fc) this->setXYWH(r.min_incl_x, r.min_incl_y, r.width(), r.height()); this->setDisposalMethod(wuffs_disposal_to_skia_disposal(fc->disposal())); this->setDuration(fc->duration() / WUFFS_BASE__FLICKS_PER_MILLISECOND); - this->setBlend(fc->overwrite_instead_of_blend() ? SkCodecAnimation::Blend::kBG - : SkCodecAnimation::Blend::kPriorFrame); -} - -SkCodec::FrameInfo SkWuffsFrame::frameInfo(bool fullyReceived) const { - SkCodec::FrameInfo ret; - ret.fRequiredFrame = getRequiredFrame(); - ret.fDuration = getDuration(); - ret.fFullyReceived = fullyReceived; - ret.fAlphaType = hasAlpha() ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType; - ret.fDisposalMethod = getDisposalMethod(); - return ret; + this->setBlend(fc->overwrite_instead_of_blend() ? SkCodecAnimation::Blend::kSrc + : SkCodecAnimation::Blend::kSrcOver); } uint64_t SkWuffsFrame::ioPosition() const { @@ -854,7 +843,7 @@ bool SkWuffsCodec::onGetFrameInfo(int i, SkCodec::FrameInfo* frameInfo) const { return false; } if (frameInfo) { - *frameInfo = f->frameInfo(static_cast(i) < this->fNumFullyReceivedFrames); + f->fillIn(frameInfo, static_cast(i) < this->fNumFullyReceivedFrames); } return true; } diff --git a/src/utils/SkAnimCodecPlayer.cpp b/src/utils/SkAnimCodecPlayer.cpp index e26ce38250..0a336169de 100644 --- a/src/utils/SkAnimCodecPlayer.cpp +++ b/src/utils/SkAnimCodecPlayer.cpp @@ -86,7 +86,7 @@ sk_sp SkAnimCodecPlayer::getFrameAt(int index) { // of transparent black, and then draw that through the origin matrix // onto the required frame. To do that, SkCodec needs to expose the // rectangle of the delta and the blend mode, so we can handle - // kRestoreBGColor frames and Blend::kBG. + // kRestoreBGColor frames and Blend::kSrc. SkMatrix inverse; SkAssertResult(originMatrix.invert(&inverse)); canvas->concat(inverse); diff --git a/tests/CodecAnimTest.cpp b/tests/CodecAnimTest.cpp index b0ccb99d62..8498d88149 100644 --- a/tests/CodecAnimTest.cpp +++ b/tests/CodecAnimTest.cpp @@ -67,6 +67,31 @@ static bool restore_previous(const SkCodec::FrameInfo& info) { 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 +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 @@ DEF_TEST(Codec_frames, r) { 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 @@ DEF_TEST(Codec_frames, r) { std::vector fDurations; int fRepetitionCount; std::vector fDisposalMethods; + std::vector fAlphaWithinBounds; + std::vector fBlends; + std::vector 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 @@ DEF_TEST(Codec_frames, r) { 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 @@ DEF_TEST(Codec_frames, r) { { 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 @@ DEF_TEST(Codec_frames, r) { 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 @@ DEF_TEST(Codec_frames, r) { } REPORTER_ASSERT(r, frameInfo.fDisposalMethod == rec.fDisposalMethods[i]); + + reporter_assert_equals(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 12d8108d0a..7ae65a524f 100644 --- a/tests/GifTest.cpp +++ b/tests/GifTest.cpp @@ -601,3 +601,46 @@ DEF_TEST(Codec_AnimatedTransparentGif, r) { } } } + +// 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); + } +}