Changeset 286628 in webkit
- Timestamp:
- Dec 7, 2021, 4:22:32 PM (5 years ago)
- Location:
- trunk
- Files:
-
- 5 added
- 7 edited
-
LayoutTests/ChangeLog (modified) (1 diff)
-
LayoutTests/TestExpectations (modified) (1 diff)
-
LayoutTests/fast/images/animated-jpegxl-loop-count-expected.html (added)
-
LayoutTests/fast/images/animated-jpegxl-loop-count.html (added)
-
LayoutTests/fast/images/resources/animated-red-green-blue-repeat-1.jxl (added)
-
LayoutTests/fast/images/resources/animated-red-green-blue-repeat-2.jxl (added)
-
LayoutTests/fast/images/resources/animated-red-green-blue-repeat-infinite.jxl (added)
-
LayoutTests/platform/glib/TestExpectations (modified) (1 diff)
-
LayoutTests/platform/wincairo/TestExpectations (modified) (1 diff)
-
Source/WebCore/ChangeLog (modified) (1 diff)
-
Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp (modified) (7 diffs)
-
Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.h (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r286627 r286628 1 2021-12-07 Yoshiaki Jitsukawa <yoshiaki.jitsukawa@sony.com> 2 3 Support Animated JPEG-XL images 4 https://bugs.webkit.org/show_bug.cgi?id=233545 5 <rdar://problem/86077143> 6 7 Reviewed by Don Olmstead. 8 9 Add test for animated JPEG XL, based on animated-gif-loop-count. 10 11 * TestExpectations: 12 * fast/images/animated-jpegxl-loop-count-expected.html: Added. 13 * fast/images/animated-jpegxl-loop-count.html: Added. 14 * fast/images/resources/animated-red-green-blue-repeat-1.jxl: Added. 15 * fast/images/resources/animated-red-green-blue-repeat-2.jxl: Added. 16 * fast/images/resources/animated-red-green-blue-repeat-infinite.jxl: Added. 17 * platform/glib/TestExpectations: 18 * platform/wincairo/TestExpectations: 19 1 20 2021-12-07 Robert Jenner <Jenner@apple.com> 2 21 -
trunk/LayoutTests/TestExpectations
r286593 r286628 136 136 fast/images/jpegxl-image-decoding.html [ Skip ] 137 137 fast/images/jpegxl-as-image.html [ Skip ] 138 fast/images/animated-jpegxl-loop-count.html [ Skip ] 138 139 139 140 # Only applicable on platforms with dark mode support -
trunk/LayoutTests/platform/glib/TestExpectations
r286586 r286628 108 108 fast/images/jpegxl-as-image.html [ Pass ] 109 109 fast/images/jpegxl-image-decoding.html [ Pass ] 110 fast/images/animated-jpegxl-loop-count.html [ Pass ] 110 111 111 112 # Some Apple ports don't support RTL scrollbars. -
trunk/LayoutTests/platform/wincairo/TestExpectations
r286451 r286628 273 273 fast/layers/prevent-hit-test-during-layout.html [ Skip ] 274 274 275 # JPEG XL is disabled until WebKitRequirements is released with libjxl 276 fast/images/jpegxl-as-image.html [ Skip ] 277 fast/images/jpegxl-image-decoding.html [ Skip ] 275 # JPEG-XL Tests 276 fast/images/jpegxl-as-image.html [ Pass ] 277 fast/images/jpegxl-image-decoding.html [ Pass ] 278 fast/images/animated-jpegxl-loop-count.html [ Pass ] 278 279 279 280 # DataTransferItems is not yet implemented -
trunk/Source/WebCore/ChangeLog
r286625 r286628 1 2021-12-07 Yoshiaki Jitsukawa <yoshiaki.jitsukawa@sony.com> 2 3 Support Animated JPEG-XL images 4 https://bugs.webkit.org/show_bug.cgi?id=233545 5 <rdar://problem/86077143> 6 7 Reviewed by Don Olmstead. 8 9 Implement relevant API, such as frameCount() and support a positive 10 frame index for frameBufferAtIndex(). 11 12 Introduce a query to count frames because JPEG XL code stream itself doesn't have frame 13 count metadata and we need to scan the code stream to the last frame. 14 15 Also introduce the Size query instead of the onlySize parameter of the decode() function. 16 17 With the DecodeImage query, the decoder decodes a single frame. To 18 specify which frame to decode, we make use of the JxlDecoderRewind() 19 and JxlDecoderSkipFrames() functions of libjxl. 20 21 To omit rewind and skip as much as possible, we record the query to 22 m_lastQuery and use it in shouldRewind(). 23 24 Test: fast/images/animated-jpegxl-loop-count.html 25 26 * platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp: 27 (WebCore::JPEGXLImageDecoder::~JPEGXLImageDecoder): 28 (WebCore::JPEGXLImageDecoder::frameCount const): 29 (WebCore::JPEGXLImageDecoder::repetitionCount const): 30 (WebCore::JPEGXLImageDecoder::frameBufferAtIndex): 31 (WebCore::JPEGXLImageDecoder::clearFrameBufferCache): 32 (WebCore::JPEGXLImageDecoder::setFailed): 33 (WebCore::JPEGXLImageDecoder::tryDecodeSize): 34 (WebCore::JPEGXLImageDecoder::hasAlpha const): 35 (WebCore::JPEGXLImageDecoder::hasAnimation const): 36 (WebCore::JPEGXLImageDecoder::ensureDecoderInitialized): 37 (WebCore::JPEGXLImageDecoder::shouldRewind const): 38 (WebCore::JPEGXLImageDecoder::rewind): 39 (WebCore::JPEGXLImageDecoder::updateFrameCount): 40 (WebCore::JPEGXLImageDecoder::decode): 41 (WebCore::JPEGXLImageDecoder::processInput): 42 (WebCore::JPEGXLImageDecoder::imageOut): 43 (WebCore::JPEGXLImageDecoder::clear): Deleted. 44 * platform/image-decoders/jpegxl/JPEGXLImageDecoder.h: 45 1 46 2021-12-07 Cameron McCormack <heycam@apple.com> 2 47 -
trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.cpp
r286011 r286628 29 29 #if USE(JPEGXL) 30 30 31 #include <optional> 32 31 33 namespace WebCore { 32 34 … … 38 40 JPEGXLImageDecoder::~JPEGXLImageDecoder() 39 41 { 40 clear(); 42 } 43 44 size_t JPEGXLImageDecoder::frameCount() const 45 { 46 if (!hasAnimation()) 47 return 1; 48 49 if (!m_isLastFrameHeaderReceived) 50 const_cast<JPEGXLImageDecoder*>(this)->updateFrameCount(); 51 52 return m_frameCount; 53 } 54 55 RepetitionCount JPEGXLImageDecoder::repetitionCount() const 56 { 57 if (hasAnimation()) { 58 if (!m_basicInfo->animation.num_loops) { 59 // If num_loops is zero, repeat infinitely. 60 return RepetitionCountInfinite; 61 } 62 return m_basicInfo->animation.num_loops; 63 } 64 return RepetitionCountNone; 41 65 } 42 66 43 67 ScalableImageDecoderFrame* JPEGXLImageDecoder::frameBufferAtIndex(size_t index) 44 68 { 45 // TODO: To support animated JPEG XL in the future we need to handle second and subsequent frames. 46 if (index) 69 if (ScalableImageDecoder::encodedDataStatus() < EncodedDataStatus::SizeAvailable) 47 70 return nullptr; 71 72 if (index >= frameCount()) 73 index = frameCount() - 1; 48 74 49 75 if (m_frameBufferCache.isEmpty()) 50 76 m_frameBufferCache.grow(1); 51 77 52 auto& frame = m_frameBufferCache[ 0];78 auto& frame = m_frameBufferCache[index]; 53 79 if (!frame.isComplete()) 54 decode( false, isAllDataReceived());80 decode(Query::DecodedImage, index, isAllDataReceived()); 55 81 return &frame; 56 82 } 57 83 58 void JPEGXLImageDecoder::clear() 84 void JPEGXLImageDecoder::clearFrameBufferCache(size_t clearBeforeFrame) 85 { 86 if (m_frameBufferCache.isEmpty()) 87 return; 88 89 // Unlike the png and gif cases, we can always try to clear frames before "clearBeforeFrame" because 90 // the dependenciy to the previous frame is handled by libjxl. 91 const Vector<ScalableImageDecoderFrame>::iterator end(m_frameBufferCache.begin() + clearBeforeFrame); 92 93 for (Vector<ScalableImageDecoderFrame>::iterator i(m_frameBufferCache.begin()); i != end; ++i) { 94 // If the frame is partial, we're still on the way to decode the frame and it's likely 95 // we continue the decode, so we don't clear the frame. 96 if (i->isPartial()) 97 continue; 98 99 i->clear(); 100 } 101 } 102 103 bool JPEGXLImageDecoder::setFailed() 59 104 { 60 105 m_decoder.reset(); 106 return ScalableImageDecoder::setFailed(); 107 } 108 109 void JPEGXLImageDecoder::tryDecodeSize(bool allDataReceived) 110 { 111 if (m_basicInfo) 112 return; 113 decode(Query::Size, 0, allDataReceived); 114 } 115 116 bool JPEGXLImageDecoder::hasAlpha() const 117 { 118 return m_basicInfo && m_basicInfo->alpha_bits > 0; 119 } 120 121 bool JPEGXLImageDecoder::hasAnimation() const 122 { 123 return m_basicInfo && m_basicInfo->have_animation; 124 } 125 126 void JPEGXLImageDecoder::ensureDecoderInitialized() 127 { 128 if (failed()) 129 return; 130 131 if (m_decoder) 132 return; 133 134 m_decoder = JxlDecoderMake(nullptr); 135 if (!m_decoder) { 136 setFailed(); 137 return; 138 } 139 140 if (JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) { 141 setFailed(); 142 return; 143 } 144 61 145 m_readOffset = 0; 62 } 63 64 bool JPEGXLImageDecoder::setFailed() 65 { 66 clear(); 67 return ScalableImageDecoder::setFailed(); 68 } 69 70 void JPEGXLImageDecoder::decode(bool onlySize, bool allDataReceived) 146 m_currentFrame = 0; 147 m_lastQuery = Query::Size; 148 } 149 150 bool JPEGXLImageDecoder::shouldRewind(Query query, size_t frameIndex) const 151 { 152 if (m_lastQuery == Query::FrameCount) { 153 // If the current query is not FrameCount, we've completed the previous FrameCount query 154 // and the decoder has reached the EOF, so we need to rewind the decoder for the new query. 155 // Otherwise we continue the FrameCount query, so we must not rewind the decoder. 156 return query != Query::FrameCount; 157 } 158 159 if (m_lastQuery == Query::Size) { 160 // The decoder is at the stream header and doesn't need rewind. 161 return false; 162 } 163 164 // At this point we know m_lastQuery is Query::DecodedImage. 165 166 if (query != Query::DecodedImage) 167 return true; 168 169 // There's two cases where we don't need to rewind: 170 // 1. Previous decoding was interrupted with JXL_DEC_NEED_MORE_INPUT 171 // and trying to continue decoding the same frame. 172 // 2. Previous decoding was completed (m_currentFrame was incremented) and starting a new frame. 173 // In both cases frameIndex is equal to m_currentFrame. 174 return frameIndex != m_currentFrame; 175 } 176 177 void JPEGXLImageDecoder::rewind() 178 { 179 JxlDecoderRewind(m_decoder.get()); 180 JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE); 181 m_readOffset = 0; 182 m_currentFrame = 0; 183 } 184 185 void JPEGXLImageDecoder::updateFrameCount() 71 186 { 72 187 if (failed()) 73 188 return; 74 189 75 if (!m_decoder) { 76 clear(); 77 m_decoder = JxlDecoderMake(nullptr); 78 if (!m_decoder) { 79 setFailed(); 80 return; 81 } 82 83 if (JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) { 84 setFailed(); 85 return; 86 } 87 } 190 decode(Query::FrameCount, 0, isAllDataReceived()); 191 192 if (m_frameCount != m_frameBufferCache.size()) 193 m_frameBufferCache.resize(m_frameCount); 194 } 195 196 void JPEGXLImageDecoder::decode(Query query, size_t frameIndex, bool allDataReceived) 197 { 198 ensureDecoderInitialized(); 199 200 if (failed()) 201 return; 202 203 if (shouldRewind(query, frameIndex)) { 204 rewind(); 205 // We care about the frameIndex only if the query is Query::DecodedImage. 206 if (query == Query::DecodedImage && frameIndex) { 207 JxlDecoderSkipFrames(m_decoder.get(), frameIndex); 208 m_currentFrame = frameIndex; 209 } 210 } 211 212 m_lastQuery = query; 88 213 89 214 m_data->data(); … … 94 219 } 95 220 96 JxlDecoderStatus status = processInput( onlySize);221 JxlDecoderStatus status = processInput(query); 97 222 // We set the status as failed if the decoder reports an error or requires more data while all data has been received. 98 223 if (status == JXL_DEC_ERROR || (allDataReceived && status == JXL_DEC_NEED_MORE_INPUT)) { … … 101 226 } 102 227 103 // We release the decoder when we finish the decoding. 104 if (status == JXL_DEC_SUCCESS) { 105 clear(); 106 return; 107 } 108 109 // Otherwise we get the decoder ready for subsequent data. 228 if (query == Query::DecodedImage && status == JXL_DEC_FULL_IMAGE && m_isLastFrameHeaderReceived && m_currentFrame == m_frameCount) { 229 // We free the decoder when the last frame is decoded. 230 m_decoder.reset(); 231 return; 232 } 233 110 234 size_t remainingDataSize = JxlDecoderReleaseInput(m_decoder.get()); 111 235 m_readOffset = dataSize - remainingDataSize; 112 236 } 113 237 114 JxlDecoderStatus JPEGXLImageDecoder::processInput( bool onlySize)238 JxlDecoderStatus JPEGXLImageDecoder::processInput(Query query) 115 239 { 116 240 while (true) { 117 241 auto status = JxlDecoderProcessInput(m_decoder.get()); 242 243 // Return JXL_DEC_ERROR when we've reached EOF without receiving a frame marked as "is_last". 244 if (status == JXL_DEC_SUCCESS && !m_isLastFrameHeaderReceived) 245 return JXL_DEC_ERROR; 118 246 119 247 // JXL_DEC_ERROR and JXL_DEC_SUCCESS are terminal states. We also exit from the loop if more data is needed. … … 122 250 123 251 if (status == JXL_DEC_BASIC_INFO) { 124 JxlBasicInfo basicInfo; 125 if (JxlDecoderGetBasicInfo(m_decoder.get(), &basicInfo) != JXL_DEC_SUCCESS) 126 return JXL_DEC_ERROR; 127 128 setSize(IntSize(basicInfo.xsize, basicInfo.ysize)); 129 if (onlySize) 252 if (!m_basicInfo) { 253 JxlBasicInfo basicInfo; 254 if (JxlDecoderGetBasicInfo(m_decoder.get(), &basicInfo) != JXL_DEC_SUCCESS) 255 return JXL_DEC_ERROR; 256 257 m_basicInfo = basicInfo; 258 } 259 260 if (query == Query::Size) { 261 // setSize() must be called only if the query is Query::Size, 262 // otherwise this would roll back the encoded data status from completed. 263 setSize(IntSize(m_basicInfo->xsize, m_basicInfo->ysize)); 130 264 return status; 265 } 266 131 267 continue; 132 268 } … … 134 270 if (status == JXL_DEC_FRAME) { 135 271 JxlPixelFormat format { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; 272 JxlFrameHeader frameHeader; 273 if (JxlDecoderGetFrameHeader(m_decoder.get(), &frameHeader) != JXL_DEC_SUCCESS) 274 return JXL_DEC_ERROR; 275 276 m_frameCount = std::max(m_frameCount, m_currentFrame + 1); 277 278 if (frameHeader.is_last) 279 m_isLastFrameHeaderReceived = true; 280 281 if (query != Query::DecodedImage) { 282 if (JxlDecoderSetImageOutCallback(m_decoder.get(), &format, [](void*, size_t, size_t, size_t, const void*) { }, nullptr) != JXL_DEC_SUCCESS) 283 return JXL_DEC_ERROR; 284 285 continue; 286 } 287 288 if (m_currentFrame >= m_frameBufferCache.size()) 289 m_frameBufferCache.resize(m_frameCount + 1); 290 291 auto& buffer = m_frameBufferCache[m_currentFrame]; 292 if (buffer.isInvalid() && buffer.initialize(size(), m_premultiplyAlpha)) { 293 buffer.setDecodingStatus(DecodingStatus::Partial); 294 buffer.setHasAlpha(false); 295 if (m_basicInfo && m_basicInfo->have_animation) { 296 buffer.setDuration(Seconds((double)frameHeader.duration * m_basicInfo->animation.tps_denominator / m_basicInfo->animation.tps_numerator)); 297 buffer.setDisposalMethod(ScalableImageDecoderFrame::DisposalMethod::DoNotDispose); 298 } 299 } 300 136 301 if (JxlDecoderSetImageOutCallback(m_decoder.get(), &format, imageOutCallback, this) != JXL_DEC_SUCCESS) 137 302 return JXL_DEC_ERROR; 303 138 304 continue; 139 305 } 140 306 141 307 if (status == JXL_DEC_FULL_IMAGE) { 142 if (m_frameBufferCache.isEmpty()) 143 continue; 144 145 auto& buffer = m_frameBufferCache[0]; 146 if (!buffer.isInvalid()) 147 buffer.setDecodingStatus(DecodingStatus::Complete); 148 149 // TODO: To support animated JPEG XL in the future we need to handle subsequent data. 150 return JXL_DEC_SUCCESS; 308 if (m_currentFrame < m_frameBufferCache.size()) { 309 auto& buffer = m_frameBufferCache[m_currentFrame]; 310 if (!buffer.isInvalid()) 311 buffer.setDecodingStatus(DecodingStatus::Complete); 312 } 313 314 m_currentFrame++; 315 if (query == Query::DecodedImage) 316 return JXL_DEC_FULL_IMAGE; 317 318 ASSERT(query == Query::FrameCount); 319 continue; 151 320 } 152 321 } … … 160 329 void JPEGXLImageDecoder::imageOut(size_t x, size_t y, size_t numPixels, const uint8_t* pixels) 161 330 { 162 if (m_frameBufferCache.isEmpty()) 163 return; 164 165 auto& buffer = m_frameBufferCache[0]; 166 if (buffer.isInvalid()) { 167 if (!buffer.initialize(size(), m_premultiplyAlpha)) 168 return; 169 170 buffer.setDecodingStatus(DecodingStatus::Partial); 171 buffer.setHasAlpha(false); 172 } 331 if (m_currentFrame >= m_frameBufferCache.size()) 332 return; 333 334 auto& buffer = m_frameBufferCache[m_currentFrame]; 335 if (buffer.isInvalid()) 336 return; 173 337 174 338 uint32_t* row = buffer.backingStore()->pixelAt(x, y); -
trunk/Source/WebCore/platform/image-decoders/jpegxl/JPEGXLImageDecoder.h
r286011 r286628 46 46 // ScalableImageDecoder 47 47 String filenameExtension() const override { return "jxl"_s; } 48 size_t frameCount() const override; 49 RepetitionCount repetitionCount() const override; 48 50 ScalableImageDecoderFrame* frameBufferAtIndex(size_t index) override; 51 void clearFrameBufferCache(size_t clearBeforeFrame) override; 49 52 50 53 bool setFailed() override; 51 54 52 55 private: 56 enum class Query { 57 // This query is used for tryDecodeSize(). 58 Size, 59 // We define a query for frame count because JPEG XL doesn't have frame count information in its code stream 60 // so we need to scan the code stream to get the frame count for animated JPEG XL. 61 // JPEG XL container can have frame count metadata but currently libjxl doesn't support it. 62 FrameCount, 63 // Query to decode a single frame. 64 DecodedImage, 65 }; 66 53 67 JPEGXLImageDecoder(AlphaOption, GammaAndColorProfileOption); 54 void tryDecodeSize(bool allDataReceived) override { decode(true, allDataReceived); }68 void tryDecodeSize(bool allDataReceived) override; 55 69 56 void decode(bool onlySize, bool allDataReceived); 70 bool hasAlpha() const; 71 bool hasAnimation() const; 57 72 58 void clear(); 73 void ensureDecoderInitialized(); 74 bool shouldRewind(Query , size_t frameIndex) const; 75 void rewind(); 76 void updateFrameCount(); 59 77 60 JxlDecoderStatus processInput(bool onlySize);61 62 static void imageOutCallback(void* that, size_t x, size_t y, size_t numPixels, const void* pixels);78 void decode(Query, size_t frameIndex, bool allDataReceived); 79 JxlDecoderStatus processInput(Query); 80 static void imageOutCallback(void*, size_t x, size_t y, size_t numPixels, const void* pixels); 63 81 void imageOut(size_t x, size_t y, size_t numPixels, const uint8_t* pixels); 64 82 65 83 JxlDecoderPtr m_decoder; 66 size_t m_readOffset = 0; 84 size_t m_readOffset { 0 }; 85 std::optional<JxlBasicInfo> m_basicInfo; 86 87 Query m_lastQuery { Query::Size }; 88 size_t m_frameCount { 1 }; 89 size_t m_currentFrame { 0 }; 90 91 bool m_isLastFrameHeaderReceived { false }; // If this is true, we know we don't need to update m_frameCount. 67 92 }; 68 93
Note:
See TracChangeset
for help on using the changeset viewer.