aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Leon Scroggins III <scroggo@google.com>2017-05-23 09:37:21 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-05-23 15:28:37 +0000
commit557fbbe05ba48bcc20be684d11fe0edfc24c87ba (patch)
treede83760e6e67a0866578056b4091091cef29cc53
parente980174d0f3af8e497d9c38851114cc6a3c02e9a (diff)
Add animation support to SkWebpCodec
TBR=reed@google.com (No change to the public API, but changed a header file) SkWebpCodec: - Implement onGetFrameCount, onGetFrameInfo, and onGetRepetitionCount - Respect the alpha reported by libwebp. Although the spec states that it is only a hint, the libwebp encoder uses it properly. Respecting allows us to draw opaque images faster and decode them to 565. This also matches other SkCodecs (and Chromium). - onGetPixels: - Decode the frame requested, recursively decoding required frame if necessary - When blending with a prior frame, use SkRasterPipeline SkCodec: - Move check for negative index to getFrameInfo - Reset the colorXform if one is not needed SkCodecAnimation: - Add new blend enum, for WebP's (and APNG's) non-blending option SkFrameHolder: - New base classes for frames and the owner of the frames, allowing code sharing between SkWebpCodec and SkGifCodec (particularly for determining whether a frame has alpha and what frame it depends on) - When moving items from SkGIFFrameContext, use Skia conventions (i.e. int instead of unsigned) - Rename "delay time" to "duration", to match e.g. SkFrameInfo:: fDuration SkGifImageReader: - Move pieces to SkFrameHolder, and adapt to changes made in the process - Make setAlphaAndRequiredFrame (now on the base class SkFrameHolder) more general to support webp, and add support for frames that do not blend - Change SkGIFFrameContext from a struct to a class, to match how we use the distinction elsewhere (i.e. struct is a small object with public fields) - Rework hasTransparentPixel (now hasTransparency, since it returns true in some cases where there is not a transparent pixel) to better fit with the modified setAlphaAndRequiredFrame. Also be more consistent when there is no transparent pixel but no color map. - Simplify an if condition that was previously simplified in 2d61e717 but accidentally got reverted in a4db9be6 CodecAnimTest: - Test new animated webp files - Rearrange the test to more cleanly print alpha type mismatches for the first frame resources: - webp-animated.webp - animated webp from Chromium - blendBG.webp - new webp file using bits of webp-animated-semitransparent4.webp from Chromium - tests required frame and alpha when using the non-blending mode - frames have the following properties: - Frame 0: no alpha, fills screen - Frame 1: alpha, fills screen - Frame 2: no alpha, fills screen - Frame 3: alpha, fills screen, blendBG - Frame 4: no alpha, fills screen, blendBG - Frame 5: alpha, blendBG - Frame 6: covers 4, has alpha, blendBG - also used to test decoding to 565 if the new frame data has alpha but blends onto an opaque frame DM.cpp: - Test animated images to non-native 8888 and unpremul DMSrcSink.cpp: - Do not test non-native 8888 decodes to f16 dst - Test unpremul decodes to f16 - Copy a frame of an animated image prior to drawing, since in unpremul mode, the DM code will premultiply first. Bug: skia: 3315 Change-Id: I4e55ae2ee5bc095b37a743bdcfac644be603b980 Reviewed-on: https://skia-review.googlesource.com/16707 Commit-Queue: Mike Reed <reed@google.com> Reviewed-by: Mike Reed <reed@google.com> Reviewed-by: Leon Scroggins <scroggo@google.com> Reviewed-by: Matt Sarett <msarett@google.com>
-rw-r--r--dm/DM.cpp8
-rw-r--r--dm/DMSrcSink.cpp28
-rw-r--r--include/codec/SkCodec.h3
-rw-r--r--resources/blendBG.webpbin0 -> 776 bytes
-rwxr-xr-xresources/webp-animated.webpbin0 -> 340 bytes
-rw-r--r--src/codec/SkCodec.cpp2
-rw-r--r--src/codec/SkCodecAnimation.h19
-rw-r--r--src/codec/SkFrameHolder.h186
-rw-r--r--src/codec/SkGifCodec.cpp5
-rw-r--r--src/codec/SkWebpCodec.cpp419
-rw-r--r--src/codec/SkWebpCodec.h59
-rw-r--r--tests/CodecAnimTest.cpp52
-rw-r--r--third_party/gif/SkGifImageReader.cpp116
-rw-r--r--third_party/gif/SkGifImageReader.h86
14 files changed, 772 insertions, 211 deletions
diff --git a/dm/DM.cpp b/dm/DM.cpp
index aa2b081fb1..2f7dd21e34 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -603,8 +603,12 @@ static void push_codec_srcs(Path path) {
{
std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
if (frameInfos.size() > 1) {
- push_codec_src(path, CodecSrc::kAnimated_Mode, CodecSrc::kGetFromCanvas_DstColorType,
- kPremul_SkAlphaType, 1.0f);
+ for (auto dstCT : { CodecSrc::kNonNative8888_Always_DstColorType,
+ CodecSrc::kGetFromCanvas_DstColorType }) {
+ for (auto at : { kUnpremul_SkAlphaType, kPremul_SkAlphaType }) {
+ push_codec_src(path, CodecSrc::kAnimated_Mode, dstCT, at, 1.0f);
+ }
+ }
}
}
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 752ab2e4ae..8979d2d285 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -364,7 +364,8 @@ static bool get_decode_info(SkImageInfo* decodeInfo, SkColorType canvasColorType
*decodeInfo = decodeInfo->makeColorType(kGray_8_SkColorType);
break;
case CodecSrc::kNonNative8888_Always_DstColorType:
- if (kRGB_565_SkColorType == canvasColorType) {
+ if (kRGB_565_SkColorType == canvasColorType
+ || kRGBA_F16_SkColorType == canvasColorType) {
return false;
}
#ifdef SK_PMCOLOR_IS_RGBA
@@ -380,11 +381,6 @@ static bool get_decode_info(SkImageInfo* decodeInfo, SkColorType canvasColorType
}
if (kRGBA_F16_SkColorType == canvasColorType) {
- if (kUnpremul_SkAlphaType == dstAlphaType) {
- // Testing kPremul is enough for adequate coverage of F16 decoding.
- return false;
- }
-
sk_sp<SkColorSpace> linearSpace =
as_CSB(decodeInfo->colorSpace())->makeLinearGamma();
*decodeInfo = decodeInfo->makeColorSpace(std::move(linearSpace));
@@ -499,6 +495,18 @@ Error CodecSrc::draw(SkCanvas* canvas) const {
switch (result) {
case SkCodec::kSuccess:
case SkCodec::kIncompleteInput: {
+ // If the next frame depends on this one, store it in priorFrame.
+ // It is possible that we may discard a frame that future frames depend on,
+ // but the codec will simply redecode the discarded frame.
+ // Do this before calling draw_to_canvas, which premultiplies in place. If
+ // we're decoding to unpremul, we want to pass the unmodified frame to the
+ // codec for decoding the next frame.
+ if (static_cast<size_t>(i+1) < frameInfos.size()
+ && frameInfos[i+1].fRequiredFrame == i) {
+ memcpy(priorFramePixels.reset(safeSize), pixels.get(), safeSize);
+ cachedFrame = i;
+ }
+
SkAutoCanvasRestore acr(canvas, true);
const int xTranslate = (i % factor) * decodeInfo.width();
const int yTranslate = (i / factor) * decodeInfo.height();
@@ -521,14 +529,6 @@ Error CodecSrc::draw(SkCanvas* canvas) const {
return SkStringPrintf("Couldn't getPixels for frame %i in %s.",
i, fPath.c_str());
}
-
- // If a future frame depends on this one, store it in priorFrame.
- // (Note that if i+1 does *not* depend on i, then no future frame can.)
- if (static_cast<size_t>(i+1) < frameInfos.size()
- && frameInfos[i+1].fRequiredFrame == i) {
- memcpy(priorFramePixels.reset(safeSize), pixels.get(), safeSize);
- cachedFrame = i;
- }
}
break;
}
diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h
index 7f88fc6117..d52433e3a3 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -644,6 +644,9 @@ public:
* have not already been parsed.
*/
bool getFrameInfo(int index, FrameInfo* info) const {
+ if (index < 0) {
+ return false;
+ }
return this->onGetFrameInfo(index, info);
}
diff --git a/resources/blendBG.webp b/resources/blendBG.webp
new file mode 100644
index 0000000000..46e4ce255d
--- /dev/null
+++ b/resources/blendBG.webp
Binary files differ
diff --git a/resources/webp-animated.webp b/resources/webp-animated.webp
new file mode 100755
index 0000000000..35a8dfcf34
--- /dev/null
+++ b/resources/webp-animated.webp
Binary files differ
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index 9e693b1dfa..688cfaa993 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -485,6 +485,8 @@ bool SkCodec::initializeColorXform(const SkImageInfo& dstInfo,
if (!fColorXform) {
return false;
}
+ } else {
+ fColorXform.reset();
}
return true;
diff --git a/src/codec/SkCodecAnimation.h b/src/codec/SkCodecAnimation.h
index b0495b89ee..46a878a801 100644
--- a/src/codec/SkCodecAnimation.h
+++ b/src/codec/SkCodecAnimation.h
@@ -44,6 +44,25 @@ public:
RestorePrevious_DisposalMethod = 3,
};
+ /**
+ * 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,
+ };
+
private:
SkCodecAnimation();
};
diff --git a/src/codec/SkFrameHolder.h b/src/codec/SkFrameHolder.h
new file mode 100644
index 0000000000..e92b40f9d8
--- /dev/null
+++ b/src/codec/SkFrameHolder.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkFrameHolder_DEFINED
+#define SkFrameHolder_DEFINED
+
+#include "SkTypes.h"
+#include "SkCodecAnimation.h"
+
+/**
+ * Base class for a single frame of an animated image.
+ *
+ * Separate from SkCodec::FrameInfo, which is a pared down
+ * interface that only contains the info the client needs.
+ */
+class SkFrame : public SkNoncopyable {
+public:
+ SkFrame(int id)
+ : fId(id)
+ , fHasAlpha(false)
+ , fRequiredFrame(kUninitialized)
+ , fDisposalMethod(SkCodecAnimation::Keep_DisposalMethod)
+ , fDuration(0)
+ , fBlend(SkCodecAnimation::Blend::kPriorFrame)
+ {
+ fRect.setEmpty();
+ }
+
+ virtual ~SkFrame() {}
+
+ /**
+ * 0-based index of the frame in the image sequence.
+ */
+ int frameId() const { return fId; }
+
+ /**
+ * Whether this frame reports alpha.
+ *
+ * This only considers the rectangle of this frame, and
+ * considers it to have alpha even if it is opaque once
+ * blended with the frame behind it.
+ */
+ bool reportsAlpha() const {
+ return this->onReportsAlpha();
+ }
+
+ /**
+ * Cached value representing whether the frame has alpha,
+ * after compositing with the prior frame.
+ */
+ bool hasAlpha() const { return fHasAlpha; }
+
+ /**
+ * Cache whether the finished frame has alpha.
+ */
+ void setHasAlpha(bool alpha) { fHasAlpha = alpha; }
+
+ /**
+ * Whether enough of the frame has been read to determine
+ * fRequiredFrame and fHasAlpha.
+ */
+ bool reachedStartOfData() const { return fRequiredFrame != kUninitialized; }
+
+ /**
+ * The frame this one depends on.
+ *
+ * Must not be called until fRequiredFrame has been set properly.
+ */
+ int getRequiredFrame() const {
+ SkASSERT(this->reachedStartOfData());
+ return fRequiredFrame;
+ }
+
+ /**
+ * Set the frame that this frame depends on.
+ */
+ void setRequiredFrame(int req) { fRequiredFrame = req; }
+
+ /**
+ * Set the rectangle that is updated by this frame.
+ */
+ void setXYWH(int x, int y, int width, int height) {
+ fRect.setXYWH(x, y, width, height);
+ }
+
+ /**
+ * The rectangle that is updated by this frame.
+ */
+ SkIRect frameRect() const { return fRect; }
+
+ int xOffset() const { return fRect.x(); }
+ int yOffset() const { return fRect.y(); }
+ int width() const { return fRect.width(); }
+ int height() const { return fRect.height(); }
+
+ SkCodecAnimation::DisposalMethod getDisposalMethod() const {
+ return fDisposalMethod;
+ }
+
+ void setDisposalMethod(SkCodecAnimation::DisposalMethod disposalMethod) {
+ fDisposalMethod = disposalMethod;
+ }
+
+ /**
+ * Set the duration (in ms) to show this frame.
+ */
+ void setDuration(int duration) {
+ fDuration = duration;
+ }
+
+ /**
+ * Duration in ms to show this frame.
+ */
+ int getDuration() const {
+ return fDuration;
+ }
+
+ void setBlend(SkCodecAnimation::Blend blend) {
+ fBlend = blend;
+ }
+
+ SkCodecAnimation::Blend getBlend() const {
+ return fBlend;
+ }
+
+protected:
+ virtual bool onReportsAlpha() const = 0;
+
+private:
+ static constexpr int kUninitialized = -2;
+
+ const int fId;
+ bool fHasAlpha;
+ int fRequiredFrame;
+ SkIRect fRect;
+ SkCodecAnimation::DisposalMethod fDisposalMethod;
+ int fDuration;
+ SkCodecAnimation::Blend fBlend;
+};
+
+/**
+ * Base class for an object which holds the SkFrames of an
+ * image sequence.
+ */
+class SkFrameHolder : public SkNoncopyable {
+public:
+ SkFrameHolder()
+ : fScreenWidth(0)
+ , fScreenHeight(0)
+ {}
+
+ virtual ~SkFrameHolder() {}
+
+ /**
+ * Size of the image. Each frame will be contained in
+ * these dimensions (possibly after clipping).
+ */
+ int screenWidth() const { return fScreenWidth; }
+ int screenHeight() const { return fScreenHeight; }
+
+ /**
+ * Compute the opacity and required frame, based on
+ * whether the frame reportsAlpha and how it blends
+ * with prior frames.
+ */
+ void setAlphaAndRequiredFrame(SkFrame*);
+
+ /**
+ * Return the frame with frameId i.
+ */
+ const SkFrame* getFrame(int i) const {
+ return this->onGetFrame(i);
+ }
+
+protected:
+ int fScreenWidth;
+ int fScreenHeight;
+
+ virtual const SkFrame* onGetFrame(int i) const = 0;
+};
+
+#endif // SkFrameHolder_DEFINED
diff --git a/src/codec/SkGifCodec.cpp b/src/codec/SkGifCodec.cpp
index 2584ee57c7..1a01c296fc 100644
--- a/src/codec/SkGifCodec.cpp
+++ b/src/codec/SkGifCodec.cpp
@@ -148,7 +148,7 @@ bool SkGifCodec::onGetFrameInfo(int i, SkCodec::FrameInfo* frameInfo) const {
}
if (frameInfo) {
- frameInfo->fDuration = frameContext->delayTime();
+ frameInfo->fDuration = frameContext->getDuration();
frameInfo->fRequiredFrame = frameContext->getRequiredFrame();
frameInfo->fFullyReceived = frameContext->isComplete();
frameInfo->fAlphaType = frameContext->hasAlpha() ? kUnpremul_SkAlphaType
@@ -282,8 +282,7 @@ void SkGifCodec::initializeSwizzler(const SkImageInfo& dstInfo, int frameIndex)
SkASSERT(frame);
const int xBegin = frame->xOffset();
- const int xEnd = std::min(static_cast<int>(frame->xOffset() + frame->width()),
- static_cast<int>(fReader->screenWidth()));
+ const int xEnd = std::min(frame->frameRect().right(), fReader->screenWidth());
// CreateSwizzler only reads left and right of the frame. We cannot use the frame's raw
// frameRect, since it might extend beyond the edge of the frame.
diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp
index ab0b91b112..c99d8a7689 100644
--- a/src/codec/SkWebpCodec.cpp
+++ b/src/codec/SkWebpCodec.cpp
@@ -5,8 +5,11 @@
* found in the LICENSE file.
*/
+#include "SkBitmap.h"
+#include "SkCanvas.h"
#include "SkCodecPriv.h"
#include "SkColorSpaceXform.h"
+#include "SkRasterPipeline.h"
#include "SkSampler.h"
#include "SkStreamPriv.h"
#include "SkTemplates.h"
@@ -32,10 +35,12 @@ bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) {
return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6);
}
+static SkAlphaType alpha_type(bool hasAlpha) {
+ return hasAlpha ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType;
+}
+
// Parse headers of RIFF container, and check for valid Webp (VP8) content.
-// NOTE: This calls peek instead of read, since onGetPixels will need these
-// bytes again.
-// Returns an SkWebpCodec on success;
+// Returns an SkWebpCodec on success
SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
std::unique_ptr<SkStream> streamDeleter(stream);
@@ -90,8 +95,6 @@ SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
}
// Get the first frame and its "features" to determine the color and alpha types.
- // Since we do not yet support animated webp, this is the only frame that we will
- // decode.
WebPIterator frame;
SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame);
if (!WebPDemuxGetFrame(demux, 1, &frame)) {
@@ -104,23 +107,32 @@ SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
return nullptr;
}
+ const bool hasAlpha = SkToBool(frame.has_alpha)
+ || frame.width != width || frame.height != height;
SkEncodedInfo::Color color;
SkEncodedInfo::Alpha alpha;
switch (features.format) {
case 0:
// This indicates a "mixed" format. We could see this for
// animated webps (multiple fragments).
- // I believe that this is a rare case.
// We could also guess kYUV here, but I think it makes more
// sense to guess kBGRA which is likely closer to the final
// output. Otherwise, we might end up converting
// BGRA->YUVA->BGRA.
- color = SkEncodedInfo::kBGRA_Color;
- alpha = SkEncodedInfo::kUnpremul_Alpha;
+ // Fallthrough:
+ case 2:
+ // This is the lossless format (BGRA).
+ if (hasAlpha) {
+ color = SkEncodedInfo::kBGRA_Color;
+ alpha = SkEncodedInfo::kUnpremul_Alpha;
+ } else {
+ color = SkEncodedInfo::kBGRX_Color;
+ alpha = SkEncodedInfo::kOpaque_Alpha;
+ }
break;
case 1:
// This is the lossy format (YUV).
- if (SkToBool(features.has_alpha) || frame.width != width || frame.height != height) {
+ if (hasAlpha) {
color = SkEncodedInfo::kYUVA_Color;
alpha = SkEncodedInfo::kUnpremul_Alpha;
} else {
@@ -128,11 +140,6 @@ SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
alpha = SkEncodedInfo::kOpaque_Alpha;
}
break;
- case 2:
- // This is the lossless format (BGRA).
- color = SkEncodedInfo::kBGRA_Color;
- alpha = SkEncodedInfo::kUnpremul_Alpha;
- break;
default:
return nullptr;
}
@@ -160,8 +167,9 @@ bool SkWebpCodec::onDimensionsSupported(const SkISize& dim) {
&& dim.height() >= 1 && dim.height() <= info.height();
}
-static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) {
- switch (ct) {
+static WEBP_CSP_MODE webp_decode_mode(const SkImageInfo& info) {
+ const bool premultiply = info.alphaType() == kPremul_SkAlphaType;
+ switch (info.colorType()) {
case kBGRA_8888_SkColorType:
return premultiply ? MODE_bgrA : MODE_BGRA;
case kRGBA_8888_SkColorType:
@@ -173,6 +181,12 @@ static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) {
}
}
+SkWebpCodec::Frame* SkWebpCodec::FrameHolder::appendNewFrame(bool hasAlpha) {
+ const int i = this->size();
+ fFrames.emplace_back(i, hasAlpha);
+ return &fFrames[i];
+}
+
bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const {
if (!desiredSubset) {
return false;
@@ -191,13 +205,210 @@ bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const {
return true;
}
+int SkWebpCodec::onGetRepetitionCount() {
+ auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS);
+ if (!(flags & ANIMATION_FLAG)) {
+ return 0;
+ }
+
+ const int repCount = WebPDemuxGetI(fDemux.get(), WEBP_FF_LOOP_COUNT);
+ if (0 == repCount) {
+ return kRepetitionCountInfinite;
+ }
+
+ return repCount;
+}
+
+int SkWebpCodec::onGetFrameCount() {
+ auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS);
+ if (!(flags & ANIMATION_FLAG)) {
+ return 1;
+ }
+
+ const uint32_t oldFrameCount = fFrameHolder.size();
+ if (fFailed) {
+ return oldFrameCount;
+ }
+
+ const uint32_t frameCount = WebPDemuxGetI(fDemux, WEBP_FF_FRAME_COUNT);
+ if (oldFrameCount == frameCount) {
+ // We have already parsed this.
+ return frameCount;
+ }
+
+ fFrameHolder.reserve(frameCount);
+
+ for (uint32_t i = oldFrameCount; i < frameCount; i++) {
+ WebPIterator iter;
+ SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoIter(&iter);
+
+ if (!WebPDemuxGetFrame(fDemux.get(), i + 1, &iter)) {
+ fFailed = true;
+ break;
+ }
+
+ // libwebp only reports complete frames of an animated image.
+ SkASSERT(iter.complete);
+
+ Frame* frame = fFrameHolder.appendNewFrame(iter.has_alpha);
+ frame->setXYWH(iter.x_offset, iter.y_offset, iter.width, iter.height);
+ frame->setDisposalMethod(iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ?
+ SkCodecAnimation::RestoreBGColor_DisposalMethod :
+ SkCodecAnimation::Keep_DisposalMethod);
+ frame->setDuration(iter.duration);
+ if (WEBP_MUX_BLEND != iter.blend_method) {
+ frame->setBlend(SkCodecAnimation::Blend::kBG);
+ }
+ fFrameHolder.setAlphaAndRequiredFrame(frame);
+ }
+
+ return fFrameHolder.size();
+
+}
+
+const SkFrame* SkWebpCodec::FrameHolder::onGetFrame(int i) const {
+ return static_cast<const SkFrame*>(this->frame(i));
+}
+
+const SkWebpCodec::Frame* SkWebpCodec::FrameHolder::frame(int i) const {
+ SkASSERT(i >= 0 && i < this->size());
+ return &fFrames[i];
+}
+
+bool SkWebpCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const {
+ if (i >= fFrameHolder.size()) {
+ return false;
+ }
+
+ const Frame* frame = fFrameHolder.frame(i);
+ if (!frame) {
+ return false;
+ }
+
+ 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 = alpha_type(frame->hasAlpha());
+ }
+
+ return true;
+}
+
+static bool is_8888(SkColorType colorType) {
+ switch (colorType) {
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void pick_memory_stages(SkColorType ct, SkRasterPipeline::StockStage* load,
+ SkRasterPipeline::StockStage* store) {
+ switch(ct) {
+ case kUnknown_SkColorType:
+ case kAlpha_8_SkColorType:
+ case kARGB_4444_SkColorType:
+ case kIndex_8_SkColorType:
+ case kGray_8_SkColorType:
+ SkASSERT(false);
+ break;
+ case kRGB_565_SkColorType:
+ if (load) *load = SkRasterPipeline::load_565;
+ if (store) *store = SkRasterPipeline::store_565;
+ break;
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType:
+ if (load) *load = SkRasterPipeline::load_8888;
+ if (store) *store = SkRasterPipeline::store_8888;
+ break;
+ case kRGBA_F16_SkColorType:
+ if (load) *load = SkRasterPipeline::load_f16;
+ if (store) *store = SkRasterPipeline::store_f16;
+ break;
+ }
+}
+
+static void blend_line(SkColorType dstCT, void* dst,
+ SkColorType srcCT, void* src,
+ bool needsSrgbToLinear, SkAlphaType at,
+ int width) {
+ // Setup conversion from the source and dest, which will be the same.
+ SkRasterPipeline convert_to_linear_premul;
+ if (needsSrgbToLinear) {
+ convert_to_linear_premul.append_from_srgb(at);
+ }
+ if (kUnpremul_SkAlphaType == at) {
+ // srcover assumes premultiplied inputs.
+ convert_to_linear_premul.append(SkRasterPipeline::premul);
+ }
+
+ SkRasterPipeline p;
+ SkRasterPipeline::StockStage load_dst, store_dst;
+ pick_memory_stages(dstCT, &load_dst, &store_dst);
+
+ // Load the final dst.
+ p.append(load_dst, dst);
+ p.extend(convert_to_linear_premul);
+ p.append(SkRasterPipeline::move_src_dst);
+
+ // Load the src.
+ SkRasterPipeline::StockStage load_src;
+ pick_memory_stages(srcCT, &load_src, nullptr);
+ p.append(load_src, src);
+ if (dstCT != srcCT) {
+ SkASSERT(kBGRA_8888_SkColorType == srcCT);
+ p.append(SkRasterPipeline::swap_rb);
+ }
+ p.extend(convert_to_linear_premul);
+
+ p.append(SkRasterPipeline::srcover);
+
+ // Convert back to dst.
+ if (kUnpremul_SkAlphaType == at) {
+ p.append(SkRasterPipeline::unpremul);
+ }
+ if (needsSrgbToLinear) {
+ p.append(SkRasterPipeline::to_srgb);
+ }
+ p.append(store_dst, dst);
+
+ p.run(0, width);
+}
+
SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
const Options& options, SkPMColor*, int*,
int* rowsDecodedPtr) {
- if (!conversion_possible(dstInfo, this->getInfo()) ||
- !this->initializeColorXform(dstInfo, options.fPremulBehavior))
+ // Ensure that we have parsed this far.
+ const int index = options.fFrameIndex;
+ if (index >= this->onGetFrameCount()) {
+ return kIncompleteInput;
+ }
+
+ const auto& srcInfo = this->getInfo();
{
- return kInvalidConversion;
+ auto info = srcInfo;
+ if (index > 0) {
+ auto alphaType = alpha_type(fFrameHolder.frame(index)->hasAlpha());
+ info = info.makeAlphaType(alphaType);
+ }
+ if (!conversion_possible(dstInfo, info) ||
+ !this->initializeColorXform(dstInfo, options.fPremulBehavior))
+ {
+ return kInvalidConversion;
+ }
+ }
+
+ if (index > 0 && (options.fSubset || dstInfo.dimensions() != srcInfo.dimensions())) {
+ // Subsetting and scaling are tricky when asking for frames beyond frame 0. In order to
+ // support it, we'll need to determine the proper rectangle for a
+ // WEBP_MUX_DISPOSE_BACKGROUND required frame before erasing it. (Currently the order
+ // is backwards.) Disable until it becomes clear that supporting it is important.
+ return kUnimplemented;
}
WebPDecoderConfig config;
@@ -212,16 +423,46 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
WebPIterator frame;
SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame);
- // If this succeeded in NewFromStream(), it should succeed again here.
- SkAssertResult(WebPDemuxGetFrame(fDemux, 1, &frame));
+ // If this succeeded in onGetFrameCount(), it should succeed again here.
+ SkAssertResult(WebPDemuxGetFrame(fDemux, index + 1, &frame));
+ const int requiredFrame = index == 0 ? kNone : fFrameHolder.frame(index)->getRequiredFrame();
// Get the frameRect. libwebp will have already signaled an error if this is not fully
// contained by the canvas.
auto frameRect = SkIRect::MakeXYWH(frame.x_offset, frame.y_offset, frame.width, frame.height);
- SkASSERT(this->getInfo().bounds().contains(frameRect));
- bool frameIsSubset = frameRect.size() != this->getInfo().dimensions();
- if (frameIsSubset) {
- SkSampler::Fill(dstInfo, dst, rowBytes, 0, options.fZeroInitialized);
+ SkASSERT(srcInfo.bounds().contains(frameRect));
+ const bool frameIsSubset = frameRect != srcInfo.bounds();
+ if (kNone == requiredFrame) {
+ if (frameIsSubset) {
+ SkSampler::Fill(dstInfo, dst, rowBytes, 0, options.fZeroInitialized);
+ }
+ } else {
+ if (!options.fHasPriorFrame) {
+ Options prevFrameOpts(options);
+ prevFrameOpts.fFrameIndex = requiredFrame;
+ const auto result = this->getPixels(dstInfo, dst, rowBytes, &prevFrameOpts,
+ nullptr, nullptr);
+ switch (result) {
+ case kSuccess:
+ break;
+ case kIncompleteInput:
+ return kInvalidInput;
+ default:
+ return result;
+ }
+ }
+
+ // Dispose bg color
+ const Frame* priorFrame = fFrameHolder.frame(requiredFrame);
+ if (priorFrame->getDisposalMethod() == SkCodecAnimation::RestoreBGColor_DisposalMethod) {
+ // FIXME: If we add support for scaling/subsets, this rectangle needs to be adjusted.
+ const auto priorRect = priorFrame->frameRect();
+ const auto info = dstInfo.makeWH(priorRect.width(), priorRect.height());
+ const size_t bpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
+ const size_t offset = priorRect.x() * bpp + priorRect.y() * rowBytes;
+ auto* eraseDst = SkTAddOffset<void>(dst, offset);
+ SkSampler::Fill(info, eraseDst, rowBytes, 0, kNo_ZeroInitialized);
+ }
}
int dstX = frameRect.x();
@@ -265,7 +506,7 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
// Ignore the frame size and offset when determining if scaling is necessary.
int scaledWidth = subsetWidth;
int scaledHeight = subsetHeight;
- SkISize srcSize = options.fSubset ? options.fSubset->size() : this->getInfo().dimensions();
+ SkISize srcSize = options.fSubset ? options.fSubset->size() : srcInfo.dimensions();
if (srcSize != dstInfo.dimensions()) {
config.options.use_scaling = 1;
@@ -291,32 +532,55 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
config.options.scaled_height = scaledHeight;
}
- // Swizzling between RGBA and BGRA is zero cost in a color transform. So when we have a
- // color transform, we should decode to whatever is easiest for libwebp, and then let the
- // color transform swizzle if necessary.
- // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). Lossless webp is
- // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA.
- config.output.colorspace = this->colorXform() ? MODE_BGRA :
- webp_decode_mode(dstInfo.colorType(), dstInfo.alphaType() == kPremul_SkAlphaType);
+ const bool blendWithPrevFrame = requiredFrame != kNone && frame.blend_method == WEBP_MUX_BLEND
+ && frame.has_alpha;
+ if (blendWithPrevFrame && options.fPremulBehavior == SkTransferFunctionBehavior::kRespect) {
+ // Blending is done with SkRasterPipeline, which requires a color space that is valid for
+ // rendering.
+ const auto* cs = dstInfo.colorSpace();
+ if (!cs || (!cs->gammaCloseToSRGB() && !cs->gammaIsLinear())) {
+ return kInvalidConversion;
+ }
+ }
+
+ SkBitmap webpDst;
+ if ((this->colorXform() && !is_8888(dstInfo.colorType())) || blendWithPrevFrame) {
+ // Swizzling between RGBA and BGRA is zero cost in a color transform. So when we have a
+ // color transform, we should decode to whatever is easiest for libwebp, and then let the
+ // color transform swizzle if necessary.
+ // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). Lossless webp is
+ // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA.
+ auto info = dstInfo.makeColorType(kBGRA_8888_SkColorType);
+ if (info.alphaType() == kPremul_SkAlphaType && this->colorXform()) {
+ info = info.makeAlphaType(kUnpremul_SkAlphaType);
+ }
+
+ // We will decode the entire image and then perform the color transform. libwebp
+ // does not provide a row-by-row API. This is a shame particularly when we do not want
+ // 8888, since we will need to create another image sized buffer.
+ webpDst.allocPixels(info);
+ } else {
+ // libwebp can decode directly into the output memory.
+ auto info = dstInfo;
+ if (this->colorXform()) {
+ SkASSERT(is_8888(dstInfo.colorType()));
+ // As above, BGRA is faster or the same cost as RGBA for libwebp,
+ // and we're going to transform, so tell libwebp to use BGRA.
+ info = info.makeColorType(kBGRA_8888_SkColorType);
+ }
+ webpDst.installPixels(info, dst, rowBytes);
+ }
+
+ if (!frame.has_alpha) {
+ webpDst.setAlphaType(kOpaque_SkAlphaType);
+ }
+
+ config.output.colorspace = webp_decode_mode(webpDst.info());
config.output.is_external_memory = 1;
- // We will decode the entire image and then perform the color transform. libwebp
- // does not provide a row-by-row API. This is a shame particularly when we do not want
- // 8888, since we will need to create another image sized buffer.
- SkAutoTMalloc<uint32_t> pixels;
- bool needsCopy = this->colorXform() && kRGBA_8888_SkColorType != dstInfo.colorType() &&
- kBGRA_8888_SkColorType != dstInfo.colorType();
- void* webpDst = needsCopy ? pixels.reset(dstInfo.width() * dstInfo.height()) : dst;
- size_t webpRowBytes = needsCopy ? dstInfo.width() * sizeof(uint32_t) : rowBytes;
- size_t totalBytes = needsCopy ? webpRowBytes * dstInfo.height()
- : dstInfo.getSafeSize(webpRowBytes);
- size_t dstBpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
- size_t webpBpp = needsCopy ? sizeof(uint32_t) : dstBpp;
-
- size_t offset = dstX * webpBpp + dstY * webpRowBytes;
- config.output.u.RGBA.rgba = SkTAddOffset<uint8_t>(webpDst, offset);
- config.output.u.RGBA.stride = (int) webpRowBytes;
- config.output.u.RGBA.size = totalBytes - offset;
+ config.output.u.RGBA.rgba = reinterpret_cast<uint8_t*>(webpDst.getAddr(dstX, dstY));
+ config.output.u.RGBA.stride = static_cast<int>(webpDst.rowBytes());
+ config.output.u.RGBA.size = webpDst.getSafeSize();
SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &config));
if (!idec) {
@@ -339,21 +603,55 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
return kInvalidInput;
}
+ // We're only transforming the new part of the frame, so no need to worry about the
+ // final composited alpha.
+ const auto srcAlpha = 0 == index ? srcInfo.alphaType() : alpha_type(frame.has_alpha);
+ const auto xformAlphaType = select_xform_alpha(dstInfo.alphaType(), srcAlpha);
+ const bool needsSrgbToLinear = dstInfo.gammaCloseToSRGB() &&
+ options.fPremulBehavior == SkTransferFunctionBehavior::kRespect;
+
+ const size_t dstBpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
+ dst = SkTAddOffset<void>(dst, dstBpp * dstX + rowBytes * dstY);
+ const size_t srcRowBytes = config.output.u.RGBA.stride;
+
+ const auto dstCT = dstInfo.colorType();
if (this->colorXform()) {
- SkColorSpaceXform::ColorFormat dstColorFormat = select_xform_format(dstInfo.colorType());
- SkAlphaType xformAlphaType = select_xform_alpha(dstInfo.alphaType(),
- this->getInfo().alphaType());
+ const auto dstColorFormat = select_xform_format(dstInfo.colorType());
+ const auto srcColorFormat = SkColorSpaceXform::kBGRA_8888_ColorFormat;
+ SkASSERT(select_xform_format(webpDst.colorType()) == srcColorFormat);
uint32_t* xformSrc = (uint32_t*) config.output.u.RGBA.rgba;
- void* xformDst = SkTAddOffset<void>(dst, dstBpp * dstX + rowBytes * dstY);
- size_t srcRowBytes = config.output.u.RGBA.stride;
+ SkBitmap tmp;
+ void* xformDst;
+
+ if (blendWithPrevFrame) {
+ // Xform into temporary bitmap big enough for one row.
+ tmp.allocPixels(dstInfo.makeWH(scaledWidth, 1));
+ xformDst = tmp.getPixels();
+ } else {
+ xformDst = dst;
+ }
for (int y = 0; y < rowsDecoded; y++) {
SkAssertResult(this->colorXform()->apply(dstColorFormat, xformDst,
- SkColorSpaceXform::kBGRA_8888_ColorFormat, xformSrc, scaledWidth,
- xformAlphaType));
- xformDst = SkTAddOffset<void>(xformDst, rowBytes);
+ srcColorFormat, xformSrc, scaledWidth, xformAlphaType));
+ if (blendWithPrevFrame) {
+ blend_line(dstCT, &dst, dstCT, &xformDst, needsSrgbToLinear, xformAlphaType,
+ scaledWidth);
+ dst = SkTAddOffset<void>(dst, rowBytes);
+ } else {
+ xformDst = SkTAddOffset<void>(xformDst, rowBytes);
+ }
xformSrc = SkTAddOffset<uint32_t>(xformSrc, srcRowBytes);
}
+ } else if (blendWithPrevFrame) {
+ const uint8_t* src = config.output.u.RGBA.rgba;
+
+ for (int y = 0; y < rowsDecoded; y++) {
+ blend_line(dstCT, &dst, webpDst.colorType(), &src, needsSrgbToLinear,
+ xformAlphaType, scaledWidth);
+ src = SkTAddOffset<const uint8_t>(src, srcRowBytes);
+ dst = SkTAddOffset<void>(dst, rowBytes);
+ }
}
return result;
@@ -365,4 +663,7 @@ SkWebpCodec::SkWebpCodec(int width, int height, const SkEncodedInfo& info,
: INHERITED(width, height, info, stream, std::move(colorSpace))
, fDemux(demux)
, fData(std::move(data))
-{}
+ , fFailed(false)
+{
+ fFrameHolder.setScreenSize(width, height);
+}
diff --git a/src/codec/SkWebpCodec.h b/src/codec/SkWebpCodec.h
index 93b60f646e..f4c7910ee6 100644
--- a/src/codec/SkWebpCodec.h
+++ b/src/codec/SkWebpCodec.h
@@ -11,9 +11,12 @@
#include "SkCodec.h"
#include "SkColorSpace.h"
#include "SkEncodedImageFormat.h"
+#include "SkFrameHolder.h"
#include "SkImageInfo.h"
#include "SkTypes.h"
+#include <vector>
+
class SkStream;
extern "C" {
struct WebPDemuxer;
@@ -37,6 +40,11 @@ protected:
bool onDimensionsSupported(const SkISize&) override;
bool onGetValidSubset(SkIRect* /* desiredSubset */) const override;
+
+ int onGetFrameCount() override;
+ bool onGetFrameInfo(int, FrameInfo*) const override;
+ int onGetRepetitionCount() override;
+
private:
SkWebpCodec(int width, int height, const SkEncodedInfo&, sk_sp<SkColorSpace>, SkStream*,
WebPDemuxer*, sk_sp<SkData>);
@@ -47,6 +55,57 @@ private:
// This should not be freed until the decode is completed.
sk_sp<SkData> fData;
+ class Frame : public SkFrame {
+ public:
+ Frame(int i, bool alpha)
+ : INHERITED(i)
+ , fReportsAlpha(alpha)
+ {}
+ Frame(Frame&& other)
+ : INHERITED(other.frameId())
+ , fReportsAlpha(other.fReportsAlpha)
+ {}
+
+ protected:
+ bool onReportsAlpha() const override {
+ return fReportsAlpha;
+ }
+
+ private:
+ const bool fReportsAlpha;
+
+ typedef SkFrame INHERITED;
+ };
+
+ class FrameHolder : public SkFrameHolder {
+ public:
+ ~FrameHolder() override {}
+ void setScreenSize(int w, int h) {
+ fScreenWidth = w;
+ fScreenHeight = h;
+ }
+ Frame* appendNewFrame(bool hasAlpha);
+ const Frame* frame(int i) const;
+ int size() const {
+ return static_cast<int>(fFrames.size());
+ }
+ void reserve(int size) {
+ fFrames.reserve(size);
+ }
+
+ protected:
+ const SkFrame* onGetFrame(int i) const override;
+
+ private:
+ std::vector<Frame> fFrames;
+ };
+
+ FrameHolder fFrameHolder;
+ // Set to true if WebPDemuxGetFrame fails. This only means
+ // that we will cap the frame count to the frames that
+ // succeed.
+ bool fFailed;
+
typedef SkCodec INHERITED;
};
#endif // SkWebpCodec_DEFINED
diff --git a/tests/CodecAnimTest.cpp b/tests/CodecAnimTest.cpp
index 24b9c13ae9..087f5d89cd 100644
--- a/tests/CodecAnimTest.cpp
+++ b/tests/CodecAnimTest.cpp
@@ -34,11 +34,37 @@ static void write_bm(const char* name, const SkBitmap& bm) {
DEF_TEST(Codec_trunc, r) {
sk_sp<SkData> data(GetResourceAsData("box.gif"));
+ if (!data) {
+ return;
+ }
data = SkData::MakeSubset(data.get(), 0, 23);
std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
codec->getFrameInfo();
}
+// 565 does not support alpha, but there is no reason for it not to support an
+// animated image with a frame that has alpha but then blends onto an opaque
+// frame making the result opaque. Test that we can decode such a frame.
+DEF_TEST(Codec_565, r) {
+ sk_sp<SkData> data(GetResourceAsData("blendBG.webp"));
+ if (!data) {
+ return;
+ }
+ std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(std::move(data)));
+ auto info = codec->getInfo().makeColorType(kRGB_565_SkColorType);
+ SkBitmap bm;
+ bm.allocPixels(info);
+
+ SkCodec::Options options;
+ options.fFrameIndex = 1;
+ options.fHasPriorFrame = false;
+
+ const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(),
+ &options, nullptr, nullptr);
+ REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+}
+
+
DEF_TEST(Codec_frames, r) {
#define kOpaque kOpaque_SkAlphaType
#define kUnpremul kUnpremul_SkAlphaType
@@ -97,6 +123,12 @@ DEF_TEST(Codec_frames, r) {
{ "mandrill.wbmp", 1, {}, {}, {}, 0 },
{ "randPixels.bmp", 1, {}, {}, {}, 0 },
{ "yellow_rose.webp", 1, {}, {}, {}, 0 },
+ { "webp-animated.webp", 3, { 0, 1 }, { kOpaque, kOpaque, kOpaque },
+ { 1000, 500, 1000 }, SkCodec::kRepetitionCountInfinite },
+ { "blendBG.webp", 7, { 0, SkCodec::kNone, SkCodec::kNone, SkCodec::kNone,
+ 3, 3 },
+ { kOpaque, kOpaque, kUnpremul, kOpaque, kUnpremul, kUnpremul },
+ { 525, 500, 525, 437, 609, 729, 444 }, 7 },
};
#undef kOpaque
#undef kUnpremul
@@ -190,17 +222,6 @@ DEF_TEST(Codec_frames, r) {
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:
@@ -212,12 +233,19 @@ DEF_TEST(Codec_frames, r) {
}
};
- auto expectedAlpha = rec.fAlphaTypes[i-1];
+ auto expectedAlpha = 0 == i ? codec->getInfo().alphaType() : 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 (0 == i) {
+ REPORTER_ASSERT(r, frameInfo.fRequiredFrame == SkCodec::kNone);
+ } else 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);
+ }
}
if (TestMode::kIndividual == mode) {
diff --git a/third_party/gif/SkGifImageReader.cpp b/third_party/gif/SkGifImageReader.cpp
index 8c5cfa1ea8..0666fedbc8 100644
--- a/third_party/gif/SkGifImageReader.cpp
+++ b/third_party/gif/SkGifImageReader.cpp
@@ -131,19 +131,19 @@ bool SkGIFLZWContext::outputRow(const unsigned char* rowBegin)
drowEnd = drowStart + rowDup;
// Extend if bottom edge isn't covered because of the shift upward.
- if (((m_frameContext->height() - 1) - drowEnd) <= rowShift)
+ if ((unsigned)((m_frameContext->height() - 1) - drowEnd) <= rowShift)
drowEnd = m_frameContext->height() - 1;
// Clamp first and last rows to upper and lower edge of image.
if (drowStart < 0)
drowStart = 0;
- if ((unsigned)drowEnd >= m_frameContext->height())
+ if (drowEnd >= m_frameContext->height())
drowEnd = m_frameContext->height() - 1;
}
// Protect against too much image data.
- if ((unsigned)drowStart >= m_frameContext->height())
+ if (drowStart >= m_frameContext->height())
return true;
// CALLBACK: Let the client know we have decoded a row.
@@ -159,7 +159,7 @@ bool SkGIFLZWContext::outputRow(const unsigned char* rowBegin)
switch (ipass) {
case 1:
irow += 8;
- if (irow >= m_frameContext->height()) {
+ if (irow >= (unsigned) m_frameContext->height()) {
ipass++;
irow = 4;
}
@@ -167,7 +167,7 @@ bool SkGIFLZWContext::outputRow(const unsigned char* rowBegin)
case 2:
irow += 8;
- if (irow >= m_frameContext->height()) {
+ if (irow >= (unsigned) m_frameContext->height()) {
ipass++;
irow = 2;
}
@@ -175,7 +175,7 @@ bool SkGIFLZWContext::outputRow(const unsigned char* rowBegin)
case 3:
irow += 4;
- if (irow >= m_frameContext->height()) {
+ if (irow >= (unsigned) m_frameContext->height()) {
ipass++;
irow = 1;
}
@@ -183,7 +183,7 @@ bool SkGIFLZWContext::outputRow(const unsigned char* rowBegin)
case 4:
irow += 2;
- if (irow >= m_frameContext->height()) {
+ if (irow >= (unsigned) m_frameContext->height()) {
ipass++;
irow = 0;
}
@@ -192,7 +192,7 @@ bool SkGIFLZWContext::outputRow(const unsigned char* rowBegin)
default:
break;
}
- } while (irow > (m_frameContext->height() - 1));
+ } while (irow > (unsigned) (m_frameContext->height() - 1));
}
return true;
}
@@ -491,8 +491,8 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
// screen.
// Note that we don't inform the client of the size yet, as it might
// change after we read the first frame's image header.
- m_screenWidth = GETINT16(currentComponent);
- m_screenHeight = GETINT16(currentComponent + 2);
+ fScreenWidth = GETINT16(currentComponent);
+ fScreenHeight = GETINT16(currentComponent + 2);
const int globalColorMapColors = 2 << (currentComponent[4] & 0x07);
@@ -623,7 +623,7 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
currentFrame->setDisposalMethod(SkCodecAnimation::Keep_DisposalMethod);
break;
}
- currentFrame->setDelayTime(GETINT16(currentComponent + 1) * 10);
+ currentFrame->setDuration(GETINT16(currentComponent + 1) * 10);
GETN(1, SkGIFConsumeBlock);
break;
}
@@ -706,7 +706,7 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
}
case SkGIFImageHeader: {
- unsigned height, width, xOffset, yOffset;
+ int height, width, xOffset, yOffset;
const unsigned char* currentComponent =
reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
@@ -730,8 +730,8 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
// set to zero, since usually the first frame completely fills
// the image.
if (currentFrameIsFirstFrame()) {
- m_screenHeight = std::max(m_screenHeight, yOffset + height);
- m_screenWidth = std::max(m_screenWidth, xOffset + width);
+ fScreenHeight = std::max(fScreenHeight, yOffset + height);
+ fScreenWidth = std::max(fScreenWidth, xOffset + width);
}
// NOTE: Chromium placed this block after setHeaderDefined, down
@@ -743,8 +743,8 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
// Work around more broken GIF files that have zero image width or
// height.
if (!height || !width) {
- height = m_screenHeight;
- width = m_screenWidth;
+ height = fScreenHeight;
+ width = fScreenWidth;
if (!height || !width) {
// This prevents attempting to continue reading this invalid stream.
GETN(0, SkGIFDone);
@@ -756,13 +756,17 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
// The three low-order bits of currentComponent[8] specify the bits per pixel.
const int numColors = 2 << (currentComponent[8] & 0x7);
if (currentFrameIsFirstFrame()) {
- if (hasTransparentPixel(0, isLocalColormapDefined, numColors)) {
+ const int transPix = m_frames.empty() ? SkGIFColorMap::kNotFound
+ : m_frames[0]->transparentPixel();
+ if (this->hasTransparency(transPix,
+ isLocalColormapDefined, numColors))
+ {
m_firstFrameHasAlpha = true;
m_firstFrameSupportsIndex8 = true;
} else {
const bool frameIsSubset = xOffset > 0 || yOffset > 0
- || xOffset + width < m_screenWidth
- || yOffset + height < m_screenHeight;
+ || width < fScreenWidth
+ || height < fScreenHeight;
m_firstFrameHasAlpha = frameIsSubset;
m_firstFrameSupportsIndex8 = !frameIsSubset;
}
@@ -780,7 +784,7 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
}
- currentFrame->setRect(xOffset, yOffset, width, height);
+ currentFrame->setXYWH(xOffset, yOffset, width, height);
currentFrame->setInterlaced(SkToBool(currentComponent[8] & 0x40));
// Overlaying interlaced, transparent GIFs over
@@ -852,20 +856,14 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
return true;
}
-bool SkGifImageReader::hasTransparentPixel(int i, bool isLocalColormapDefined,
- int localColors) {
- SkASSERT(i >= 0);
- if (m_frames.size() <= static_cast<size_t>(i)) {
- // This should only happen when parsing the first frame.
- SkASSERT(0 == i);
-
- // We did not see a Graphics Control Extension, so no transparent
- // pixel was specified. But if there is no color table, this frame is
- // still transparent.
- return !isLocalColormapDefined && m_globalColorMap.numColors() == 0;
+bool SkGifImageReader::hasTransparency(int transparentPixel, bool isLocalColormapDefined,
+ int localColors) const {
+ const int globalColors = m_globalColorMap.numColors();
+ if (!isLocalColormapDefined && globalColors == 0) {
+ // No color table for this frame, so it is completely transparent.
+ return true;
}
- const int transparentPixel = m_frames[i]->transparentPixel();
if (transparentPixel < 0) {
SkASSERT(SkGIFColorMap::kNotFound == transparentPixel);
return false;
@@ -875,16 +873,9 @@ bool SkGifImageReader::hasTransparentPixel(int i, bool isLocalColormapDefined,
return transparentPixel < localColors;
}
- const int globalColors = m_globalColorMap.numColors();
- if (!globalColors) {
- // No color table for this frame, so the frame is empty.
- // This is technically different from having a transparent
- // pixel, but we'll treat it the same - nothing to draw here.
- return true;
- }
-
// If there is a global color table, it will be parsed before reaching
// here. If its numColors is set, it will be defined.
+ SkASSERT(globalColors > 0);
SkASSERT(m_globalColorMap.isDefined());
return transparentPixel < globalColors;
}
@@ -893,7 +884,7 @@ void SkGifImageReader::addFrameIfNecessary()
{
if (m_frames.empty() || m_frames.back()->isComplete()) {
const size_t i = m_frames.size();
- std::unique_ptr<SkGIFFrameContext> frame(new SkGIFFrameContext(static_cast<int>(i)));
+ std::unique_ptr<SkGIFFrameContext> frame(new SkGIFFrameContext(this, static_cast<int>(i)));
m_frames.push_back(std::move(frame));
}
}
@@ -907,38 +898,43 @@ static SkIRect frame_rect_on_screen(SkIRect frameRect,
return frameRect;
}
-static bool independent(const SkGIFFrameContext& frame) {
+static bool independent(const SkFrame& frame) {
return frame.getRequiredFrame() == SkCodec::kNone;
}
-static bool restore_bg(const SkGIFFrameContext& frame) {
+static bool restore_bg(const SkFrame& frame) {
return frame.getDisposalMethod() == SkCodecAnimation::RestoreBGColor_DisposalMethod;
}
-void SkGifImageReader::setAlphaAndRequiredFrame(SkGIFFrameContext* frame) {
+bool SkGIFFrameContext::onReportsAlpha() const {
+ // Note: We could correct these after decoding - i.e. some frames may turn out to be
+ // independent and opaque if they do not use the transparent pixel, but that would require
+ // checking whether each pixel used the transparent index.
+ return m_owner->hasTransparency(this->transparentPixel(),
+ m_localColorMap.isDefined(), m_localColorMap.numColors());
+}
+
+void SkFrameHolder::setAlphaAndRequiredFrame(SkFrame* frame) {
+ const bool reportsAlpha = frame->reportsAlpha();
+ const auto screenRect = SkIRect::MakeWH(fScreenWidth, fScreenHeight);
+ const auto frameRect = frame_rect_on_screen(frame->frameRect(), screenRect);
+
const int i = frame->frameId();
if (0 == i) {
- frame->setHasAlpha(m_firstFrameHasAlpha);
+ frame->setHasAlpha(reportsAlpha || frameRect != screenRect);
frame->setRequiredFrame(SkCodec::kNone);
return;
}
- // Note: We could correct these after decoding - i.e. some frames may turn out to be
- // 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 auto screenRect = SkIRect::MakeWH(m_screenWidth, m_screenHeight);
- const auto frameRect = frame_rect_on_screen(frame->frameRect(), screenRect);
- if (!transValid && frameRect == screenRect) {
- frame->setHasAlpha(false);
+ const bool blendWithPrevFrame = frame->getBlend() == SkCodecAnimation::Blend::kPriorFrame;
+ if ((!reportsAlpha || !blendWithPrevFrame) && frameRect == screenRect) {
+ frame->setHasAlpha(reportsAlpha);
frame->setRequiredFrame(SkCodec::kNone);
return;
}
- const SkGIFFrameContext* prevFrame = m_frames[i - 1].get();
+ const SkFrame* prevFrame = this->getFrame(i-1);
while (prevFrame->getDisposalMethod() == SkCodecAnimation::RestorePrevious_DisposalMethod) {
const int prevId = prevFrame->frameId();
if (0 == prevId) {
@@ -947,7 +943,7 @@ void SkGifImageReader::setAlphaAndRequiredFrame(SkGIFFrameContext* frame) {
return;
}
- prevFrame = m_frames[prevId - 1].get();
+ prevFrame = this->getFrame(prevId - 1);
}
const bool clearPrevFrame = restore_bg(*prevFrame);
@@ -961,7 +957,7 @@ void SkGifImageReader::setAlphaAndRequiredFrame(SkGIFFrameContext* frame) {
}
}
- if (transValid) {
+ if (reportsAlpha && blendWithPrevFrame) {
// 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.
@@ -979,7 +975,7 @@ void SkGifImageReader::setAlphaAndRequiredFrame(SkGIFFrameContext* frame) {
return;
}
- prevFrame = m_frames[prevRequiredFrame].get();
+ prevFrame = this->getFrame(prevRequiredFrame);
prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect);
}
@@ -998,7 +994,7 @@ void SkGifImageReader::setAlphaAndRequiredFrame(SkGIFFrameContext* frame) {
SkASSERT(prevFrame->getDisposalMethod() == SkCodecAnimation::Keep_DisposalMethod);
frame->setRequiredFrame(prevFrame->frameId());
- frame->setHasAlpha(prevFrame->hasAlpha());
+ frame->setHasAlpha(prevFrame->hasAlpha() || (reportsAlpha && !blendWithPrevFrame));
}
// FIXME: Move this method to close to doLZW().
diff --git a/third_party/gif/SkGifImageReader.h b/third_party/gif/SkGifImageReader.h
index 4667f7963f..6c313e923b 100644
--- a/third_party/gif/SkGifImageReader.h
+++ b/third_party/gif/SkGifImageReader.h
@@ -47,6 +47,7 @@ class SkGifCodec;
#include "SkCodecAnimation.h"
#include "SkColorTable.h"
#include "SkData.h"
+#include "SkFrameHolder.h"
#include "SkImageInfo.h"
#include "SkStreamBuffer.h"
#include "../private/SkTArray.h"
@@ -85,7 +86,7 @@ enum SkGIFState {
SkGIFConsumeComment
};
-struct SkGIFFrameContext;
+class SkGIFFrameContext;
class SkGIFColorMap;
// LZW decoder state machine.
@@ -191,19 +192,15 @@ private:
mutable sk_sp<SkColorTable> m_table;
};
+class SkGifImageReader;
+
// LocalFrame output state machine.
-struct SkGIFFrameContext : SkNoncopyable {
+class SkGIFFrameContext : public SkFrame {
public:
- SkGIFFrameContext(int id)
- : m_frameId(id)
- , m_xOffset(0)
- , m_yOffset(0)
- , m_width(0)
- , m_height(0)
+ SkGIFFrameContext(SkGifImageReader* reader, int id)
+ : INHERITED(id)
+ , m_owner(reader)
, m_transparentPixel(SkGIFColorMap::kNotFound)
- , m_hasAlpha(false)
- , m_disposalMethod(SkCodecAnimation::Keep_DisposalMethod)
- , m_requiredFrame(kUninitialized)
, m_dataSize(0)
, m_progressiveDisplay(false)
, m_interlaced(false)
@@ -226,31 +223,8 @@ public:
bool decode(SkStreamBuffer*, SkGifCodec* client, bool* frameDecoded);
- int frameId() const { return m_frameId; }
- void setRect(unsigned x, unsigned y, unsigned width, unsigned height)
- {
- m_xOffset = x;
- m_yOffset = y;
- m_width = width;
- m_height = height;
- }
- SkIRect frameRect() const { return SkIRect::MakeXYWH(m_xOffset, m_yOffset, m_width, m_height); }
- unsigned xOffset() const { return m_xOffset; }
- unsigned yOffset() const { return m_yOffset; }
- unsigned width() const { return m_width; }
- unsigned height() const { return m_height; }
int transparentPixel() const { return m_transparentPixel; }
void setTransparentPixel(int pixel) { m_transparentPixel = pixel; }
- bool hasAlpha() const { return m_hasAlpha; }
- void setHasAlpha(bool alpha) { m_hasAlpha = alpha; }
- SkCodecAnimation::DisposalMethod getDisposalMethod() const { return m_disposalMethod; }
- void setDisposalMethod(SkCodecAnimation::DisposalMethod disposalMethod) { m_disposalMethod = disposalMethod; }
-
- int getRequiredFrame() const {
- SkASSERT(this->reachedStartOfData());
- return m_requiredFrame;
- }
- void setRequiredFrame(int req) { m_requiredFrame = req; }
unsigned delayTime() const { return m_delayTime; }
void setDelayTime(unsigned delay) { m_delayTime = delay; }
@@ -274,24 +248,14 @@ public:
const SkGIFColorMap& localColorMap() const { return m_localColorMap; }
SkGIFColorMap& localColorMap() { return m_localColorMap; }
- bool reachedStartOfData() const { return m_requiredFrame != kUninitialized; }
+protected:
+ bool onReportsAlpha() const override;
private:
- static constexpr int kUninitialized = -2;
+ // Unowned pointer to the object that owns this frame.
+ const SkGifImageReader* m_owner;
- int m_frameId;
- unsigned m_xOffset;
- unsigned m_yOffset; // With respect to "screen" origin.
- unsigned m_width;
- unsigned m_height;
int m_transparentPixel; // Index of transparent pixel. Value is kNotFound if there is no transparent pixel.
- // Cached value, taking into account:
- // - m_transparentPixel
- // - frameRect
- // - previous required frame
- bool m_hasAlpha;
- SkCodecAnimation::DisposalMethod m_disposalMethod; // Restore to background, leave in place, etc.
- int m_requiredFrame;
int m_dataSize;
bool m_progressiveDisplay; // If true, do Haeberli interlace hack.
@@ -309,9 +273,11 @@ private:
bool m_isComplete;
bool m_isHeaderDefined;
bool m_isDataSizeDefined;
+
+ typedef SkFrame INHERITED;
};
-class SkGifImageReader final : public SkNoncopyable {
+class SkGifImageReader final : public SkFrameHolder {
public:
// This takes ownership of stream.
SkGifImageReader(SkStream* stream)
@@ -319,8 +285,6 @@ public:
, m_state(SkGIFType)
, m_bytesToConsume(6) // Number of bytes for GIF type, either "GIF87a" or "GIF89a".
, m_version(0)
- , m_screenWidth(0)
- , m_screenHeight(0)
, m_loopCount(cLoopCountNotSeen)
, m_streamBuffer(stream)
, m_parseCompleted(false)
@@ -335,9 +299,6 @@ public:
void setClient(SkGifCodec* client) { m_client = client; }
- unsigned screenWidth() const { return m_screenWidth; }
- unsigned screenHeight() const { return m_screenHeight; }
-
// Option to pass to parse(). All enums are negative, because a non-negative value is used to
// indicate that the Reader should parse up to and including the frame indicated.
enum SkGIFParseQuery {
@@ -408,6 +369,16 @@ public:
bool firstFrameSupportsIndex8() const { return m_firstFrameSupportsIndex8; }
+ // Helper function that returns whether an SkGIFFrameContext has transparency.
+ // This method is sometimes called before creating one/parsing its color map,
+ // so it cannot rely on SkGIFFrameContext::transparentPixel or ::localColorMap().
+ bool hasTransparency(int transPix, bool hasLocalColorMap, int localMapColors) const;
+
+protected:
+ const SkFrame* onGetFrame(int i) const override {
+ return static_cast<const SkFrame*>(this->frameContext(i));
+ }
+
private:
// Requires that one byte has been buffered into m_streamBuffer.
unsigned char getOneByte() const {
@@ -415,11 +386,6 @@ private:
}
void addFrameIfNecessary();
- // Must be called *after* the SkGIFFrameContext's color table (if any) has been parsed.
- void setAlphaAndRequiredFrame(SkGIFFrameContext*);
- // This method is sometimes called before creating a SkGIFFrameContext, so it cannot rely
- // on SkGIFFrameContext::localColorMap().
- bool hasTransparentPixel(int frameIndex, bool hasLocalColorMap, int localMapColors);
bool currentFrameIsFirstFrame() const
{
return m_frames.empty() || (m_frames.size() == 1u && !m_frames[0]->isComplete());
@@ -434,8 +400,6 @@ private:
// Global (multi-image) state.
int m_version; // Either 89 for GIF89 or 87 for GIF87.
- unsigned m_screenWidth; // Logical screen width & height.
- unsigned m_screenHeight;
SkGIFColorMap m_globalColorMap;
static constexpr int cLoopCountNotSeen = -2;