aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--resources/randPixelsAnim.gifbin0 -> 1225 bytes
-rw-r--r--tests/CodecAnimTest.cpp37
-rw-r--r--third_party/gif/SkGifImageReader.cpp160
-rw-r--r--third_party/gif/SkGifImageReader.h14
4 files changed, 140 insertions, 71 deletions
diff --git a/resources/randPixelsAnim.gif b/resources/randPixelsAnim.gif
new file mode 100644
index 0000000000..7b12bfc6f6
--- /dev/null
+++ b/resources/randPixelsAnim.gif
Binary files differ
diff --git a/tests/CodecAnimTest.cpp b/tests/CodecAnimTest.cpp
index 762cd477a7..9f9a160bbb 100644
--- a/tests/CodecAnimTest.cpp
+++ b/tests/CodecAnimTest.cpp
@@ -7,6 +7,9 @@
#include "SkBitmap.h"
#include "SkCodec.h"
+#include "SkCommonFlags.h"
+#include "SkImageEncoder.h"
+#include "SkOSPath.h"
#include "SkStream.h"
#include "Resources.h"
@@ -15,6 +18,19 @@
#include <initializer_list>
#include <vector>
+static void write_bm(const char* name, const SkBitmap& bm) {
+ if (FLAGS_writePath.isEmpty()) {
+ return;
+ }
+
+ SkString filename = SkOSPath::Join(FLAGS_writePath[0], name);
+ filename.appendf(".png");
+ SkFILEWStream file(filename.c_str());
+ if (!SkEncodeImage(&file, bm, SkEncodedImageFormat::kPNG, 100)) {
+ SkDebugf("failed to write '%s'\n", filename.c_str());
+ }
+}
+
DEF_TEST(Codec_frames, r) {
static const struct {
const char* fName;
@@ -27,6 +43,13 @@ DEF_TEST(Codec_frames, r) {
std::vector<size_t> fDurations;
int fRepetitionCount;
} gRecs[] = {
+ { "randPixelsAnim.gif", 13,
+ // required frames
+ { SkCodec::kNone, 1, 2, 3, 4, 4, 6, 7, 7, 7, 7, 7 },
+ // durations
+ { 0, 1000, 170, 40, 220, 7770, 90, 90, 90, 90, 90, 90, 90 },
+ // repetition count
+ 0 },
{ "box.gif", 1, {}, {}, 0 },
{ "color_wheel.gif", 1, {}, {}, 0 },
{ "test640x479.gif", 4, { 0, 1, 2 }, { 200, 200, 200, 200 },
@@ -87,7 +110,10 @@ DEF_TEST(Codec_frames, r) {
// From here on, we are only concerned with animated images.
REPORTER_ASSERT(r, frameInfos[0].fRequiredFrame == SkCodec::kNone);
for (size_t i = 1; i < frameCount; i++) {
- REPORTER_ASSERT(r, rec.fRequiredFrames[i-1] == frameInfos[i].fRequiredFrame);
+ if (rec.fRequiredFrames[i-1] != frameInfos[i].fRequiredFrame) {
+ ERRORF(r, "%s's frame %i has wrong dependency! expected: %i\tactual: %i",
+ rec.fName, i, rec.fRequiredFrames[i-1], frameInfos[i].fRequiredFrame);
+ }
}
// Compare decoding in two ways:
@@ -132,6 +158,10 @@ DEF_TEST(Codec_frames, r) {
SkASSERT(uncachedAddr != nullptr);
const bool lineMatches = memcmp(cachedAddr, uncachedAddr, rowLen) == 0;
if (!lineMatches) {
+ SkString name = SkStringPrintf("cached_%i", i);
+ write_bm(name.c_str(), cachedFrame);
+ name = SkStringPrintf("uncached_%i", i);
+ write_bm(name.c_str(), uncachedFrame);
ERRORF(r, "%s's frame %i is different depending on caching!", rec.fName, i);
break;
}
@@ -145,7 +175,10 @@ DEF_TEST(Codec_frames, r) {
}
for (size_t i = 0; i < frameCount; i++) {
- REPORTER_ASSERT(r, rec.fDurations[i] == frameInfos[i].fDuration);
+ if (rec.fDurations[i] != frameInfos[i].fDuration) {
+ ERRORF(r, "%s frame %i's durations do not match! expected: %i\tactual: %i",
+ rec.fName, i, rec.fDurations[i], frameInfos[i].fDuration);
+ }
}
}
}
diff --git a/third_party/gif/SkGifImageReader.cpp b/third_party/gif/SkGifImageReader.cpp
index ae5663f651..2603807cb3 100644
--- a/third_party/gif/SkGifImageReader.cpp
+++ b/third_party/gif/SkGifImageReader.cpp
@@ -312,17 +312,15 @@ sk_sp<SkColorTable> SkGIFColorMap::buildTable(SkStreamBuffer* streamBuffer, SkCo
return nullptr;
const PackColorProc proc = choose_pack_color_proc(false, colorType);
- if (m_table) {
- if (transparentPixel > (unsigned) m_table->count()
- || m_table->operator[](transparentPixel) == SK_ColorTRANSPARENT) {
- if (proc == m_packColorProc) {
- // This SkColorTable has already been built with the same transparent color and
- // packing proc. Reuse it.
- return m_table;
- }
- }
+ if (m_table && proc == m_packColorProc && m_transPixel == transparentPixel) {
+ SkASSERT(transparentPixel > (unsigned) m_table->count()
+ || m_table->operator[](transparentPixel) == SK_ColorTRANSPARENT);
+ // This SkColorTable has already been built with the same transparent color and
+ // packing proc. Reuse it.
+ return m_table;
}
m_packColorProc = proc;
+ m_transPixel = transparentPixel;
const size_t bytes = m_colors * SK_BYTES_PER_COLORMAP_ENTRY;
sk_sp<SkData> rawData(streamBuffer->getDataAtPosition(m_position, bytes));
@@ -458,7 +456,10 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
}
case SkGIFLZWStart: {
SkASSERT(!m_frames.empty());
- m_frames.back()->setDataSize(this->getOneByte());
+ auto* currentFrame = m_frames.back().get();
+ setRequiredFrame(currentFrame);
+
+ currentFrame->setDataSize(this->getOneByte());
GETN(1, SkGIFSubBlock);
break;
}
@@ -755,33 +756,7 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
// The three low-order bits of currentComponent[8] specify the bits per pixel.
const size_t numColors = 2 << (currentComponent[8] & 0x7);
if (currentFrameIsFirstFrame()) {
- bool hasTransparentPixel;
- if (m_frames.size() == 0) {
- // 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.
- hasTransparentPixel = !isLocalColormapDefined
- && m_globalColorMap.numColors() == 0;
- } else {
- // This means we did see a Graphics Control Extension, which specifies
- // the transparent pixel
- const size_t transparentPixel = m_frames[0]->transparentPixel();
- if (isLocalColormapDefined) {
- hasTransparentPixel = transparentPixel < numColors;
- } else {
- const size_t 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.
- hasTransparentPixel = true;
- } else {
- hasTransparentPixel = transparentPixel < globalColors;
- }
- }
- }
-
- if (hasTransparentPixel) {
+ if (hasTransparentPixel(0, isLocalColormapDefined, numColors)) {
m_firstFrameHasAlpha = true;
m_firstFrameSupportsIndex8 = true;
} else {
@@ -874,44 +849,97 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
return true;
}
+bool SkGifImageReader::hasTransparentPixel(size_t i, bool isLocalColormapDefined,
+ size_t localColors) {
+ if (m_frames.size() <= 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;
+ }
+
+ const size_t transparentPixel = m_frames[i]->transparentPixel();
+ if (isLocalColormapDefined) {
+ return transparentPixel < localColors;
+ }
+
+ const size_t 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(m_globalColorMap.isDefined());
+ return transparentPixel < globalColors;
+}
+
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(i));
- if (0 == i) {
- frame->setRequiredFrame(SkCodec::kNone);
- } else {
- // FIXME: We could correct these after decoding (i.e. some frames may turn out to be
- // independent although we did not determine that here).
- const SkGIFFrameContext* prevFrameContext = m_frames[i - 1].get();
- switch (prevFrameContext->getDisposalMethod()) {
- case SkCodecAnimation::Keep_DisposalMethod:
- frame->setRequiredFrame(i - 1);
- break;
- case SkCodecAnimation::RestorePrevious_DisposalMethod:
- frame->setRequiredFrame(prevFrameContext->getRequiredFrame());
- break;
- case SkCodecAnimation::RestoreBGColor_DisposalMethod:
- // If the prior frame covers the whole image
- if (prevFrameContext->frameRect() == SkIRect::MakeWH(m_screenWidth,
- m_screenHeight)
- // Or the prior frame was independent
- || prevFrameContext->getRequiredFrame() == SkCodec::kNone)
- {
- // This frame is independent, since we clear everything
- // prior frame to the BG color
- frame->setRequiredFrame(SkCodec::kNone);
- } else {
- frame->setRequiredFrame(i - 1);
- }
- break;
- }
- }
m_frames.push_back(std::move(frame));
}
}
+void SkGifImageReader::setRequiredFrame(SkGIFFrameContext* frame) {
+ const size_t i = frame->frameId();
+ if (0 == i) {
+ frame->setRequiredFrame(SkCodec::kNone);
+ return;
+ }
+
+ const SkGIFFrameContext* prevFrame = m_frames[i - 1].get();
+ if (prevFrame->getDisposalMethod() == SkCodecAnimation::RestorePrevious_DisposalMethod) {
+ frame->setRequiredFrame(prevFrame->getRequiredFrame());
+ return;
+ }
+
+ // Note: We could correct these after decoding - i.e. some frames may turn out to be
+ // independent if they do not use the transparent pixel, but that would require
+ // checking whether each pixel used the transparent pixel.
+ const SkGIFColorMap& localMap = frame->localColorMap();
+ const bool transValid = hasTransparentPixel(i, localMap.isDefined(), localMap.numColors());
+
+ const SkIRect prevFrameRect = prevFrame->frameRect();
+ const bool frameCoversPriorFrame = frame->frameRect().contains(prevFrameRect);
+
+ if (!transValid && frameCoversPriorFrame) {
+ frame->setRequiredFrame(prevFrame->getRequiredFrame());
+ return;
+ }
+
+ switch (prevFrame->getDisposalMethod()) {
+ case SkCodecAnimation::Keep_DisposalMethod:
+ frame->setRequiredFrame(i - 1);
+ break;
+ case SkCodecAnimation::RestorePrevious_DisposalMethod:
+ // This was already handled above.
+ SkASSERT(false);
+ break;
+ case SkCodecAnimation::RestoreBGColor_DisposalMethod:
+ // If the prior frame covers the whole image
+ if (prevFrameRect == SkIRect::MakeWH(m_screenWidth, m_screenHeight)
+ // Or the prior frame was independent
+ || prevFrame->getRequiredFrame() == SkCodec::kNone)
+ {
+ // This frame is independent, since we clear everything in the
+ // prior frame to the BG color
+ frame->setRequiredFrame(SkCodec::kNone);
+ } else {
+ frame->setRequiredFrame(i - 1);
+ }
+ break;
+ }
+}
+
// FIXME: Move this method to close to doLZW().
bool SkGIFLZWContext::prepareToDecode()
{
diff --git a/third_party/gif/SkGifImageReader.h b/third_party/gif/SkGifImageReader.h
index ddfd2cef03..c51f4a8983 100644
--- a/third_party/gif/SkGifImageReader.h
+++ b/third_party/gif/SkGifImageReader.h
@@ -148,10 +148,13 @@ struct SkGIFLZWBlock {
class SkGIFColorMap final {
public:
+ static constexpr size_t kNotFound = static_cast<size_t>(-1);
+
SkGIFColorMap()
: m_isDefined(false)
, m_position(0)
, m_colors(0)
+ , m_transPixel(kNotFound)
, m_packColorProc(nullptr)
{
}
@@ -182,6 +185,8 @@ private:
bool m_isDefined;
size_t m_position;
size_t m_colors;
+ // Cached values. If these match on a new request, we can reuse m_table.
+ mutable size_t m_transPixel;
mutable PackColorProc m_packColorProc;
mutable sk_sp<SkColorTable> m_table;
};
@@ -195,7 +200,7 @@ public:
, m_yOffset(0)
, m_width(0)
, m_height(0)
- , m_transparentPixel(kNotFound)
+ , m_transparentPixel(SkGIFColorMap::kNotFound)
, m_disposalMethod(SkCodecAnimation::Keep_DisposalMethod)
, m_requiredFrame(SkCodec::kNone)
, m_dataSize(0)
@@ -209,8 +214,6 @@ public:
{
}
- static constexpr size_t kNotFound = static_cast<size_t>(-1);
-
~SkGIFFrameContext()
{
}
@@ -388,6 +391,11 @@ private:
}
void addFrameIfNecessary();
+ // Must be called *after* the SkGIFFrameContext's color table (if any) has been parsed.
+ void setRequiredFrame(SkGIFFrameContext*);
+ // This method is sometimes called before creating a SkGIFFrameContext, so it cannot rely
+ // on SkGIFFrameContext::localColorMap().
+ bool hasTransparentPixel(size_t frameIndex, bool hasLocalColorMap, size_t localMapColors);
bool currentFrameIsFirstFrame() const
{
return m_frames.empty() || (m_frames.size() == 1u && !m_frames[0]->isComplete());