diff options
author | Leon Scroggins III <scroggo@google.com> | 2017-05-23 09:37:21 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-05-23 15:28:37 +0000 |
commit | 557fbbe05ba48bcc20be684d11fe0edfc24c87ba (patch) | |
tree | de83760e6e67a0866578056b4091091cef29cc53 /third_party/gif | |
parent | e980174d0f3af8e497d9c38851114cc6a3c02e9a (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.cpp | 116 | ||||
-rw-r--r-- | third_party/gif/SkGifImageReader.h | 86 |
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; |