}
DEF_TEST(Codec_frames, r) {
+ #define kOpaque kOpaque_SkAlphaType
+ #define kUnpremul kUnpremul_SkAlphaType
static const struct {
- const char* fName;
- size_t fFrameCount;
+ const char* fName;
+ size_t fFrameCount;
// One less than fFramecount, since the first frame is always
// independent.
- std::vector<size_t> fRequiredFrames;
+ std::vector<size_t> fRequiredFrames;
+ // Same, since the first frame should match getInfo.
+ std::vector<SkAlphaType> fAlphaTypes;
// The size of this one should match fFrameCount for animated, empty
// otherwise.
- std::vector<size_t> fDurations;
- int fRepetitionCount;
+ std::vector<size_t> fDurations;
+ int fRepetitionCount;
} gRecs[] = {
+ { "alphabetAnim.gif", 13,
+ { SkCodec::kNone, 0, 0, 0, 0, 5, 6, SkCodec::kNone,
+ SkCodec::kNone, SkCodec::kNone, 10, 11 },
+ { kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul,
+ kUnpremul, kUnpremul, kUnpremul, kOpaque, kOpaque, kUnpremul },
+ { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 },
+ 0 },
+ { "randPixelsAnim2.gif", 4,
+ // required frames
+ { 0, 0, 1 },
+ // alphas
+ { kOpaque, kOpaque, kOpaque },
+ // durations
+ { 0, 1000, 170, 40 },
+ // repetition count
+ 0 },
{ "randPixelsAnim.gif", 13,
// required frames
- { SkCodec::kNone, 1, 2, 3, 4, 4, 6, 7, 7, 7, 7, 7 },
+ { SkCodec::kNone, 1, 2, 3, 4, 3, 6, 7, 7, 7, 9, 9 },
+ { kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul,
+ kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul },
// durations
{ 0, 1000, 170, 40, 220, 7770, 90, 90, 90, 90, 90, 90, 90 },
// repetition count
0 },
- { "box.gif", 1, {}, {}, 0 },
- { "color_wheel.gif", 1, {}, {}, 0 },
- { "test640x479.gif", 4, { 0, 1, 2 }, { 200, 200, 200, 200 },
+ { "box.gif", 1, {}, {}, {}, 0 },
+ { "color_wheel.gif", 1, {}, {}, {}, 0 },
+ { "test640x479.gif", 4, { 0, 1, 2 },
+ { kOpaque, kOpaque, kOpaque },
+ { 200, 200, 200, 200 },
SkCodec::kRepetitionCountInfinite },
- { "colorTables.gif", 2, { 0 }, { 1000, 1000 }, 5 },
+ { "colorTables.gif", 2, { 0 }, { kOpaque }, { 1000, 1000 }, 5 },
- { "arrow.png", 1, {}, {}, 0 },
- { "google_chrome.ico", 1, {}, {}, 0 },
- { "brickwork-texture.jpg", 1, {}, {}, 0 },
+ { "arrow.png", 1, {}, {}, {}, 0 },
+ { "google_chrome.ico", 1, {}, {}, {}, 0 },
+ { "brickwork-texture.jpg", 1, {}, {}, {}, 0 },
#if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32))
- { "dng_with_preview.dng", 1, {}, {}, 0 },
+ { "dng_with_preview.dng", 1, {}, {}, {}, 0 },
#endif
- { "mandrill.wbmp", 1, {}, {}, 0 },
- { "randPixels.bmp", 1, {}, {}, 0 },
- { "yellow_rose.webp", 1, {}, {}, 0 },
+ { "mandrill.wbmp", 1, {}, {}, {}, 0 },
+ { "randPixels.bmp", 1, {}, {}, {}, 0 },
+ { "yellow_rose.webp", 1, {}, {}, {}, 0 },
};
+ #undef kOpaque
+ #undef kUnpremul
- for (auto rec : gRecs) {
+ for (const auto& rec : gRecs) {
std::unique_ptr<SkStream> stream(GetResourceAsStream(rec.fName));
if (!stream) {
// Useful error statement, but sometimes people run tests without
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:
m_firstFrameSupportsIndex8 = true;
} else {
const bool frameIsSubset = xOffset > 0 || yOffset > 0
- || width < m_screenWidth
- || height < m_screenHeight;
+ || xOffset + width < m_screenWidth
+ || yOffset + height < m_screenHeight;
m_firstFrameHasAlpha = frameIsSubset;
m_firstFrameSupportsIndex8 = !frameIsSubset;
}
break;
}
- setRequiredFrame(currentFrame);
+ setAlphaAndRequiredFrame(currentFrame);
GETN(1, SkGIFLZWStart);
break;
}
auto* currentFrame = m_frames.back().get();
auto& cmap = currentFrame->localColorMap();
cmap.setTablePosition(m_streamBuffer.markPosition());
- setRequiredFrame(currentFrame);
+ setAlphaAndRequiredFrame(currentFrame);
GETN(1, SkGIFLZWStart);
break;
}
}
}
-void SkGifImageReader::setRequiredFrame(SkGIFFrameContext* frame) {
+static SkIRect frame_rect_on_screen(SkIRect frameRect,
+ const SkIRect& screenRect) {
+ if (!frameRect.intersect(screenRect)) {
+ return SkIRect::MakeEmpty();
+ }
+
+ return frameRect;
+}
+
+static bool independent(const SkGIFFrameContext& frame) {
+ return frame.getRequiredFrame() == SkCodec::kNone;
+}
+
+static bool restore_bg(const SkGIFFrameContext& frame) {
+ return frame.getDisposalMethod() == SkCodecAnimation::RestoreBGColor_DisposalMethod;
+}
+
+void SkGifImageReader::setAlphaAndRequiredFrame(SkGIFFrameContext* frame) {
const size_t i = frame->frameId();
if (0 == i) {
+ frame->setHasAlpha(m_firstFrameHasAlpha);
frame->setRequiredFrame(SkCodec::kNone);
return;
}
- const SkGIFFrameContext* prevFrame = m_frames[i - 1].get();
- if (prevFrame->getDisposalMethod() == SkCodecAnimation::RestorePrevious_DisposalMethod) {
- frame->setRequiredFrame(prevFrame->getRequiredFrame());
- return;
- }
-
// Note: We could correct these after decoding - i.e. some frames may turn out to be
- // independent if they do not use the transparent pixel, but that would require
- // checking whether each pixel used the transparent pixel.
+ // independent and opaque if they do not use the transparent pixel, but that would require
+ // checking whether each pixel used the transparent index.
const SkGIFColorMap& localMap = frame->localColorMap();
const bool transValid = hasTransparentPixel(i, localMap.isDefined(), localMap.numColors());
- const SkIRect prevFrameRect = prevFrame->frameRect();
- const bool frameCoversPriorFrame = frame->frameRect().contains(prevFrameRect);
+ const auto screenRect = SkIRect::MakeWH(m_screenWidth, m_screenHeight);
+ const auto frameRect = frame_rect_on_screen(frame->frameRect(), screenRect);
- if (!transValid && frameCoversPriorFrame) {
- frame->setRequiredFrame(prevFrame->getRequiredFrame());
+ if (!transValid && frameRect == screenRect) {
+ frame->setHasAlpha(false);
+ frame->setRequiredFrame(SkCodec::kNone);
return;
}
- switch (prevFrame->getDisposalMethod()) {
- case SkCodecAnimation::Keep_DisposalMethod:
- frame->setRequiredFrame(i - 1);
- break;
- case SkCodecAnimation::RestorePrevious_DisposalMethod:
- // This was already handled above.
- SkASSERT(false);
- break;
- case SkCodecAnimation::RestoreBGColor_DisposalMethod:
- // If the prior frame covers the whole image
- if (prevFrameRect == SkIRect::MakeWH(m_screenWidth, m_screenHeight)
- // Or the prior frame was independent
- || prevFrame->getRequiredFrame() == SkCodec::kNone)
- {
- // This frame is independent, since we clear everything in the
- // prior frame to the BG color
- frame->setRequiredFrame(SkCodec::kNone);
- } else {
- frame->setRequiredFrame(i - 1);
- }
- break;
+ const SkGIFFrameContext* prevFrame = m_frames[i - 1].get();
+ while (prevFrame->getDisposalMethod() == SkCodecAnimation::RestorePrevious_DisposalMethod) {
+ const size_t prevId = prevFrame->frameId();
+ if (0 == prevId) {
+ frame->setHasAlpha(true);
+ frame->setRequiredFrame(SkCodec::kNone);
+ return;
+ }
+
+ prevFrame = m_frames[prevId - 1].get();
+ }
+
+ const bool clearPrevFrame = restore_bg(*prevFrame);
+ auto prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect);
+
+ if (clearPrevFrame) {
+ if (prevFrameRect == screenRect || independent(*prevFrame)) {
+ frame->setHasAlpha(true);
+ frame->setRequiredFrame(SkCodec::kNone);
+ return;
+ }
}
+
+ if (transValid) {
+ // Note: We could be more aggressive here. If prevFrame clears
+ // to background color and covers its required frame (and that
+ // frame is independent), prevFrame could be marked independent.
+ // Would this extra complexity be worth it?
+ frame->setRequiredFrame(prevFrame->frameId());
+ frame->setHasAlpha(prevFrame->hasAlpha() || clearPrevFrame);
+ return;
+ }
+
+ while (frameRect.contains(prevFrameRect)) {
+ const size_t prevRequiredFrame = prevFrame->getRequiredFrame();
+ if (prevRequiredFrame == SkCodec::kNone) {
+ frame->setRequiredFrame(SkCodec::kNone);
+ frame->setHasAlpha(true);
+ return;
+ }
+
+ prevFrame = m_frames[prevRequiredFrame].get();
+ prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect);
+ }
+
+ if (restore_bg(*prevFrame)) {
+ frame->setHasAlpha(true);
+ if (prevFrameRect == screenRect || independent(*prevFrame)) {
+ frame->setRequiredFrame(SkCodec::kNone);
+ } else {
+ // Note: As above, frame could still be independent, e.g. if
+ // prevFrame covers its required frame and that frame is
+ // independent.
+ frame->setRequiredFrame(prevFrame->frameId());
+ }
+ return;
+ }
+
+ SkASSERT(prevFrame->getDisposalMethod() == SkCodecAnimation::Keep_DisposalMethod);
+ frame->setRequiredFrame(prevFrame->frameId());
+ frame->setHasAlpha(prevFrame->hasAlpha());
}
// FIXME: Move this method to close to doLZW().