Diff
Modified: trunk/LayoutTests/ChangeLog (286627 => 286628)
--- trunk/LayoutTests/ChangeLog 2021-12-07 23:36:44 UTC (rev 286627)
+++ trunk/LayoutTests/ChangeLog 2021-12-08 00:22:32 UTC (rev 286628)
@@ -1,3 +1,22 @@
+2021-12-07 Yoshiaki Jitsukawa <[email protected]>
+
+ Support Animated JPEG-XL images
+ https://bugs.webkit.org/show_bug.cgi?id=233545
+ <rdar://problem/86077143>
+
+ Reviewed by Don Olmstead.
+
+ Add test for animated JPEG XL, based on animated-gif-loop-count.
+
+ * TestExpectations:
+ * fast/images/animated-jpegxl-loop-count-expected.html: Added.
+ * fast/images/animated-jpegxl-loop-count.html: Added.
+ * fast/images/resources/animated-red-green-blue-repeat-1.jxl: Added.
+ * fast/images/resources/animated-red-green-blue-repeat-2.jxl: Added.
+ * fast/images/resources/animated-red-green-blue-repeat-infinite.jxl: Added.
+ * platform/glib/TestExpectations:
+ * platform/wincairo/TestExpectations:
+
2021-12-07 Robert Jenner <[email protected]>
REGRESSION(iOS 14): svg/W3C-I18N/tspan-direction-rtl.svg is a constant failure
Modified: trunk/LayoutTests/TestExpectations (286627 => 286628)
--- trunk/LayoutTests/TestExpectations 2021-12-07 23:36:44 UTC (rev 286627)
+++ trunk/LayoutTests/TestExpectations 2021-12-08 00:22:32 UTC (rev 286628)
@@ -135,6 +135,7 @@
# These tests don't have to be platform-specific, but they are only implemented on WinCairo now.
fast/images/jpegxl-image-decoding.html [ Skip ]
fast/images/jpegxl-as-image.html [ Skip ]
+fast/images/animated-jpegxl-loop-count.html [ Skip ]
# Only applicable on platforms with dark mode support
css-dark-mode [ Skip ]
Added: trunk/LayoutTests/fast/images/animated-jpegxl-loop-count-expected.html (0 => 286628)
--- trunk/LayoutTests/fast/images/animated-jpegxl-loop-count-expected.html (rev 0)
+++ trunk/LayoutTests/fast/images/animated-jpegxl-loop-count-expected.html 2021-12-08 00:22:32 UTC (rev 286628)
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<style>
+ .box {
+ width: 100px;
+ height: 100px;
+ display: inline-block;
+ }
+</style>
+<body>
+ <div>
+ <p>Frames of a 3-frame animated image with missing loopCount, (repetitionCount = 1):</p>
+ <div class="box" style="background-color: red;"></div>
+ <div class="box" style="background-color: green;"></div>
+ <div class="box" style="background-color: blue;"></div>
+ <div class="box" style="background-color: blue;"></div>
+ </div>
+ <div>
+ <p>Frames of a 3-frame animated image with loopCount = 1, (repetitionCount = 2):</p>
+ <div class="box" style="background-color: red;"></div>
+ <div class="box" style="background-color: green;"></div>
+ <div class="box" style="background-color: blue;"></div>
+ <div class="box" style="background-color: red;"></div>
+ <div class="box" style="background-color: green;"></div>
+ <div class="box" style="background-color: blue;"></div>
+ <div class="box" style="background-color: blue;"></div>
+ </div>
+ <div>
+ <p>Frames of a 3-frame animated image with loopCount = 0, (repetitionCount = infinite):</p>
+ <div class="box" style="background-color: red;"></div>
+ <div class="box" style="background-color: green;"></div>
+ <div class="box" style="background-color: blue;"></div>
+ <div class="box" style="background-color: red;"></div>
+ <div class="box" style="background-color: green;"></div>
+ <div class="box" style="background-color: blue;"></div>
+ <div class="box" style="background-color: red;"></div>
+ </div>
+</body>
+</html>
Added: trunk/LayoutTests/fast/images/animated-jpegxl-loop-count.html (0 => 286628)
--- trunk/LayoutTests/fast/images/animated-jpegxl-loop-count.html (rev 0)
+++ trunk/LayoutTests/fast/images/animated-jpegxl-loop-count.html 2021-12-08 00:22:32 UTC (rev 286628)
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<style>
+ canvas {
+ width: 100px;
+ height: 100px;
+ }
+</style>
+<script src=""
+<body>
+ <div>
+ <p>Frames of a 3-frame animated image with missing loopCount, (repetitionCount = 1):</p>
+ <canvas id="canvas-1"></canvas>
+ <canvas id="canvas-2"></canvas>
+ <canvas id="canvas-3"></canvas>
+ <canvas id="canvas-4"></canvas>
+ </div>
+ <div>
+ <p>Frames of a 3-frame animated image with loopCount = 1, (repetitionCount = 2):</p>
+ <canvas id="canvas-a"></canvas>
+ <canvas id="canvas-b"></canvas>
+ <canvas id="canvas-c"></canvas>
+ <canvas id="canvas-d"></canvas>
+ <canvas id="canvas-e"></canvas>
+ <canvas id="canvas-f"></canvas>
+ <canvas id="canvas-g"></canvas>
+ </div>
+ <div>
+ <p>Frames of a 3-frame animated image with loopCount = 0, (repetitionCount = infinite):</p>
+ <canvas id="canvas-A"></canvas>
+ <canvas id="canvas-B"></canvas>
+ <canvas id="canvas-C"></canvas>
+ <canvas id="canvas-D"></canvas>
+ <canvas id="canvas-E"></canvas>
+ <canvas id="canvas-F"></canvas>
+ <canvas id="canvas-G"></canvas>
+ </div>
+ <script>
+ (function() {
+ var images = [
+ { src: "resources/animated-red-green-blue-repeat-1.jxl", canvasId: '1', frameCount: 4 },
+ { src: "resources/animated-red-green-blue-repeat-2.jxl", canvasId: 'a', frameCount: 7 },
+ { src: "resources/animated-red-green-blue-repeat-infinite.jxl", canvasId: 'A', frameCount: 7 }
+ ];
+ runTest(images);
+ })();
+ </script>
+</body>
+</html>
Added: trunk/LayoutTests/fast/images/resources/animated-red-green-blue-repeat-1.jxl (0 => 286628)
--- trunk/LayoutTests/fast/images/resources/animated-red-green-blue-repeat-1.jxl (rev 0)
+++ trunk/LayoutTests/fast/images/resources/animated-red-green-blue-repeat-1.jxl 2021-12-08 00:22:32 UTC (rev 286628)
@@ -0,0 +1,5 @@
+\xFF
+A(
+����d��K\x8B��\xD4c0v/�� R\xA7?��A_<
+����`��K\x8B��\xD4c0v/��ou����@_\xFC
+��L��K\x8B\x80]\xFE\xFF��ؘ6��(\xFA
\ No newline at end of file
Added: trunk/LayoutTests/fast/images/resources/animated-red-green-blue-repeat-2.jxl (0 => 286628)
--- trunk/LayoutTests/fast/images/resources/animated-red-green-blue-repeat-2.jxl (rev 0)
+++ trunk/LayoutTests/fast/images/resources/animated-red-green-blue-repeat-2.jxl 2021-12-08 00:22:32 UTC (rev 286628)
@@ -0,0 +1,5 @@
+\xFF
+AH
+����d��K\x8B��\xD4c0v/�� R\xA7?��A_<
+����`��K\x8B��\xD4c0v/��ou����@_\xFC
+��L��K\x8B\x80]\xFE\xFF��ؘ6��(\xFA
\ No newline at end of file
Added: trunk/LayoutTests/fast/images/resources/animated-red-green-blue-repeat-infinite.jxl (0 => 286628)
--- trunk/LayoutTests/fast/images/resources/animated-red-green-blue-repeat-infinite.jxl (rev 0)
+++ trunk/LayoutTests/fast/images/resources/animated-red-green-blue-repeat-infinite.jxl 2021-12-08 00:22:32 UTC (rev 286628)
@@ -0,0 +1,5 @@
+\xFF
+A��b
+����d��K\x8B��\xD4c0v/�� R\xA7?��A_<
+����`��K\x8B��\xD4c0v/��ou����@_\xFC
+��L��K\x8B\x80]\xFE\xFF��ؘ6��(\xFA
\ No newline at end of file
Modified: trunk/LayoutTests/platform/glib/TestExpectations (286627 => 286628)
--- trunk/LayoutTests/platform/glib/TestExpectations 2021-12-07 23:36:44 UTC (rev 286627)
+++ trunk/LayoutTests/platform/glib/TestExpectations 2021-12-08 00:22:32 UTC (rev 286628)
@@ -107,6 +107,7 @@
# JPEG-XL tests
fast/images/jpegxl-as-image.html [ Pass ]
fast/images/jpegxl-image-decoding.html [ Pass ]
+fast/images/animated-jpegxl-loop-count.html [ Pass ]
# Some Apple ports don't support RTL scrollbars.
fast/scrolling/rtl-scrollbars-elementFromPoint-static.html [ Pass ]
Modified: trunk/LayoutTests/platform/wincairo/TestExpectations (286627 => 286628)
--- trunk/LayoutTests/platform/wincairo/TestExpectations 2021-12-07 23:36:44 UTC (rev 286627)
+++ trunk/LayoutTests/platform/wincairo/TestExpectations 2021-12-08 00:22:32 UTC (rev 286628)
@@ -272,9 +272,10 @@
# PLATFORM(MAC), internals.rangeForDictionaryLookupAtLocation()
fast/layers/prevent-hit-test-during-layout.html [ Skip ]
-# JPEG XL is disabled until WebKitRequirements is released with libjxl
-fast/images/jpegxl-as-image.html [ Skip ]
-fast/images/jpegxl-image-decoding.html [ Skip ]
+# JPEG-XL Tests
+fast/images/jpegxl-as-image.html [ Pass ]
+fast/images/jpegxl-image-decoding.html [ Pass ]
+fast/images/animated-jpegxl-loop-count.html [ Pass ]
# DataTransferItems is not yet implemented
fast/events/clipboard-dataTransferItemList.html [ Skip ]
Modified: trunk/Source/WebCore/ChangeLog (286627 => 286628)
--- trunk/Source/WebCore/ChangeLog 2021-12-07 23:36:44 UTC (rev 286627)
+++ trunk/Source/WebCore/ChangeLog 2021-12-08 00:22:32 UTC (rev 286628)
@@ -1,3 +1,48 @@
+2021-12-07 Yoshiaki Jitsukawa <[email protected]>
+
+ Support Animated JPEG-XL images
+ https://bugs.webkit.org/show_bug.cgi?id=233545
+ <rdar://problem/86077143>
+
+ Reviewed by Don Olmstead.
+
+ Implement relevant API, such as frameCount() and support a positive
+ frame index for frameBufferAtIndex().
+
+ Introduce a query to count frames because JPEG XL code stream itself doesn't have frame
+ count metadata and we need to scan the code stream to the last frame.
+
+ Also introduce the Size query instead of the onlySize parameter of the decode() function.
+
+ With the DecodeImage query, the decoder decodes a single frame. To
+ specify which frame to decode, we make use of the JxlDecoderRewind()
+ and JxlDecoderSkipFrames() functions of libjxl.
+
+ To omit rewind and skip as much as possible, we record the query to
+ m_lastQuery and use it in shouldRewind().
+
+ Test: fast/images/animated-jpegxl-loop-count.html
+
+ * platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp:
+ (WebCore::JPEGXLImageDecoder::~JPEGXLImageDecoder):
+ (WebCore::JPEGXLImageDecoder::frameCount const):
+ (WebCore::JPEGXLImageDecoder::repetitionCount const):
+ (WebCore::JPEGXLImageDecoder::frameBufferAtIndex):
+ (WebCore::JPEGXLImageDecoder::clearFrameBufferCache):
+ (WebCore::JPEGXLImageDecoder::setFailed):
+ (WebCore::JPEGXLImageDecoder::tryDecodeSize):
+ (WebCore::JPEGXLImageDecoder::hasAlpha const):
+ (WebCore::JPEGXLImageDecoder::hasAnimation const):
+ (WebCore::JPEGXLImageDecoder::ensureDecoderInitialized):
+ (WebCore::JPEGXLImageDecoder::shouldRewind const):
+ (WebCore::JPEGXLImageDecoder::rewind):
+ (WebCore::JPEGXLImageDecoder::updateFrameCount):
+ (WebCore::JPEGXLImageDecoder::decode):
+ (WebCore::JPEGXLImageDecoder::processInput):
+ (WebCore::JPEGXLImageDecoder::imageOut):
+ (WebCore::JPEGXLImageDecoder::clear): Deleted.
+ * platform/image-decoders/jpegxl/JPEGXLImageDecoder.h:
+
2021-12-07 Cameron McCormack <[email protected]>
Move FontCache singleton and instance on WorkerGlobalScope to ThreadGlobalData
Modified: trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp (286627 => 286628)
--- trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp 2021-12-07 23:36:44 UTC (rev 286627)
+++ trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp 2021-12-08 00:22:32 UTC (rev 286628)
@@ -28,6 +28,8 @@
#if USE(JPEGXL)
+#include <optional>
+
namespace WebCore {
JPEGXLImageDecoder::JPEGXLImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption gammaAndColorProfileOption)
@@ -37,55 +39,178 @@
JPEGXLImageDecoder::~JPEGXLImageDecoder()
{
- clear();
}
+size_t JPEGXLImageDecoder::frameCount() const
+{
+ if (!hasAnimation())
+ return 1;
+
+ if (!m_isLastFrameHeaderReceived)
+ const_cast<JPEGXLImageDecoder*>(this)->updateFrameCount();
+
+ return m_frameCount;
+}
+
+RepetitionCount JPEGXLImageDecoder::repetitionCount() const
+{
+ if (hasAnimation()) {
+ if (!m_basicInfo->animation.num_loops) {
+ // If num_loops is zero, repeat infinitely.
+ return RepetitionCountInfinite;
+ }
+ return m_basicInfo->animation.num_loops;
+ }
+ return RepetitionCountNone;
+}
+
ScalableImageDecoderFrame* JPEGXLImageDecoder::frameBufferAtIndex(size_t index)
{
- // TODO: To support animated JPEG XL in the future we need to handle second and subsequent frames.
- if (index)
+ if (ScalableImageDecoder::encodedDataStatus() < EncodedDataStatus::SizeAvailable)
return nullptr;
+ if (index >= frameCount())
+ index = frameCount() - 1;
+
if (m_frameBufferCache.isEmpty())
m_frameBufferCache.grow(1);
- auto& frame = m_frameBufferCache[0];
+ auto& frame = m_frameBufferCache[index];
if (!frame.isComplete())
- decode(false, isAllDataReceived());
+ decode(Query::DecodedImage, index, isAllDataReceived());
return &frame;
}
-void JPEGXLImageDecoder::clear()
+void JPEGXLImageDecoder::clearFrameBufferCache(size_t clearBeforeFrame)
{
- m_decoder.reset();
- m_readOffset = 0;
+ if (m_frameBufferCache.isEmpty())
+ return;
+
+ // Unlike the png and gif cases, we can always try to clear frames before "clearBeforeFrame" because
+ // the dependenciy to the previous frame is handled by libjxl.
+ const Vector<ScalableImageDecoderFrame>::iterator end(m_frameBufferCache.begin() + clearBeforeFrame);
+
+ for (Vector<ScalableImageDecoderFrame>::iterator i(m_frameBufferCache.begin()); i != end; ++i) {
+ // If the frame is partial, we're still on the way to decode the frame and it's likely
+ // we continue the decode, so we don't clear the frame.
+ if (i->isPartial())
+ continue;
+
+ i->clear();
+ }
}
bool JPEGXLImageDecoder::setFailed()
{
- clear();
+ m_decoder.reset();
return ScalableImageDecoder::setFailed();
}
-void JPEGXLImageDecoder::decode(bool onlySize, bool allDataReceived)
+void JPEGXLImageDecoder::tryDecodeSize(bool allDataReceived)
{
+ if (m_basicInfo)
+ return;
+ decode(Query::Size, 0, allDataReceived);
+}
+
+bool JPEGXLImageDecoder::hasAlpha() const
+{
+ return m_basicInfo && m_basicInfo->alpha_bits > 0;
+}
+
+bool JPEGXLImageDecoder::hasAnimation() const
+{
+ return m_basicInfo && m_basicInfo->have_animation;
+}
+
+void JPEGXLImageDecoder::ensureDecoderInitialized()
+{
if (failed())
return;
+ if (m_decoder)
+ return;
+
+ m_decoder = JxlDecoderMake(nullptr);
if (!m_decoder) {
- clear();
- m_decoder = JxlDecoderMake(nullptr);
- if (!m_decoder) {
- setFailed();
- return;
- }
+ setFailed();
+ return;
+ }
- if (JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
- setFailed();
- return;
+ if (JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
+ setFailed();
+ return;
+ }
+
+ m_readOffset = 0;
+ m_currentFrame = 0;
+ m_lastQuery = Query::Size;
+}
+
+bool JPEGXLImageDecoder::shouldRewind(Query query, size_t frameIndex) const
+{
+ if (m_lastQuery == Query::FrameCount) {
+ // If the current query is not FrameCount, we've completed the previous FrameCount query
+ // and the decoder has reached the EOF, so we need to rewind the decoder for the new query.
+ // Otherwise we continue the FrameCount query, so we must not rewind the decoder.
+ return query != Query::FrameCount;
+ }
+
+ if (m_lastQuery == Query::Size) {
+ // The decoder is at the stream header and doesn't need rewind.
+ return false;
+ }
+
+ // At this point we know m_lastQuery is Query::DecodedImage.
+
+ if (query != Query::DecodedImage)
+ return true;
+
+ // There's two cases where we don't need to rewind:
+ // 1. Previous decoding was interrupted with JXL_DEC_NEED_MORE_INPUT
+ // and trying to continue decoding the same frame.
+ // 2. Previous decoding was completed (m_currentFrame was incremented) and starting a new frame.
+ // In both cases frameIndex is equal to m_currentFrame.
+ return frameIndex != m_currentFrame;
+}
+
+void JPEGXLImageDecoder::rewind()
+{
+ JxlDecoderRewind(m_decoder.get());
+ JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE);
+ m_readOffset = 0;
+ m_currentFrame = 0;
+}
+
+void JPEGXLImageDecoder::updateFrameCount()
+{
+ if (failed())
+ return;
+
+ decode(Query::FrameCount, 0, isAllDataReceived());
+
+ if (m_frameCount != m_frameBufferCache.size())
+ m_frameBufferCache.resize(m_frameCount);
+}
+
+void JPEGXLImageDecoder::decode(Query query, size_t frameIndex, bool allDataReceived)
+{
+ ensureDecoderInitialized();
+
+ if (failed())
+ return;
+
+ if (shouldRewind(query, frameIndex)) {
+ rewind();
+ // We care about the frameIndex only if the query is Query::DecodedImage.
+ if (query == Query::DecodedImage && frameIndex) {
+ JxlDecoderSkipFrames(m_decoder.get(), frameIndex);
+ m_currentFrame = frameIndex;
}
}
+ m_lastQuery = query;
+
m_data->data();
size_t dataSize = m_data->size();
if (JxlDecoderSetInput(m_decoder.get(), m_data->data() + m_readOffset, dataSize - m_readOffset) != JXL_DEC_SUCCESS) {
@@ -93,7 +218,7 @@
return;
}
- JxlDecoderStatus status = processInput(onlySize);
+ JxlDecoderStatus status = processInput(query);
// We set the status as failed if the decoder reports an error or requires more data while all data has been received.
if (status == JXL_DEC_ERROR || (allDataReceived && status == JXL_DEC_NEED_MORE_INPUT)) {
setFailed();
@@ -100,54 +225,98 @@
return;
}
- // We release the decoder when we finish the decoding.
- if (status == JXL_DEC_SUCCESS) {
- clear();
+ if (query == Query::DecodedImage && status == JXL_DEC_FULL_IMAGE && m_isLastFrameHeaderReceived && m_currentFrame == m_frameCount) {
+ // We free the decoder when the last frame is decoded.
+ m_decoder.reset();
return;
}
- // Otherwise we get the decoder ready for subsequent data.
size_t remainingDataSize = JxlDecoderReleaseInput(m_decoder.get());
m_readOffset = dataSize - remainingDataSize;
}
-JxlDecoderStatus JPEGXLImageDecoder::processInput(bool onlySize)
+JxlDecoderStatus JPEGXLImageDecoder::processInput(Query query)
{
while (true) {
auto status = JxlDecoderProcessInput(m_decoder.get());
+ // Return JXL_DEC_ERROR when we've reached EOF without receiving a frame marked as "is_last".
+ if (status == JXL_DEC_SUCCESS && !m_isLastFrameHeaderReceived)
+ return JXL_DEC_ERROR;
+
// JXL_DEC_ERROR and JXL_DEC_SUCCESS are terminal states. We also exit from the loop if more data is needed.
if (status == JXL_DEC_ERROR || status == JXL_DEC_SUCCESS || status == JXL_DEC_NEED_MORE_INPUT)
return status;
if (status == JXL_DEC_BASIC_INFO) {
- JxlBasicInfo basicInfo;
- if (JxlDecoderGetBasicInfo(m_decoder.get(), &basicInfo) != JXL_DEC_SUCCESS)
- return JXL_DEC_ERROR;
+ if (!m_basicInfo) {
+ JxlBasicInfo basicInfo;
+ if (JxlDecoderGetBasicInfo(m_decoder.get(), &basicInfo) != JXL_DEC_SUCCESS)
+ return JXL_DEC_ERROR;
- setSize(IntSize(basicInfo.xsize, basicInfo.ysize));
- if (onlySize)
+ m_basicInfo = basicInfo;
+ }
+
+ if (query == Query::Size) {
+ // setSize() must be called only if the query is Query::Size,
+ // otherwise this would roll back the encoded data status from completed.
+ setSize(IntSize(m_basicInfo->xsize, m_basicInfo->ysize));
return status;
+ }
+
continue;
}
if (status == JXL_DEC_FRAME) {
JxlPixelFormat format { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
+ JxlFrameHeader frameHeader;
+ if (JxlDecoderGetFrameHeader(m_decoder.get(), &frameHeader) != JXL_DEC_SUCCESS)
+ return JXL_DEC_ERROR;
+
+ m_frameCount = std::max(m_frameCount, m_currentFrame + 1);
+
+ if (frameHeader.is_last)
+ m_isLastFrameHeaderReceived = true;
+
+ if (query != Query::DecodedImage) {
+ if (JxlDecoderSetImageOutCallback(m_decoder.get(), &format, [](void*, size_t, size_t, size_t, const void*) { }, nullptr) != JXL_DEC_SUCCESS)
+ return JXL_DEC_ERROR;
+
+ continue;
+ }
+
+ if (m_currentFrame >= m_frameBufferCache.size())
+ m_frameBufferCache.resize(m_frameCount + 1);
+
+ auto& buffer = m_frameBufferCache[m_currentFrame];
+ if (buffer.isInvalid() && buffer.initialize(size(), m_premultiplyAlpha)) {
+ buffer.setDecodingStatus(DecodingStatus::Partial);
+ buffer.setHasAlpha(false);
+ if (m_basicInfo && m_basicInfo->have_animation) {
+ buffer.setDuration(Seconds((double)frameHeader.duration * m_basicInfo->animation.tps_denominator / m_basicInfo->animation.tps_numerator));
+ buffer.setDisposalMethod(ScalableImageDecoderFrame::DisposalMethod::DoNotDispose);
+ }
+ }
+
if (JxlDecoderSetImageOutCallback(m_decoder.get(), &format, imageOutCallback, this) != JXL_DEC_SUCCESS)
return JXL_DEC_ERROR;
+
continue;
}
if (status == JXL_DEC_FULL_IMAGE) {
- if (m_frameBufferCache.isEmpty())
- continue;
+ if (m_currentFrame < m_frameBufferCache.size()) {
+ auto& buffer = m_frameBufferCache[m_currentFrame];
+ if (!buffer.isInvalid())
+ buffer.setDecodingStatus(DecodingStatus::Complete);
+ }
- auto& buffer = m_frameBufferCache[0];
- if (!buffer.isInvalid())
- buffer.setDecodingStatus(DecodingStatus::Complete);
+ m_currentFrame++;
+ if (query == Query::DecodedImage)
+ return JXL_DEC_FULL_IMAGE;
- // TODO: To support animated JPEG XL in the future we need to handle subsequent data.
- return JXL_DEC_SUCCESS;
+ ASSERT(query == Query::FrameCount);
+ continue;
}
}
}
@@ -159,18 +328,13 @@
void JPEGXLImageDecoder::imageOut(size_t x, size_t y, size_t numPixels, const uint8_t* pixels)
{
- if (m_frameBufferCache.isEmpty())
+ if (m_currentFrame >= m_frameBufferCache.size())
return;
- auto& buffer = m_frameBufferCache[0];
- if (buffer.isInvalid()) {
- if (!buffer.initialize(size(), m_premultiplyAlpha))
- return;
+ auto& buffer = m_frameBufferCache[m_currentFrame];
+ if (buffer.isInvalid())
+ return;
- buffer.setDecodingStatus(DecodingStatus::Partial);
- buffer.setHasAlpha(false);
- }
-
uint32_t* row = buffer.backingStore()->pixelAt(x, y);
uint32_t* currentAddress = row;
for (size_t i = 0; i < numPixels; i++) {
Modified: trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.h (286627 => 286628)
--- trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.h 2021-12-07 23:36:44 UTC (rev 286627)
+++ trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.h 2021-12-08 00:22:32 UTC (rev 286628)
@@ -45,25 +45,50 @@
// ScalableImageDecoder
String filenameExtension() const override { return "jxl"_s; }
+ size_t frameCount() const override;
+ RepetitionCount repetitionCount() const override;
ScalableImageDecoderFrame* frameBufferAtIndex(size_t index) override;
+ void clearFrameBufferCache(size_t clearBeforeFrame) override;
bool setFailed() override;
private:
+ enum class Query {
+ // This query is used for tryDecodeSize().
+ Size,
+ // We define a query for frame count because JPEG XL doesn't have frame count information in its code stream
+ // so we need to scan the code stream to get the frame count for animated JPEG XL.
+ // JPEG XL container can have frame count metadata but currently libjxl doesn't support it.
+ FrameCount,
+ // Query to decode a single frame.
+ DecodedImage,
+ };
+
JPEGXLImageDecoder(AlphaOption, GammaAndColorProfileOption);
- void tryDecodeSize(bool allDataReceived) override { decode(true, allDataReceived); }
+ void tryDecodeSize(bool allDataReceived) override;
- void decode(bool onlySize, bool allDataReceived);
+ bool hasAlpha() const;
+ bool hasAnimation() const;
- void clear();
+ void ensureDecoderInitialized();
+ bool shouldRewind(Query , size_t frameIndex) const;
+ void rewind();
+ void updateFrameCount();
- JxlDecoderStatus processInput(bool onlySize);
-
- static void imageOutCallback(void* that, size_t x, size_t y, size_t numPixels, const void* pixels);
+ void decode(Query, size_t frameIndex, bool allDataReceived);
+ JxlDecoderStatus processInput(Query);
+ static void imageOutCallback(void*, size_t x, size_t y, size_t numPixels, const void* pixels);
void imageOut(size_t x, size_t y, size_t numPixels, const uint8_t* pixels);
JxlDecoderPtr m_decoder;
- size_t m_readOffset = 0;
+ size_t m_readOffset { 0 };
+ std::optional<JxlBasicInfo> m_basicInfo;
+
+ Query m_lastQuery { Query::Size };
+ size_t m_frameCount { 1 };
+ size_t m_currentFrame { 0 };
+
+ bool m_isLastFrameHeaderReceived { false }; // If this is true, we know we don't need to update m_frameCount.
};
} // namespace WebCore