aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/gif
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 /third_party/gif
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>
Diffstat (limited to 'third_party/gif')
-rw-r--r--third_party/gif/SkGifImageReader.cpp116
-rw-r--r--third_party/gif/SkGifImageReader.h86
2 files changed, 81 insertions, 121 deletions
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;