Add animation support for WebP images
authormagomez@igalia.com <magomez@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Oct 2017 12:36:10 +0000 (12:36 +0000)
committermagomez@igalia.com <magomez@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Oct 2017 12:36:10 +0000 (12:36 +0000)
https://bugs.webkit.org/show_bug.cgi?id=113124

Reviewed by Žan Doberšek.

.:

* Source/cmake/FindWebP.cmake:

Source/WebCore:

Implement decoding of WebP animations.

Test: fast/images/animated-webp.html

* platform/graphics/ImageBackingStore.h:
(WebCore::ImageBackingStore::blendPixel):
* platform/image-decoders/webp/WEBPImageDecoder.cpp:
(WebCore::WEBPImageDecoder::WEBPImageDecoder):
(WebCore::WEBPImageDecoder::~WEBPImageDecoder):
(WebCore::WEBPImageDecoder::setData):
(WebCore::WEBPImageDecoder::repetitionCount const):
(WebCore::WEBPImageDecoder::frameBufferAtIndex):
(WebCore::WEBPImageDecoder::findFirstRequiredFrameToDecode):
(WebCore::WEBPImageDecoder::decode):
(WebCore::WEBPImageDecoder::decodeFrame):
(WebCore::WEBPImageDecoder::initFrameBuffer):
(WebCore::WEBPImageDecoder::applyPostProcessing):
(WebCore::WEBPImageDecoder::parseHeader):
(WebCore::WEBPImageDecoder::clearFrameBufferCache):
* platform/image-decoders/webp/WEBPImageDecoder.h:

LayoutTests:

Add a new test for WebP animations and skip it on ios, mac and win. Also unskip some
webp tests that are passing on gtk.

* fast/images/animated-webp-expected.html: Added.
* fast/images/animated-webp.html: Added.
* fast/images/resources/awebp00-ref.webp: Added.
* fast/images/resources/awebp00.webp: Added.
* fast/images/resources/awebp01-ref.webp: Added.
* fast/images/resources/awebp01.webp: Added.
* fast/images/resources/awebp02-ref.webp: Added.
* fast/images/resources/awebp02.webp: Added.
* fast/images/resources/awebp03-ref.webp: Added.
* fast/images/resources/awebp03.webp: Added.
* fast/images/resources/awebp04-ref.webp: Added.
* fast/images/resources/awebp04.webp: Added.
* fast/images/resources/awebp05-ref.webp: Added.
* fast/images/resources/awebp05.webp: Added.
* fast/images/resources/awebp06-ref.webp: Added.
* fast/images/resources/awebp06.webp: Added.
* fast/images/resources/awebp07-ref.webp: Added.
* fast/images/resources/awebp07.webp: Added.
* fast/images/resources/awebp08-ref.webp: Added.
* fast/images/resources/awebp08.webp: Added.
* fast/images/resources/awebp09-ref.webp: Added.
* fast/images/resources/awebp09.webp: Added.
* platform/gtk/TestExpectations:
* platform/ios/TestExpectations:
* platform/mac/TestExpectations:
* platform/win/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@222841 268f45cc-cd09-0410-ab3c-d52691b4dbfc

33 files changed:
ChangeLog
LayoutTests/ChangeLog
LayoutTests/fast/images/animated-webp-expected.html [new file with mode: 0644]
LayoutTests/fast/images/animated-webp.html [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp00-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp00.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp01-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp01.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp02-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp02.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp03-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp03.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp04-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp04.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp05-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp05.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp06-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp06.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp07-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp07.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp08-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp08.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp09-ref.webp [new file with mode: 0644]
LayoutTests/fast/images/resources/awebp09.webp [new file with mode: 0644]
LayoutTests/platform/gtk/TestExpectations
LayoutTests/platform/ios/TestExpectations
LayoutTests/platform/mac/TestExpectations
LayoutTests/platform/win/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/ImageBackingStore.h
Source/WebCore/platform/image-decoders/webp/WEBPImageDecoder.cpp
Source/WebCore/platform/image-decoders/webp/WEBPImageDecoder.h
Source/cmake/FindWebP.cmake

index 603ec2d..bc0c2b5 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2017-10-04  Miguel Gomez  <magomez@igalia.com>
+
+        Add animation support for WebP images
+        https://bugs.webkit.org/show_bug.cgi?id=113124
+
+        Reviewed by Žan Doberšek.
+
+        * Source/cmake/FindWebP.cmake:
+
 2017-10-04  Carlos Alberto Lopez Perez  <clopez@igalia.com>
 
         Generate a compile error if release is built without compiler optimizations
index 12b61a6..e77c6d5 100644 (file)
@@ -1,3 +1,40 @@
+2017-10-04  Miguel Gomez  <magomez@igalia.com>
+
+        Add animation support for WebP images
+        https://bugs.webkit.org/show_bug.cgi?id=113124
+
+        Reviewed by Žan Doberšek.
+
+        Add a new test for WebP animations and skip it on ios, mac and win. Also unskip some
+        webp tests that are passing on gtk.
+
+        * fast/images/animated-webp-expected.html: Added.
+        * fast/images/animated-webp.html: Added.
+        * fast/images/resources/awebp00-ref.webp: Added.
+        * fast/images/resources/awebp00.webp: Added.
+        * fast/images/resources/awebp01-ref.webp: Added.
+        * fast/images/resources/awebp01.webp: Added.
+        * fast/images/resources/awebp02-ref.webp: Added.
+        * fast/images/resources/awebp02.webp: Added.
+        * fast/images/resources/awebp03-ref.webp: Added.
+        * fast/images/resources/awebp03.webp: Added.
+        * fast/images/resources/awebp04-ref.webp: Added.
+        * fast/images/resources/awebp04.webp: Added.
+        * fast/images/resources/awebp05-ref.webp: Added.
+        * fast/images/resources/awebp05.webp: Added.
+        * fast/images/resources/awebp06-ref.webp: Added.
+        * fast/images/resources/awebp06.webp: Added.
+        * fast/images/resources/awebp07-ref.webp: Added.
+        * fast/images/resources/awebp07.webp: Added.
+        * fast/images/resources/awebp08-ref.webp: Added.
+        * fast/images/resources/awebp08.webp: Added.
+        * fast/images/resources/awebp09-ref.webp: Added.
+        * fast/images/resources/awebp09.webp: Added.
+        * platform/gtk/TestExpectations:
+        * platform/ios/TestExpectations:
+        * platform/mac/TestExpectations:
+        * platform/win/TestExpectations:
+
 2017-10-04  Joanmarie Diggs  <jdiggs@igalia.com>
 
         AX: [ATK] aria-pressed="mixed" should be exposed via ATK_STATE_INDETERMINATE
diff --git a/LayoutTests/fast/images/animated-webp-expected.html b/LayoutTests/fast/images/animated-webp-expected.html
new file mode 100644 (file)
index 0000000..4899c7e
--- /dev/null
@@ -0,0 +1,13 @@
+<html>
+<head>
+<title>WEBP animation reftest: when animation ends, compare its last frame against the reference static WEBP image.</title>
+<style>
+img { margin: 1px; }
+</style>
+</head>
+<body style="margin: 1px">
+<img src=resources/awebp00-ref.webp><img src=resources/awebp01-ref.webp><img src=resources/awebp02-ref.webp><img src=resources/awebp03-ref.webp><br>
+<img src=resources/awebp04-ref.webp><img src=resources/awebp05-ref.webp><img src=resources/awebp06-ref.webp><img src=resources/awebp07-ref.webp><br>
+<img src=resources/awebp08-ref.webp><img src=resources/awebp09-ref.webp>
+</body>
+</html>
diff --git a/LayoutTests/fast/images/animated-webp.html b/LayoutTests/fast/images/animated-webp.html
new file mode 100644 (file)
index 0000000..cca81b9
--- /dev/null
@@ -0,0 +1,27 @@
+<html>
+<head>
+<title>WEBP animation reftest: when animation ends, compare its last frame against the reference static WEBP image.</title>
+<script>
+    if (window.testRunner)
+        testRunner.dumpAsText(true);
+
+    window.onload = function() {
+        if (window.testRunner)
+            testRunner.waitUntilDone();
+
+        window.setTimeout(function() {
+            if (window.testRunner)
+                testRunner.notifyDone();
+        }, 500);
+    }
+</script>
+<style>
+img { margin: 1px; }
+</style>
+</head>
+<body style="margin: 1px">
+<img src=resources/awebp00.webp><img src=resources/awebp01.webp><img src=resources/awebp02.webp><img src=resources/awebp03.webp><br>
+<img src=resources/awebp04.webp><img src=resources/awebp05.webp><img src=resources/awebp06.webp><img src=resources/awebp07.webp><br>
+<img src=resources/awebp08.webp><img src=resources/awebp09.webp>
+</body>
+</html>
diff --git a/LayoutTests/fast/images/resources/awebp00-ref.webp b/LayoutTests/fast/images/resources/awebp00-ref.webp
new file mode 100644 (file)
index 0000000..1e01344
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp00-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp00.webp b/LayoutTests/fast/images/resources/awebp00.webp
new file mode 100644 (file)
index 0000000..0bab2e8
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp00.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp01-ref.webp b/LayoutTests/fast/images/resources/awebp01-ref.webp
new file mode 100644 (file)
index 0000000..ce30f2f
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp01-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp01.webp b/LayoutTests/fast/images/resources/awebp01.webp
new file mode 100644 (file)
index 0000000..0623e4c
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp01.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp02-ref.webp b/LayoutTests/fast/images/resources/awebp02-ref.webp
new file mode 100644 (file)
index 0000000..b660fcd
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp02-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp02.webp b/LayoutTests/fast/images/resources/awebp02.webp
new file mode 100644 (file)
index 0000000..8db4c68
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp02.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp03-ref.webp b/LayoutTests/fast/images/resources/awebp03-ref.webp
new file mode 100644 (file)
index 0000000..a9872a7
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp03-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp03.webp b/LayoutTests/fast/images/resources/awebp03.webp
new file mode 100644 (file)
index 0000000..3aeab70
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp03.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp04-ref.webp b/LayoutTests/fast/images/resources/awebp04-ref.webp
new file mode 100644 (file)
index 0000000..39a53f3
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp04-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp04.webp b/LayoutTests/fast/images/resources/awebp04.webp
new file mode 100644 (file)
index 0000000..0ef0b49
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp04.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp05-ref.webp b/LayoutTests/fast/images/resources/awebp05-ref.webp
new file mode 100644 (file)
index 0000000..d7e9d38
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp05-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp05.webp b/LayoutTests/fast/images/resources/awebp05.webp
new file mode 100644 (file)
index 0000000..a004592
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp05.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp06-ref.webp b/LayoutTests/fast/images/resources/awebp06-ref.webp
new file mode 100644 (file)
index 0000000..fa12842
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp06-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp06.webp b/LayoutTests/fast/images/resources/awebp06.webp
new file mode 100644 (file)
index 0000000..1e28de5
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp06.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp07-ref.webp b/LayoutTests/fast/images/resources/awebp07-ref.webp
new file mode 100644 (file)
index 0000000..9ffc9fe
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp07-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp07.webp b/LayoutTests/fast/images/resources/awebp07.webp
new file mode 100644 (file)
index 0000000..bd75261
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp07.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp08-ref.webp b/LayoutTests/fast/images/resources/awebp08-ref.webp
new file mode 100644 (file)
index 0000000..74a9e12
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp08-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp08.webp b/LayoutTests/fast/images/resources/awebp08.webp
new file mode 100644 (file)
index 0000000..3817696
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp08.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp09-ref.webp b/LayoutTests/fast/images/resources/awebp09-ref.webp
new file mode 100644 (file)
index 0000000..01d2822
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp09-ref.webp differ
diff --git a/LayoutTests/fast/images/resources/awebp09.webp b/LayoutTests/fast/images/resources/awebp09.webp
new file mode 100644 (file)
index 0000000..d798662
Binary files /dev/null and b/LayoutTests/fast/images/resources/awebp09.webp differ
index 8c66db6..cb659c7 100644 (file)
@@ -183,12 +183,6 @@ webkit.org/b/177447 imported/w3c/web-platform-tests/dom/events/Event-timestamp-h
 
 # Requires WebP support.
 webkit.org/b/98939 fast/canvas/canvas-toDataURL-webp.html [ Skip ]
-# Requires WebP 0.2 support.
-webkit.org/b/98939 fast/images/webp-image-decoding.html [ Skip ]
-# Requires WebP 0.3 and color profile support.
-webkit.org/b/98939 fast/images/webp-color-profile-lossless.html [ Skip ]
-webkit.org/b/98939 fast/images/webp-color-profile-lossy-alpha.html [ Skip ]
-webkit.org/b/98939 fast/images/webp-color-profile-lossy.html [ Skip ]
 
 # DataTransferItems is not yet implemented.
 webkit.org/b/98940 editing/pasteboard/data-transfer-items.html [ Skip ]
index 14ec89f..74be126 100644 (file)
@@ -230,6 +230,7 @@ fast/dom/DeviceOrientation
 
 # WebP image format is not supported
 fast/canvas/canvas-toDataURL-webp.html
+fast/images/animated-webp-expected.html
 
 # Drag-and-drop is not supported:
 editing/pasteboard/datatransfer-items-drop-plaintext-file.html
index 8283eec..45bc056 100644 (file)
@@ -178,6 +178,7 @@ fast/images/webp-color-profile-lossy-alpha.html
 fast/images/webp-color-profile-lossy.html
 http/tests/images/webp-partial-load.html
 http/tests/images/webp-progressive-load.html
+fast/images/animated-webp-expected.html
 
 # Times out because plugins aren't allowed to execute JS after NPP_Destroy has been called in WebKit1's OOP plugins implementation
 webkit.org/b/48929 plugins/evaluate-js-after-removing-plugin-element.html
index cb451ca..0aa7887 100644 (file)
@@ -384,6 +384,7 @@ fast/images/webp-color-profile-lossy-alpha.html [ Skip ]
 fast/images/webp-color-profile-lossy.html [ Skip ]
 http/tests/images/webp-partial-load.html [ Skip ]
 http/tests/images/webp-progressive-load.html [ Skip ]
+fast/images/animated-webp-expected.html [ Skip ]
 
 # TODO The following tests requires the DRT's dumpUserGestureInFrameLoadCallbacks
 # method. But that method is not implemented in win port since win port can't
index 31c6c86..803f783 100644 (file)
@@ -1,3 +1,31 @@
+2017-10-04  Miguel Gomez  <magomez@igalia.com>
+
+        Add animation support for WebP images
+        https://bugs.webkit.org/show_bug.cgi?id=113124
+
+        Reviewed by Žan Doberšek.
+
+        Implement decoding of WebP animations.
+
+        Test: fast/images/animated-webp.html
+
+        * platform/graphics/ImageBackingStore.h:
+        (WebCore::ImageBackingStore::blendPixel):
+        * platform/image-decoders/webp/WEBPImageDecoder.cpp:
+        (WebCore::WEBPImageDecoder::WEBPImageDecoder):
+        (WebCore::WEBPImageDecoder::~WEBPImageDecoder):
+        (WebCore::WEBPImageDecoder::setData):
+        (WebCore::WEBPImageDecoder::repetitionCount const):
+        (WebCore::WEBPImageDecoder::frameBufferAtIndex):
+        (WebCore::WEBPImageDecoder::findFirstRequiredFrameToDecode):
+        (WebCore::WEBPImageDecoder::decode):
+        (WebCore::WEBPImageDecoder::decodeFrame):
+        (WebCore::WEBPImageDecoder::initFrameBuffer):
+        (WebCore::WEBPImageDecoder::applyPostProcessing):
+        (WebCore::WEBPImageDecoder::parseHeader):
+        (WebCore::WEBPImageDecoder::clearFrameBufferCache):
+        * platform/image-decoders/webp/WEBPImageDecoder.h:
+
 2017-10-04  Ryosuke Niwa  <rniwa@webkit.org>
 
         Use blob URL when pasting RTFD instead of overriding DocumentLoader
index d5bec33..ae65cd0 100644 (file)
@@ -147,7 +147,6 @@ public:
         setPixel(pixelAt(x, y), r, g, b, a);
     }
 
-#if ENABLE(APNG)
     void blendPixel(RGBA32* dest, unsigned r, unsigned g, unsigned b, unsigned a)
     {
         if (!a)
@@ -173,7 +172,6 @@ public:
         else
             *dest = makeUnPremultipliedRGBA(r, g, b, a);
     }
-#endif
 
     static bool isOverSize(const IntSize& size)
     {
index 83fbba5..7149859 100644 (file)
 
 #if USE(WEBP)
 
-// Backward emulation for earlier versions than 0.1.99.
-#if (WEBP_DECODER_ABI_VERSION < 0x0163)
-#define MODE_rgbA MODE_RGBA
-#define MODE_bgrA MODE_BGRA
-#endif
-
-#if CPU(BIG_ENDIAN) || CPU(MIDDLE_ENDIAN)
-inline WEBP_CSP_MODE outputMode(bool hasAlpha) { return hasAlpha ? MODE_rgbA : MODE_RGBA; }
-#else // LITTLE_ENDIAN, output BGRA pixels.
-inline WEBP_CSP_MODE outputMode(bool hasAlpha) { return hasAlpha ? MODE_bgrA : MODE_BGRA; }
-#endif
-
 namespace WebCore {
 
 WEBPImageDecoder::WEBPImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption gammaAndColorProfileOption)
     : ScalableImageDecoder(alphaOption, gammaAndColorProfileOption)
-    , m_decoder(0)
-    , m_hasAlpha(false)
 {
 }
 
 WEBPImageDecoder::~WEBPImageDecoder()
 {
-    clear();
 }
 
-void WEBPImageDecoder::clear()
+void WEBPImageDecoder::setData(SharedBuffer& data, bool allDataReceived)
 {
-    if (m_decoder)
-        WebPIDelete(m_decoder);
-    m_decoder = 0;
+    if (failed())
+        return;
+
+    // We need to ensure that the header is parsed everytime new data arrives, as the number
+    // of frames may change until we have the complete data. If the size has not been obtained
+    // yet, the call to ScalableImageDecoder::setData() will call parseHeader() and set
+    // m_headerParsed to true, so the call to parseHeader() at the end won't do anything. If the size
+    // is available, then parseHeader() is only called once.
+    m_headerParsed = false;
+    ScalableImageDecoder::setData(data, allDataReceived);
+    parseHeader();
+}
+
+RepetitionCount WEBPImageDecoder::repetitionCount() const
+{
+    if (failed())
+        return RepetitionCountOnce;
+
+    return m_repetitionCount ? m_repetitionCount : RepetitionCountInfinite;
 }
 
 ImageFrame* WEBPImageDecoder::frameBufferAtIndex(size_t index)
 {
-    if (index)
+    if (index >= frameCount())
         return 0;
 
-    if (m_frameBufferCache.isEmpty())
-        m_frameBufferCache.grow(1);
+    // The size of m_frameBufferCache may be smaller than the index requested. This can happen
+    // because new data may have arrived with a bigger frameCount, but decode() hasn't been called
+    // yet, which is the one that resizes the cache.
+    if ((m_frameBufferCache.size() > index) && m_frameBufferCache[index].isComplete())
+        return &m_frameBufferCache[index];
+
+    decode(index, isAllDataReceived());
+
+    return &m_frameBufferCache[index];
+}
+
+size_t WEBPImageDecoder::findFirstRequiredFrameToDecode(size_t frameIndex, WebPDemuxer* demuxer)
+{
+    // The first frame doesn't depend on any other.
+    if (!frameIndex)
+        return 0;
+
+    // Check the most probable scenario first: the previous frame is complete, so we can decode the requested one.
+    if (m_frameBufferCache[frameIndex - 1].isComplete())
+        return frameIndex;
+
+    // Check if the requested frame can be rendered without dependencies. This happens if the frame
+    // fills the whole area and doesn't have alpha.
+    WebPIterator webpFrame;
+    if (WebPDemuxGetFrame(demuxer, frameIndex + 1, &webpFrame)) {
+        IntRect frameRect(webpFrame.x_offset, webpFrame.y_offset, webpFrame.width, webpFrame.height);
+        if (frameRect.contains(IntRect(IntPoint(), size())) && !webpFrame.has_alpha)
+            return frameIndex;
+    }
 
-    ImageFrame& frame = m_frameBufferCache[0];
-    if (!frame.isComplete())
-        decode(false, isAllDataReceived());
-    return &frame;
+    // Go backwards in the list of frames, until we find the first complete frame or a frame that
+    // doesn't depend on previous frames.
+    for (size_t i = frameIndex - 1; i > 0; i--) {
+        // This frame is complete, so we can start the decoding from the next one.
+        if (m_frameBufferCache[i].isComplete())
+            return i + 1;
+
+        if (WebPDemuxGetFrame(demuxer, i + 1, &webpFrame)) {
+            IntRect frameRect(webpFrame.x_offset, webpFrame.y_offset, webpFrame.width, webpFrame.height);
+            // This frame is not complete, but it fills the whole size and its disposal method is
+            // RestoreToBackground. This means that we will draw the next frame on an initially transparent
+            // buffer, so there's no dependency. We can start decoding from the next frame.
+            if (frameRect.contains(IntRect(IntPoint(), size())) && (m_frameBufferCache[i].disposalMethod() == ImageFrame::DisposalMethod::RestoreToBackground))
+                return i + 1;
+
+            // This frame is not complete, but it fills the whole size and doesn't have alpha,
+            // so it doesn't depend on former frames. We can start decoding from here.
+            if (frameRect.contains(IntRect(IntPoint(), size())) && !webpFrame.has_alpha)
+                return i;
+        }
+    }
+    return 0;
 }
 
-bool WEBPImageDecoder::decode(bool onlySize, bool)
+void WEBPImageDecoder::decode(size_t frameIndex, bool allDataReceived)
 {
     if (failed())
-        return false;
+        return;
 
-    const uint8_t* dataBytes = reinterpret_cast<const uint8_t*>(m_data->data());
-    const size_t dataSize = m_data->size();
+    // This can be executed both in the main thread (when not using async decoding) or in the decoding thread.
+    // When executed in the decoding thread, a call to setData() from the main thread may change the data
+    // the WebPDemuxer is using, leaving it in an inconsistent state, so we need to protect the data.
+    RefPtr<SharedBuffer> protectedData(m_data);
+    WebPData inputData = { reinterpret_cast<const uint8_t*>(protectedData->data()), protectedData->size() };
+    WebPDemuxState demuxerState;
+    WebPDemuxer* demuxer = WebPDemuxPartial(&inputData, &demuxerState);
+    if (!demuxer) {
+        setFailed();
+        return;
+    }
 
-    if (ScalableImageDecoder::encodedDataStatus() < EncodedDataStatus::SizeAvailable) {
-        static const size_t imageHeaderSize = 30;
-        if (dataSize < imageHeaderSize)
-            return false;
-        int width, height;
-#if (WEBP_DECODER_ABI_VERSION >= 0x0163)
-        WebPBitstreamFeatures features;
-        if (WebPGetFeatures(dataBytes, dataSize, &features) != VP8_STATUS_OK)
-            return setFailed();
-        width = features.width;
-        height = features.height;
-        m_hasAlpha = features.has_alpha;
-#else
-        // Earlier version won't be able to display WebP files with alpha.
-        if (!WebPGetInfo(dataBytes, dataSize, &width, &height))
-            return setFailed();
-        m_hasAlpha = false;
-#endif
-        if (!setSize(IntSize(width, height)))
-            return setFailed();
+    m_frameBufferCache.resize(m_frameCount);
+
+    // It is a fatal error if all data is received and we have decoded all frames available but the file is truncated.
+    if (frameIndex >= m_frameBufferCache.size() - 1 && allDataReceived && demuxer && demuxerState != WEBP_DEMUX_DONE) {
+        setFailed();
+        return;
     }
 
-    ASSERT(ScalableImageDecoder::encodedDataStatus() >= EncodedDataStatus::SizeAvailable);
-    if (onlySize)
-        return true;
+    size_t startFrame = findFirstRequiredFrameToDecode(frameIndex, demuxer);
+    for (size_t i = startFrame; i <= frameIndex; i++)
+        decodeFrame(i, demuxer);
 
-    ASSERT(!m_frameBufferCache.isEmpty());
-    ImageFrame& buffer = m_frameBufferCache[0];
+    WebPDemuxDelete(demuxer);
+}
+
+void WEBPImageDecoder::decodeFrame(size_t frameIndex, WebPDemuxer* demuxer)
+{
+    if (failed())
+        return;
+
+    WebPIterator webpFrame;
+    if (!WebPDemuxGetFrame(demuxer, frameIndex + 1, &webpFrame))
+        return;
+
+    const uint8_t* dataBytes = reinterpret_cast<const uint8_t*>(webpFrame.fragment.bytes);
+    size_t dataSize = webpFrame.fragment.size;
+    bool blend = webpFrame.blend_method == WEBP_MUX_BLEND ? true : false;
+
+    ASSERT(m_frameBufferCache.size() > frameIndex);
+    ImageFrame& buffer = m_frameBufferCache[frameIndex];
+    buffer.setDuration(Seconds::fromMilliseconds(webpFrame.duration));
+    buffer.setDisposalMethod(webpFrame.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? ImageFrame::DisposalMethod::RestoreToBackground : ImageFrame::DisposalMethod::DoNotDispose);
     ASSERT(!buffer.isComplete());
 
-    if (buffer.isInvalid()) {
-        if (!buffer.initialize(size(), m_premultiplyAlpha))
-            return setFailed();
-        buffer.setDecodingStatus(DecodingStatus::Partial);
-        buffer.setHasAlpha(m_hasAlpha);
+    if (buffer.isInvalid() && !initFrameBuffer(frameIndex, &webpFrame)) {
+        setFailed();
+        return;
     }
 
-    if (!m_decoder) {
-        WEBP_CSP_MODE mode = outputMode(m_hasAlpha);
-        if (!m_premultiplyAlpha)
-            mode = outputMode(false);
-        int rowStride = size().width() * sizeof(RGBA32);
-        uint8_t* output = reinterpret_cast<uint8_t*>(buffer.backingStore()->pixelAt(0, 0));
-        int outputSize = size().height() * rowStride;
-        m_decoder = WebPINewRGB(mode, output, outputSize, rowStride);
-        if (!m_decoder)
-            return setFailed();
+    WebPDecBuffer decoderBuffer;
+    WebPInitDecBuffer(&decoderBuffer);
+    decoderBuffer.colorspace = MODE_RGBA;
+    decoderBuffer.u.RGBA.stride = webpFrame.width * sizeof(RGBA32);
+    decoderBuffer.u.RGBA.size = decoderBuffer.u.RGBA.stride * webpFrame.height;
+    decoderBuffer.is_external_memory = 1;
+    decoderBuffer.u.RGBA.rgba = reinterpret_cast<uint8_t*>(fastMalloc(decoderBuffer.u.RGBA.size));
+    if (!decoderBuffer.u.RGBA.rgba) {
+        setFailed();
+        return;
     }
 
-    switch (WebPIUpdate(m_decoder, dataBytes, dataSize)) {
+    WebPIDecoder* decoder = WebPINewDecoder(&decoderBuffer);
+    if (!decoder) {
+        fastFree(decoderBuffer.u.RGBA.rgba);
+        setFailed();
+        return;
+    }
+
+    switch (WebPIUpdate(decoder, dataBytes, dataSize)) {
     case VP8_STATUS_OK:
+        applyPostProcessing(frameIndex, decoder, decoderBuffer, blend);
         buffer.setDecodingStatus(DecodingStatus::Complete);
-        clear();
-        return true;
+        break;
     case VP8_STATUS_SUSPENDED:
-        return false;
+        if (!isAllDataReceived()) {
+            applyPostProcessing(frameIndex, decoder, decoderBuffer, blend);
+            break;
+        }
+        // Fallthrough.
     default:
-        clear();
-        return setFailed();
+        setFailed();
+    }
+
+    WebPIDelete(decoder);
+    fastFree(decoderBuffer.u.RGBA.rgba);
+}
+
+bool WEBPImageDecoder::initFrameBuffer(size_t frameIndex, const WebPIterator* webpFrame)
+{
+    if (frameIndex >= frameCount())
+        return false;
+
+    ImageFrame& buffer = m_frameBufferCache[frameIndex];
+
+    // Initialize the frame rect in our buffer.
+    IntRect frameRect(webpFrame->x_offset, webpFrame->y_offset, webpFrame->width, webpFrame->height);
+
+    // Make sure the frameRect doesn't extend outside the buffer.
+    if (frameRect.maxX() > size().width())
+        frameRect.setWidth(size().width() - webpFrame->x_offset);
+    if (frameRect.maxY() > size().height())
+        frameRect.setHeight(size().height() - webpFrame->y_offset);
+
+    if (!frameIndex || !m_frameBufferCache[frameIndex - 1].backingStore()) {
+        // This frame doesn't rely on any previous data.
+        if (!buffer.initialize(size(), m_premultiplyAlpha))
+            return false;
+    } else {
+        const ImageFrame& prevBuffer = m_frameBufferCache[frameIndex - 1];
+        ASSERT(prevBuffer.status() == ImageFrame::FrameComplete);
+
+        // Preserve the last frame as the starting state for this frame.
+        if (!prevBuffer.backingStore() || !buffer.initialize(*prevBuffer.backingStore()))
+            return false;
+
+        if (prevBuffer.disposalMethod() == ImageFrame::DisposalMethod::RestoreToBackground) {
+            // We want to clear the previous frame to transparent, without
+            // affecting pixels in the image outside of the frame.
+            const IntRect& prevRect = prevBuffer.backingStore()->frameRect();
+            buffer.backingStore()->clearRect(prevRect);
+        }
+    }
+
+    buffer.setHasAlpha(webpFrame->has_alpha);
+    buffer.backingStore()->setFrameRect(frameRect);
+    buffer.setDecodingStatus(DecodingStatus::Partial);
+
+    return true;
+}
+
+void WEBPImageDecoder::applyPostProcessing(size_t frameIndex, WebPIDecoder* decoder, WebPDecBuffer& decoderBuffer, bool blend)
+{
+    ImageFrame& buffer = m_frameBufferCache[frameIndex];
+    int decodedWidth = 0;
+    int decodedHeight = 0;
+    if (!WebPIDecGetRGB(decoder, &decodedHeight, &decodedWidth, 0, 0))
+        return; // See also https://bugs.webkit.org/show_bug.cgi?id=74062
+    if (decodedHeight <= 0)
+        return;
+
+    const IntRect& frameRect = buffer.backingStore()->frameRect();
+    ASSERT_WITH_SECURITY_IMPLICATION(decodedWidth == frameRect.width());
+    ASSERT_WITH_SECURITY_IMPLICATION(decodedHeight <= frameRect.height());
+    const int left = frameRect.x();
+    const int top = frameRect.y();
+
+    for (int y = 0; y < decodedHeight; y++) {
+        const int canvasY = top + y;
+        for (int x = 0; x < decodedWidth; x++) {
+            const int canvasX = left + x;
+            RGBA32* address = buffer.backingStore()->pixelAt(canvasX, canvasY);
+            uint8_t* pixel = decoderBuffer.u.RGBA.rgba + (y * frameRect.width() + x) * sizeof(RGBA32);
+            if (blend && (pixel[3] < 255))
+                buffer.backingStore()->blendPixel(address, pixel[0], pixel[1], pixel[2], pixel[3]);
+            else
+                buffer.backingStore()->setPixel(address, pixel[0], pixel[1], pixel[2], pixel[3]);
+        }
+    }
+}
+
+void WEBPImageDecoder::parseHeader()
+{
+    if (m_headerParsed)
+        return;
+
+    m_headerParsed = true;
+
+    const unsigned webpHeaderSize = 30; // RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE + VP8_FRAME_HEADER_SIZE
+    if (m_data->size() < webpHeaderSize)
+        return; // Await VP8X header so WebPDemuxPartial succeeds.
+
+    WebPData inputData = { reinterpret_cast<const uint8_t*>(m_data->data()), m_data->size() };
+    WebPDemuxState demuxerState;
+    WebPDemuxer* demuxer = WebPDemuxPartial(&inputData, &demuxerState);
+    if (!demuxer) {
+        setFailed();
+        return;
+    }
+
+    m_frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
+    if (!m_frameCount) {
+        WebPDemuxDelete(demuxer);
+        return; // Wait until the encoded image frame data arrives.
+    }
+
+    int width = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
+    int height = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
+    if (!isSizeAvailable() && !setSize(IntSize(width, height))) {
+        WebPDemuxDelete(demuxer);
+        return;
+    }
+
+    m_formatFlags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
+    if (!(m_formatFlags & ANIMATION_FLAG))
+        m_repetitionCount = WebCore::RepetitionCountNone;
+    else {
+        // Since we have parsed at least one frame, even if partially,
+        // the global animation (ANIM) properties have been read since
+        // an ANIM chunk must precede the ANMF frame chunks.
+        m_repetitionCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
+        ASSERT(m_repetitionCount == (m_repetitionCount & 0xffff)); // Loop count is always <= 16 bits.
+        if (!m_repetitionCount)
+            m_repetitionCount = WebCore::RepetitionCountInfinite;
+    }
+
+    WebPDemuxDelete(demuxer);
+}
+
+void WEBPImageDecoder::clearFrameBufferCache(size_t clearBeforeFrame)
+{
+    if (m_frameBufferCache.isEmpty())
+        return;
+
+    // We don't want to delete the last frame in the cache, as is may be needed for
+    // decoding when new data arrives. See GIFImageDecoder for the full explanation.
+    clearBeforeFrame = std::min(clearBeforeFrame, m_frameBufferCache.size() - 1);
+
+    // Also from GIFImageDecoder: We need to preserve frames such that:
+    //   * We don't clear |clearBeforeFrame|.
+    //   * We don't clear the frame we're currently decoding.
+    //   * We don't clear any frame from which a future initFrameBuffer() call will copy bitmap data.
+    //
+    // In WEBP every frame depends on the previous one or none. That means that frames after clearBeforeFrame
+    // won't need any frame before them to render, so we can clear them all. If we find a buffer that is partial,
+    // don't delete it as it's being decoded.
+    for (int i = clearBeforeFrame - 1; i >= 0; i--) {
+        ImageFrame& buffer = m_frameBufferCache[i];
+        if (buffer.isComplete() || buffer.isInvalid())
+            buffer.clear();
     }
 }
 
index 6cf71f4..b1e340b 100644 (file)
@@ -33,6 +33,7 @@
 #if USE(WEBP)
 
 #include "webp/decode.h"
+#include "webp/demux.h"
 
 namespace WebCore {
 
@@ -46,19 +47,26 @@ public:
     virtual ~WEBPImageDecoder();
 
     String filenameExtension() const override { return ASCIILiteral("webp"); }
+    void setData(SharedBuffer&, bool) final;
     ImageFrame* frameBufferAtIndex(size_t index) override;
+    RepetitionCount repetitionCount() const override;
+    size_t frameCount() const override { return m_frameCount; }
+    void clearFrameBufferCache(size_t) override;
 
 private:
     WEBPImageDecoder(AlphaOption, GammaAndColorProfileOption);
-    void tryDecodeSize(bool allDataReceived) override { decode(true, allDataReceived); }
+    void tryDecodeSize(bool) override { parseHeader(); }
+    void decode(size_t, bool);
+    void decodeFrame(size_t, WebPDemuxer*);
+    void parseHeader();
+    bool initFrameBuffer(size_t, const WebPIterator*);
+    void applyPostProcessing(size_t, WebPIDecoder*, WebPDecBuffer&, bool);
+    size_t findFirstRequiredFrameToDecode(size_t, WebPDemuxer*);
 
-    bool decode(bool onlySize, bool allDataReceived);
-
-    WebPIDecoder* m_decoder;
-    bool m_hasAlpha;
-
-    void applyColorProfile(const uint8_t*, size_t, ImageFrame&) { };
-    void clear();
+    int m_repetitionCount { 0 };
+    size_t m_frameCount { 0 };
+    int m_formatFlags { 0 };
+    bool m_headerParsed { false };
 };
 
 } // namespace WebCore
index 969708e..81e8aa3 100644 (file)
@@ -33,20 +33,34 @@ find_package(PkgConfig)
 pkg_check_modules(PC_WEBP QUIET libwebp)
 
 # Look for the header file.
-find_path(WEBP_INCLUDE_DIRS
+find_path(WEBP_INCLUDE_DIR
     NAMES webp/decode.h
     HINTS ${PC_WEBP_INCLUDEDIR} ${PC_WEBP_INCLUDE_DIRS}
 )
+list(APPEND WEBP_INCLUDE_DIRS ${WEBP_INCLUDE_DIR})
 mark_as_advanced(WEBP_INCLUDE_DIRS)
 
 # Look for the library.
 find_library(
-    WEBP_LIBRARIES
+    WEBP_LIBRARY
     NAMES webp
     HINTS ${PC_WEBP_LIBDIR} ${PC_WEBP_LIBRARY_DIRS}
 )
+list(APPEND WEBP_LIBRARIES ${WEBP_LIBRARY})
 mark_as_advanced(WEBP_LIBRARIES)
 
+find_path(WEBP_DEMUX_INCLUDE_DIR
+    NAMES webp/demux.h
+    HINTS ${PC_WEBP_INCLUDEDIR} ${PC_WEBP_INCLUDE_DIRS}
+)
+list(APPEND WEBP_INCLUDE_DIRS ${WEBP_DEMUX_INCLUDE_DIR})
+
+find_library(WEBP_DEMUX_LIBRARY
+    NAMES webpdemux
+    HINTS ${PC_WEBP_LIBDIR} ${PC_WEBP_LIBRARY_DIRS}
+)
+list(APPEND WEBP_LIBRARIES ${WEBP_DEMUX_LIBRARY})
+
 include(FindPackageHandleStandardArgs)
 find_package_handle_standard_args(WebP REQUIRED_VARS WEBP_INCLUDE_DIRS WEBP_LIBRARIES
                                   FOUND_VAR WEBP_FOUND)