1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ImageLogging.h" // Must appear first
8 #include "gfxPlatform.h"
9 #include "mozilla/TelemetryHistogramEnums.h"
10 #include "nsWebPDecoder.h"
12 #include "RasterImage.h"
13 #include "SurfacePipeFactory.h"
15 using namespace mozilla::gfx
;
20 static LazyLogModule
sWebPLog("WebPDecoder");
22 nsWebPDecoder::nsWebPDecoder(RasterImage
* aImage
)
25 mBlend(BlendMethod::OVER
),
26 mDisposal(DisposalMethod::KEEP
),
27 mTimeout(FrameTimeout::Forever()),
28 mFormat(SurfaceFormat::OS_RGBX
),
33 mIteratorComplete(false),
35 mGotColorProfile(false) {
36 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
37 ("[this=%p] nsWebPDecoder::nsWebPDecoder", this));
40 nsWebPDecoder::~nsWebPDecoder() {
41 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
42 ("[this=%p] nsWebPDecoder::~nsWebPDecoder", this));
44 WebPIDelete(mDecoder
);
45 WebPFreeDecBuffer(&mBuffer
);
49 LexerResult
nsWebPDecoder::ReadData() {
51 MOZ_ASSERT(mLength
> 0);
53 WebPDemuxer
* demuxer
= nullptr;
54 bool complete
= mIteratorComplete
;
59 fragment
.bytes
= mData
;
60 fragment
.size
= mLength
;
62 demuxer
= WebPDemuxPartial(&fragment
, &state
);
63 if (state
== WEBP_DEMUX_PARSE_ERROR
) {
65 sWebPLog
, LogLevel::Error
,
66 ("[this=%p] nsWebPDecoder::ReadData -- demux parse error\n", this));
67 WebPDemuxDelete(demuxer
);
68 return LexerResult(TerminalState::FAILURE
);
71 if (state
== WEBP_DEMUX_PARSING_HEADER
) {
72 WebPDemuxDelete(demuxer
);
73 return LexerResult(Yield::NEED_MORE_DATA
);
77 MOZ_LOG(sWebPLog
, LogLevel::Error
,
78 ("[this=%p] nsWebPDecoder::ReadData -- no demuxer\n", this));
79 return LexerResult(TerminalState::FAILURE
);
82 complete
= complete
|| state
== WEBP_DEMUX_DONE
;
85 LexerResult
rv(TerminalState::FAILURE
);
87 rv
= ReadHeader(demuxer
, complete
);
89 rv
= ReadPayload(demuxer
, complete
);
92 WebPDemuxDelete(demuxer
);
96 LexerResult
nsWebPDecoder::DoDecode(SourceBufferIterator
& aIterator
,
97 IResumable
* aOnResume
) {
99 SourceBufferIterator::State state
= SourceBufferIterator::COMPLETE
;
100 if (!mIteratorComplete
) {
101 state
= aIterator
.AdvanceOrScheduleResume(SIZE_MAX
, aOnResume
);
103 // We need to remember since we can't advance a complete iterator.
104 mIteratorComplete
= state
== SourceBufferIterator::COMPLETE
;
107 if (state
== SourceBufferIterator::WAITING
) {
108 return LexerResult(Yield::NEED_MORE_DATA
);
111 LexerResult rv
= UpdateBuffer(aIterator
, state
);
112 if (rv
.is
<Yield
>() && rv
.as
<Yield
>() == Yield::NEED_MORE_DATA
) {
113 // We need to check the iterator to see if more is available before
114 // giving up unless we are already complete.
115 if (mIteratorComplete
) {
116 MOZ_LOG(sWebPLog
, LogLevel::Error
,
117 ("[this=%p] nsWebPDecoder::DoDecode -- read all data, "
120 return LexerResult(TerminalState::FAILURE
);
129 LexerResult
nsWebPDecoder::UpdateBuffer(SourceBufferIterator
& aIterator
,
130 SourceBufferIterator::State aState
) {
131 MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
134 case SourceBufferIterator::READY
:
135 if (!aIterator
.IsContiguous()) {
136 // We need to buffer. This should be rare, but expensive.
140 // For as long as we hold onto an iterator, we know the data pointers
141 // to the chunks cannot change underneath us, so save the pointer to
143 MOZ_ASSERT(mLength
== 0);
144 mData
= reinterpret_cast<const uint8_t*>(aIterator
.Data());
146 mLength
+= aIterator
.Length();
148 case SourceBufferIterator::COMPLETE
:
150 // We must have hit an error, such as an OOM, when buffering the
151 // first set of encoded data.
153 sWebPLog
, LogLevel::Error
,
154 ("[this=%p] nsWebPDecoder::DoDecode -- complete no data\n", this));
155 return LexerResult(TerminalState::FAILURE
);
159 MOZ_LOG(sWebPLog
, LogLevel::Error
,
160 ("[this=%p] nsWebPDecoder::DoDecode -- bad state\n", this));
161 return LexerResult(TerminalState::FAILURE
);
164 // We need to buffer. If we have no data buffered, we need to get everything
165 // from the first chunk of the source buffer before appending the new data.
166 if (mBufferedData
.empty()) {
168 MOZ_ASSERT(mLength
> 0);
170 if (!mBufferedData
.append(mData
, mLength
)) {
171 MOZ_LOG(sWebPLog
, LogLevel::Error
,
172 ("[this=%p] nsWebPDecoder::DoDecode -- oom, initialize %zu\n",
174 return LexerResult(TerminalState::FAILURE
);
177 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
178 ("[this=%p] nsWebPDecoder::DoDecode -- buffered %zu bytes\n", this,
182 // Append the incremental data from the iterator.
183 if (!mBufferedData
.append(aIterator
.Data(), aIterator
.Length())) {
184 MOZ_LOG(sWebPLog
, LogLevel::Error
,
185 ("[this=%p] nsWebPDecoder::DoDecode -- oom, append %zu on %zu\n",
186 this, aIterator
.Length(), mBufferedData
.length()));
187 return LexerResult(TerminalState::FAILURE
);
190 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
191 ("[this=%p] nsWebPDecoder::DoDecode -- buffered %zu -> %zu bytes\n",
192 this, aIterator
.Length(), mBufferedData
.length()));
193 mData
= mBufferedData
.begin();
194 mLength
= mBufferedData
.length();
198 nsresult
nsWebPDecoder::CreateFrame(const OrientedIntRect
& aFrameRect
) {
199 MOZ_ASSERT(HasSize());
200 MOZ_ASSERT(!mDecoder
);
203 sWebPLog
, LogLevel::Debug
,
204 ("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, (%d, %d) %d x %d\n",
205 this, mCurrentFrame
, aFrameRect
.x
, aFrameRect
.y
, aFrameRect
.width
,
208 if (aFrameRect
.width
<= 0 || aFrameRect
.height
<= 0) {
209 MOZ_LOG(sWebPLog
, LogLevel::Error
,
210 ("[this=%p] nsWebPDecoder::CreateFrame -- bad frame rect\n", this));
211 return NS_ERROR_FAILURE
;
214 // If this is our first frame in an animation and it doesn't cover the
215 // full frame, then we are transparent even if there is no alpha
216 if (mCurrentFrame
== 0 && !aFrameRect
.IsEqualEdges(FullFrame())) {
217 MOZ_ASSERT(HasAnimation());
218 mFormat
= SurfaceFormat::OS_RGBA
;
219 PostHasTransparency();
222 if (!WebPInitDecBuffer(&mBuffer
)) {
224 sWebPLog
, LogLevel::Error
,
225 ("[this=%p] nsWebPDecoder::CreateFrame -- WebPInitDecBuffer failed\n",
227 return NS_ERROR_FAILURE
;
230 switch (SurfaceFormat::OS_RGBA
) {
231 case SurfaceFormat::B8G8R8A8
:
232 mBuffer
.colorspace
= MODE_BGRA
;
234 case SurfaceFormat::A8R8G8B8
:
235 mBuffer
.colorspace
= MODE_ARGB
;
237 case SurfaceFormat::R8G8B8A8
:
238 mBuffer
.colorspace
= MODE_RGBA
;
241 MOZ_ASSERT_UNREACHABLE("Unknown OS_RGBA");
242 return NS_ERROR_FAILURE
;
245 mDecoder
= WebPINewDecoder(&mBuffer
);
247 MOZ_LOG(sWebPLog
, LogLevel::Error
,
248 ("[this=%p] nsWebPDecoder::CreateFrame -- create decoder error\n",
250 return NS_ERROR_FAILURE
;
253 // WebP doesn't guarantee that the alpha generated matches the hint in the
254 // header, so we always need to claim the input is BGRA. If the output is
255 // BGRX, swizzling will mask off the alpha channel.
256 SurfaceFormat inFormat
= SurfaceFormat::OS_RGBA
;
258 SurfacePipeFlags pipeFlags
= SurfacePipeFlags();
259 if (mFormat
== SurfaceFormat::OS_RGBA
&&
260 !(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA
)) {
261 pipeFlags
|= SurfacePipeFlags::PREMULTIPLY_ALPHA
;
264 Maybe
<AnimationParams
> animParams
;
265 if (!IsFirstFrameDecode()) {
266 animParams
.emplace(aFrameRect
.ToUnknownRect(), mTimeout
, mCurrentFrame
,
270 Maybe
<SurfacePipe
> pipe
= SurfacePipeFactory::CreateSurfacePipe(
271 this, Size(), OutputSize(), aFrameRect
, inFormat
, mFormat
, animParams
,
272 mTransform
, pipeFlags
);
274 MOZ_LOG(sWebPLog
, LogLevel::Error
,
275 ("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this));
276 return NS_ERROR_FAILURE
;
279 mFrameRect
= aFrameRect
;
280 mPipe
= std::move(*pipe
);
284 void nsWebPDecoder::EndFrame() {
285 MOZ_ASSERT(HasSize());
286 MOZ_ASSERT(mDecoder
);
288 auto opacity
= mFormat
== SurfaceFormat::OS_RGBA
? Opacity::SOME_TRANSPARENCY
289 : Opacity::FULLY_OPAQUE
;
291 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
292 ("[this=%p] nsWebPDecoder::EndFrame -- frame %u, opacity %d, "
293 "disposal %d, timeout %d, blend %d\n",
294 this, mCurrentFrame
, (int)opacity
, (int)mDisposal
,
295 mTimeout
.AsEncodedValueDeprecated(), (int)mBlend
));
297 PostFrameStop(opacity
);
298 WebPIDelete(mDecoder
);
299 WebPFreeDecBuffer(&mBuffer
);
305 void nsWebPDecoder::ApplyColorProfile(const char* aProfile
, size_t aLength
) {
306 MOZ_ASSERT(!mGotColorProfile
);
307 mGotColorProfile
= true;
309 if (mCMSMode
== CMSMode::Off
|| !GetCMSOutputProfile() ||
310 (mCMSMode
== CMSMode::TaggedOnly
&& !aProfile
)) {
315 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
316 ("[this=%p] nsWebPDecoder::ApplyColorProfile -- not tagged, use "
319 mTransform
= GetCMSsRGBTransform(SurfaceFormat::OS_RGBA
);
323 mInProfile
= qcms_profile_from_memory(aProfile
, aLength
);
326 sWebPLog
, LogLevel::Error
,
327 ("[this=%p] nsWebPDecoder::ApplyColorProfile -- bad color profile\n",
332 uint32_t profileSpace
= qcms_profile_get_color_space(mInProfile
);
333 if (profileSpace
!= icSigRgbData
) {
334 // WebP doesn't produce grayscale data, this must be corrupt.
335 MOZ_LOG(sWebPLog
, LogLevel::Error
,
336 ("[this=%p] nsWebPDecoder::ApplyColorProfile -- ignoring non-rgb "
342 // Calculate rendering intent.
343 int intent
= gfxPlatform::GetRenderingIntent();
345 intent
= qcms_profile_get_rendering_intent(mInProfile
);
348 // Create the color management transform.
349 qcms_data_type type
= gfxPlatform::GetCMSOSRGBAType();
350 mTransform
= qcms_transform_create(mInProfile
, type
, GetCMSOutputProfile(),
351 type
, (qcms_intent
)intent
);
352 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
353 ("[this=%p] nsWebPDecoder::ApplyColorProfile -- use tagged "
358 LexerResult
nsWebPDecoder::ReadHeader(WebPDemuxer
* aDemuxer
, bool aIsComplete
) {
359 MOZ_ASSERT(aDemuxer
);
362 sWebPLog
, LogLevel::Debug
,
363 ("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, mLength
));
365 uint32_t flags
= WebPDemuxGetI(aDemuxer
, WEBP_FF_FORMAT_FLAGS
);
367 if (!IsMetadataDecode() && !mGotColorProfile
) {
368 if (flags
& WebPFeatureFlags::ICCP_FLAG
) {
369 WebPChunkIterator iter
;
370 if (WebPDemuxGetChunk(aDemuxer
, "ICCP", 1, &iter
)) {
371 ApplyColorProfile(reinterpret_cast<const char*>(iter
.chunk
.bytes
),
373 WebPDemuxReleaseChunkIterator(&iter
);
377 return LexerResult(Yield::NEED_MORE_DATA
);
380 MOZ_LOG(sWebPLog
, LogLevel::Warning
,
381 ("[this=%p] nsWebPDecoder::ReadHeader header specified ICCP "
382 "but no ICCP chunk found, ignoring\n",
385 ApplyColorProfile(nullptr, 0);
388 ApplyColorProfile(nullptr, 0);
392 if (flags
& WebPFeatureFlags::ANIMATION_FLAG
) {
393 // The demuxer only knows how many frames it will have once it has the
395 if (WantsFrameCount() && !aIsComplete
) {
396 return LexerResult(Yield::NEED_MORE_DATA
);
399 // A metadata decode expects to get the correct first frame timeout which
400 // sadly is not provided by the normal WebP header parsing.
402 if (!WebPDemuxGetFrame(aDemuxer
, 1, &iter
)) {
403 return aIsComplete
? LexerResult(TerminalState::FAILURE
)
404 : LexerResult(Yield::NEED_MORE_DATA
);
407 PostIsAnimated(FrameTimeout::FromRawMilliseconds(iter
.duration
));
408 WebPDemuxReleaseIterator(&iter
);
410 uint32_t loopCount
= WebPDemuxGetI(aDemuxer
, WEBP_FF_LOOP_COUNT
);
411 if (loopCount
> INT32_MAX
) {
412 loopCount
= INT32_MAX
;
415 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
416 ("[this=%p] nsWebPDecoder::ReadHeader -- loop count %u\n", this,
418 PostLoopCount(static_cast<int32_t>(loopCount
) - 1);
420 // Single frames don't need a demuxer to be created.
421 mNeedDemuxer
= false;
424 uint32_t width
= WebPDemuxGetI(aDemuxer
, WEBP_FF_CANVAS_WIDTH
);
425 uint32_t height
= WebPDemuxGetI(aDemuxer
, WEBP_FF_CANVAS_HEIGHT
);
426 if (width
> INT32_MAX
|| height
> INT32_MAX
) {
427 return LexerResult(TerminalState::FAILURE
);
430 PostSize(width
, height
);
432 if (WantsFrameCount()) {
433 uint32_t frameCount
= WebPDemuxGetI(aDemuxer
, WEBP_FF_FRAME_COUNT
);
434 PostFrameCount(frameCount
);
437 bool alpha
= flags
& WebPFeatureFlags::ALPHA_FLAG
;
439 mFormat
= SurfaceFormat::OS_RGBA
;
440 PostHasTransparency();
443 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
444 ("[this=%p] nsWebPDecoder::ReadHeader -- %u x %u, alpha %d, "
445 "animation %d, metadata decode %d, first frame decode %d\n",
446 this, width
, height
, alpha
, HasAnimation(), IsMetadataDecode(),
447 IsFirstFrameDecode()));
449 if (IsMetadataDecode()) {
450 return LexerResult(TerminalState::SUCCESS
);
453 return ReadPayload(aDemuxer
, aIsComplete
);
456 LexerResult
nsWebPDecoder::ReadPayload(WebPDemuxer
* aDemuxer
,
458 if (!HasAnimation()) {
459 auto rv
= ReadSingle(mData
, mLength
, FullFrame());
460 if (rv
.is
<TerminalState
>() &&
461 rv
.as
<TerminalState
>() == TerminalState::SUCCESS
) {
466 return ReadMultiple(aDemuxer
, aIsComplete
);
469 LexerResult
nsWebPDecoder::ReadSingle(const uint8_t* aData
, size_t aLength
,
470 const OrientedIntRect
& aFrameRect
) {
471 MOZ_ASSERT(!IsMetadataDecode());
473 MOZ_ASSERT(aLength
> 0);
476 sWebPLog
, LogLevel::Debug
,
477 ("[this=%p] nsWebPDecoder::ReadSingle -- %zu bytes\n", this, aLength
));
479 if (!mDecoder
&& NS_FAILED(CreateFrame(aFrameRect
))) {
480 return LexerResult(TerminalState::FAILURE
);
485 VP8StatusCode status
= WebPIUpdate(mDecoder
, aData
, aLength
);
490 case VP8_STATUS_SUSPENDED
:
494 MOZ_LOG(sWebPLog
, LogLevel::Error
,
495 ("[this=%p] nsWebPDecoder::ReadSingle -- append error %d\n",
497 return LexerResult(TerminalState::FAILURE
);
505 WebPIDecGetRGB(mDecoder
, &lastRow
, &width
, &height
, &stride
);
508 sWebPLog
, LogLevel::Debug
,
509 ("[this=%p] nsWebPDecoder::ReadSingle -- complete %d, read %d rows, "
510 "has %d rows available\n",
511 this, complete
, mLastRow
, lastRow
));
513 if (!rowStart
|| lastRow
== -1 || lastRow
== mLastRow
) {
514 return LexerResult(Yield::NEED_MORE_DATA
);
517 if (width
!= mFrameRect
.width
|| height
!= mFrameRect
.height
||
518 stride
< mFrameRect
.width
* 4 || lastRow
> mFrameRect
.height
) {
519 MOZ_LOG(sWebPLog
, LogLevel::Error
,
520 ("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, "
522 this, width
, height
, stride
));
523 return LexerResult(TerminalState::FAILURE
);
526 for (int row
= mLastRow
; row
< lastRow
; row
++) {
527 uint32_t* src
= reinterpret_cast<uint32_t*>(rowStart
+ row
* stride
);
528 WriteState result
= mPipe
.WriteBuffer(src
);
530 Maybe
<SurfaceInvalidRect
> invalidRect
= mPipe
.TakeInvalidRect();
532 PostInvalidation(invalidRect
->mInputSpaceRect
,
533 Some(invalidRect
->mOutputSpaceRect
));
536 if (result
== WriteState::FAILURE
) {
537 MOZ_LOG(sWebPLog
, LogLevel::Error
,
538 ("[this=%p] nsWebPDecoder::ReadSingle -- write pixels error\n",
540 return LexerResult(TerminalState::FAILURE
);
543 if (result
== WriteState::FINISHED
) {
544 MOZ_ASSERT(row
== lastRow
- 1, "There was more data to read?");
554 return LexerResult(Yield::NEED_MORE_DATA
);
558 return LexerResult(TerminalState::SUCCESS
);
561 LexerResult
nsWebPDecoder::ReadMultiple(WebPDemuxer
* aDemuxer
,
563 MOZ_ASSERT(!IsMetadataDecode());
564 MOZ_ASSERT(aDemuxer
);
566 MOZ_LOG(sWebPLog
, LogLevel::Debug
,
567 ("[this=%p] nsWebPDecoder::ReadMultiple\n", this));
569 bool complete
= aIsComplete
;
571 auto rv
= LexerResult(Yield::NEED_MORE_DATA
);
572 if (WebPDemuxGetFrame(aDemuxer
, mCurrentFrame
+ 1, &iter
)) {
573 switch (iter
.blend_method
) {
575 mBlend
= BlendMethod::OVER
;
577 case WEBP_MUX_NO_BLEND
:
578 mBlend
= BlendMethod::SOURCE
;
581 MOZ_ASSERT_UNREACHABLE("Unhandled blend method");
585 switch (iter
.dispose_method
) {
586 case WEBP_MUX_DISPOSE_NONE
:
587 mDisposal
= DisposalMethod::KEEP
;
589 case WEBP_MUX_DISPOSE_BACKGROUND
:
590 mDisposal
= DisposalMethod::CLEAR
;
593 MOZ_ASSERT_UNREACHABLE("Unhandled dispose method");
597 mFormat
= iter
.has_alpha
|| mCurrentFrame
> 0 ? SurfaceFormat::OS_RGBA
598 : SurfaceFormat::OS_RGBX
;
599 mTimeout
= FrameTimeout::FromRawMilliseconds(iter
.duration
);
600 OrientedIntRect
frameRect(iter
.x_offset
, iter
.y_offset
, iter
.width
,
603 rv
= ReadSingle(iter
.fragment
.bytes
, iter
.fragment
.size
, frameRect
);
604 complete
= complete
&& !WebPDemuxNextFrame(&iter
);
605 WebPDemuxReleaseIterator(&iter
);
608 if (rv
.is
<TerminalState
>() &&
609 rv
.as
<TerminalState
>() == TerminalState::SUCCESS
) {
610 // If we extracted one frame, and it is not the last, we need to yield to
611 // the lexer to allow the upper layers to acknowledge the frame.
612 if (!complete
&& !IsFirstFrameDecode()) {
613 rv
= LexerResult(Yield::OUTPUT_AVAILABLE
);
622 Maybe
<glean::impl::MemoryDistributionMetric
> nsWebPDecoder::SpeedMetric()
624 return Some(glean::image_decode::speed_webp
);
628 } // namespace mozilla