nsWebPDecoder.cpp (20020B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 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/. */ 6 7 #include "ImageLogging.h" // Must appear first 8 #include "gfxPlatform.h" 9 #include "mozilla/TelemetryHistogramEnums.h" 10 #include "nsWebPDecoder.h" 11 12 #include "RasterImage.h" 13 #include "SurfacePipeFactory.h" 14 15 using namespace mozilla::gfx; 16 17 namespace mozilla { 18 namespace image { 19 20 static LazyLogModule sWebPLog("WebPDecoder"); 21 22 nsWebPDecoder::nsWebPDecoder(RasterImage* aImage) 23 : Decoder(aImage), 24 mDecoder(nullptr), 25 mBlend(BlendMethod::OVER), 26 mDisposal(DisposalMethod::KEEP), 27 mTimeout(FrameTimeout::Forever()), 28 mFormat(SurfaceFormat::OS_RGBX), 29 mLastRow(0), 30 mCurrentFrame(0), 31 mData(nullptr), 32 mLength(0), 33 mIteratorComplete(false), 34 mNeedDemuxer(true), 35 mGotColorProfile(false) { 36 MOZ_LOG(sWebPLog, LogLevel::Debug, 37 ("[this=%p] nsWebPDecoder::nsWebPDecoder", this)); 38 } 39 40 nsWebPDecoder::~nsWebPDecoder() { 41 MOZ_LOG(sWebPLog, LogLevel::Debug, 42 ("[this=%p] nsWebPDecoder::~nsWebPDecoder", this)); 43 if (mDecoder) { 44 WebPIDelete(mDecoder); 45 WebPFreeDecBuffer(&mBuffer); 46 } 47 } 48 49 LexerResult nsWebPDecoder::ReadData() { 50 MOZ_ASSERT(mData); 51 MOZ_ASSERT(mLength > 0); 52 53 WebPDemuxer* demuxer = nullptr; 54 bool complete = mIteratorComplete; 55 56 if (mNeedDemuxer) { 57 WebPDemuxState state; 58 WebPData fragment; 59 fragment.bytes = mData; 60 fragment.size = mLength; 61 62 demuxer = WebPDemuxPartial(&fragment, &state); 63 if (state == WEBP_DEMUX_PARSE_ERROR) { 64 MOZ_LOG( 65 sWebPLog, LogLevel::Error, 66 ("[this=%p] nsWebPDecoder::ReadData -- demux parse error\n", this)); 67 WebPDemuxDelete(demuxer); 68 return LexerResult(TerminalState::FAILURE); 69 } 70 71 if (state == WEBP_DEMUX_PARSING_HEADER) { 72 WebPDemuxDelete(demuxer); 73 return LexerResult(Yield::NEED_MORE_DATA); 74 } 75 76 if (!demuxer) { 77 MOZ_LOG(sWebPLog, LogLevel::Error, 78 ("[this=%p] nsWebPDecoder::ReadData -- no demuxer\n", this)); 79 return LexerResult(TerminalState::FAILURE); 80 } 81 82 complete = complete || state == WEBP_DEMUX_DONE; 83 } 84 85 LexerResult rv(TerminalState::FAILURE); 86 if (!HasSize()) { 87 rv = ReadHeader(demuxer, complete); 88 } else { 89 rv = ReadPayload(demuxer, complete); 90 } 91 92 WebPDemuxDelete(demuxer); 93 return rv; 94 } 95 96 LexerResult nsWebPDecoder::DoDecode(SourceBufferIterator& aIterator, 97 IResumable* aOnResume) { 98 while (true) { 99 SourceBufferIterator::State state = SourceBufferIterator::COMPLETE; 100 if (!mIteratorComplete) { 101 state = aIterator.AdvanceOrScheduleResume(SIZE_MAX, aOnResume); 102 103 // We need to remember since we can't advance a complete iterator. 104 mIteratorComplete = state == SourceBufferIterator::COMPLETE; 105 } 106 107 if (state == SourceBufferIterator::WAITING) { 108 return LexerResult(Yield::NEED_MORE_DATA); 109 } 110 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, " 118 "but needs more\n", 119 this)); 120 return LexerResult(TerminalState::FAILURE); 121 } 122 continue; 123 } 124 125 return rv; 126 } 127 } 128 129 LexerResult nsWebPDecoder::UpdateBuffer(SourceBufferIterator& aIterator, 130 SourceBufferIterator::State aState) { 131 MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!"); 132 133 switch (aState) { 134 case SourceBufferIterator::READY: 135 if (!aIterator.IsContiguous()) { 136 // We need to buffer. This should be rare, but expensive. 137 break; 138 } 139 if (!mData) { 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 142 // the first block. 143 MOZ_ASSERT(mLength == 0); 144 mData = reinterpret_cast<const uint8_t*>(aIterator.Data()); 145 } 146 mLength += aIterator.Length(); 147 return ReadData(); 148 case SourceBufferIterator::COMPLETE: 149 if (!mData) { 150 // We must have hit an error, such as an OOM, when buffering the 151 // first set of encoded data. 152 MOZ_LOG( 153 sWebPLog, LogLevel::Error, 154 ("[this=%p] nsWebPDecoder::DoDecode -- complete no data\n", this)); 155 return LexerResult(TerminalState::FAILURE); 156 } 157 return ReadData(); 158 default: 159 MOZ_LOG(sWebPLog, LogLevel::Error, 160 ("[this=%p] nsWebPDecoder::DoDecode -- bad state\n", this)); 161 return LexerResult(TerminalState::FAILURE); 162 } 163 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()) { 167 MOZ_ASSERT(mData); 168 MOZ_ASSERT(mLength > 0); 169 170 if (!mBufferedData.append(mData, mLength)) { 171 MOZ_LOG(sWebPLog, LogLevel::Error, 172 ("[this=%p] nsWebPDecoder::DoDecode -- oom, initialize %zu\n", 173 this, mLength)); 174 return LexerResult(TerminalState::FAILURE); 175 } 176 177 MOZ_LOG(sWebPLog, LogLevel::Debug, 178 ("[this=%p] nsWebPDecoder::DoDecode -- buffered %zu bytes\n", this, 179 mLength)); 180 } 181 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); 188 } 189 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(); 195 return ReadData(); 196 } 197 198 nsresult nsWebPDecoder::CreateFrame(const OrientedIntRect& aFrameRect) { 199 MOZ_ASSERT(HasSize()); 200 MOZ_ASSERT(!mDecoder); 201 202 MOZ_LOG( 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, 206 aFrameRect.height)); 207 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; 212 } 213 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(); 220 } 221 222 if (!WebPInitDecBuffer(&mBuffer)) { 223 MOZ_LOG( 224 sWebPLog, LogLevel::Error, 225 ("[this=%p] nsWebPDecoder::CreateFrame -- WebPInitDecBuffer failed\n", 226 this)); 227 return NS_ERROR_FAILURE; 228 } 229 230 switch (SurfaceFormat::OS_RGBA) { 231 case SurfaceFormat::B8G8R8A8: 232 mBuffer.colorspace = MODE_BGRA; 233 break; 234 case SurfaceFormat::A8R8G8B8: 235 mBuffer.colorspace = MODE_ARGB; 236 break; 237 case SurfaceFormat::R8G8B8A8: 238 mBuffer.colorspace = MODE_RGBA; 239 break; 240 default: 241 MOZ_ASSERT_UNREACHABLE("Unknown OS_RGBA"); 242 return NS_ERROR_FAILURE; 243 } 244 245 mDecoder = WebPINewDecoder(&mBuffer); 246 if (!mDecoder) { 247 MOZ_LOG(sWebPLog, LogLevel::Error, 248 ("[this=%p] nsWebPDecoder::CreateFrame -- create decoder error\n", 249 this)); 250 return NS_ERROR_FAILURE; 251 } 252 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; 257 258 SurfacePipeFlags pipeFlags = SurfacePipeFlags(); 259 if (mFormat == SurfaceFormat::OS_RGBA && 260 !(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA)) { 261 pipeFlags |= SurfacePipeFlags::PREMULTIPLY_ALPHA; 262 } 263 264 Maybe<AnimationParams> animParams; 265 if (!IsFirstFrameDecode()) { 266 animParams.emplace(aFrameRect.ToUnknownRect(), mTimeout, mCurrentFrame, 267 mBlend, mDisposal); 268 } 269 270 Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe( 271 this, Size(), OutputSize(), aFrameRect, inFormat, mFormat, animParams, 272 mTransform, pipeFlags); 273 if (!pipe) { 274 MOZ_LOG(sWebPLog, LogLevel::Error, 275 ("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this)); 276 return NS_ERROR_FAILURE; 277 } 278 279 mFrameRect = aFrameRect; 280 mPipe = std::move(*pipe); 281 return NS_OK; 282 } 283 284 void nsWebPDecoder::EndFrame() { 285 MOZ_ASSERT(HasSize()); 286 MOZ_ASSERT(mDecoder); 287 288 auto opacity = mFormat == SurfaceFormat::OS_RGBA ? Opacity::SOME_TRANSPARENCY 289 : Opacity::FULLY_OPAQUE; 290 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)); 296 297 PostFrameStop(opacity); 298 WebPIDelete(mDecoder); 299 WebPFreeDecBuffer(&mBuffer); 300 mDecoder = nullptr; 301 mLastRow = 0; 302 ++mCurrentFrame; 303 } 304 305 void nsWebPDecoder::ApplyColorProfile(const char* aProfile, size_t aLength) { 306 MOZ_ASSERT(!mGotColorProfile); 307 mGotColorProfile = true; 308 309 if (mCMSMode == CMSMode::Off || !GetCMSOutputProfile() || 310 (mCMSMode == CMSMode::TaggedOnly && !aProfile)) { 311 return; 312 } 313 314 if (!aProfile) { 315 MOZ_LOG(sWebPLog, LogLevel::Debug, 316 ("[this=%p] nsWebPDecoder::ApplyColorProfile -- not tagged, use " 317 "sRGB transform\n", 318 this)); 319 mTransform = GetCMSsRGBTransform(SurfaceFormat::OS_RGBA); 320 return; 321 } 322 323 mInProfile = qcms_profile_from_memory(aProfile, aLength); 324 if (!mInProfile) { 325 MOZ_LOG( 326 sWebPLog, LogLevel::Error, 327 ("[this=%p] nsWebPDecoder::ApplyColorProfile -- bad color profile\n", 328 this)); 329 return; 330 } 331 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 " 337 "color profile\n", 338 this)); 339 return; 340 } 341 342 // Calculate rendering intent. 343 int intent = gfxPlatform::GetRenderingIntent(); 344 if (intent == -1) { 345 intent = qcms_profile_get_rendering_intent(mInProfile); 346 } 347 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 " 354 "transform\n", 355 this)); 356 } 357 358 LexerResult nsWebPDecoder::ReadHeader(WebPDemuxer* aDemuxer, bool aIsComplete) { 359 MOZ_ASSERT(aDemuxer); 360 361 MOZ_LOG( 362 sWebPLog, LogLevel::Debug, 363 ("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, mLength)); 364 365 uint32_t flags = WebPDemuxGetI(aDemuxer, WEBP_FF_FORMAT_FLAGS); 366 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), 372 iter.chunk.size); 373 WebPDemuxReleaseChunkIterator(&iter); 374 375 } else { 376 if (!aIsComplete) { 377 return LexerResult(Yield::NEED_MORE_DATA); 378 } 379 380 MOZ_LOG(sWebPLog, LogLevel::Warning, 381 ("[this=%p] nsWebPDecoder::ReadHeader header specified ICCP " 382 "but no ICCP chunk found, ignoring\n", 383 this)); 384 385 ApplyColorProfile(nullptr, 0); 386 } 387 } else { 388 ApplyColorProfile(nullptr, 0); 389 } 390 } 391 392 if (flags & WebPFeatureFlags::ANIMATION_FLAG) { 393 // The demuxer only knows how many frames it will have once it has the 394 // complete buffer. 395 if (WantsFrameCount() && !aIsComplete) { 396 return LexerResult(Yield::NEED_MORE_DATA); 397 } 398 399 // A metadata decode expects to get the correct first frame timeout which 400 // sadly is not provided by the normal WebP header parsing. 401 WebPIterator iter; 402 if (!WebPDemuxGetFrame(aDemuxer, 1, &iter)) { 403 return aIsComplete ? LexerResult(TerminalState::FAILURE) 404 : LexerResult(Yield::NEED_MORE_DATA); 405 } 406 407 PostIsAnimated(FrameTimeout::FromRawMilliseconds(iter.duration)); 408 WebPDemuxReleaseIterator(&iter); 409 410 uint32_t loopCount = WebPDemuxGetI(aDemuxer, WEBP_FF_LOOP_COUNT); 411 if (loopCount > INT32_MAX) { 412 loopCount = INT32_MAX; 413 } 414 415 MOZ_LOG(sWebPLog, LogLevel::Debug, 416 ("[this=%p] nsWebPDecoder::ReadHeader -- loop count %u\n", this, 417 loopCount)); 418 PostLoopCount(static_cast<int32_t>(loopCount) - 1); 419 } else { 420 // Single frames don't need a demuxer to be created. 421 mNeedDemuxer = false; 422 } 423 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); 428 } 429 430 PostSize(width, height); 431 432 if (WantsFrameCount()) { 433 uint32_t frameCount = WebPDemuxGetI(aDemuxer, WEBP_FF_FRAME_COUNT); 434 PostFrameCount(frameCount); 435 } 436 437 bool alpha = flags & WebPFeatureFlags::ALPHA_FLAG; 438 if (alpha) { 439 mFormat = SurfaceFormat::OS_RGBA; 440 PostHasTransparency(); 441 } 442 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())); 448 449 if (IsMetadataDecode()) { 450 return LexerResult(TerminalState::SUCCESS); 451 } 452 453 return ReadPayload(aDemuxer, aIsComplete); 454 } 455 456 LexerResult nsWebPDecoder::ReadPayload(WebPDemuxer* aDemuxer, 457 bool aIsComplete) { 458 if (!HasAnimation()) { 459 auto rv = ReadSingle(mData, mLength, FullFrame()); 460 if (rv.is<TerminalState>() && 461 rv.as<TerminalState>() == TerminalState::SUCCESS) { 462 PostDecodeDone(); 463 } 464 return rv; 465 } 466 return ReadMultiple(aDemuxer, aIsComplete); 467 } 468 469 LexerResult nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, 470 const OrientedIntRect& aFrameRect) { 471 MOZ_ASSERT(!IsMetadataDecode()); 472 MOZ_ASSERT(aData); 473 MOZ_ASSERT(aLength > 0); 474 475 MOZ_LOG( 476 sWebPLog, LogLevel::Debug, 477 ("[this=%p] nsWebPDecoder::ReadSingle -- %zu bytes\n", this, aLength)); 478 479 if (!mDecoder && NS_FAILED(CreateFrame(aFrameRect))) { 480 return LexerResult(TerminalState::FAILURE); 481 } 482 483 bool complete; 484 do { 485 VP8StatusCode status = WebPIUpdate(mDecoder, aData, aLength); 486 switch (status) { 487 case VP8_STATUS_OK: 488 complete = true; 489 break; 490 case VP8_STATUS_SUSPENDED: 491 complete = false; 492 break; 493 default: 494 MOZ_LOG(sWebPLog, LogLevel::Error, 495 ("[this=%p] nsWebPDecoder::ReadSingle -- append error %d\n", 496 this, status)); 497 return LexerResult(TerminalState::FAILURE); 498 } 499 500 int lastRow = -1; 501 int width = 0; 502 int height = 0; 503 int stride = 0; 504 uint8_t* rowStart = 505 WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride); 506 507 MOZ_LOG( 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)); 512 513 if (!rowStart || lastRow == -1 || lastRow == mLastRow) { 514 return LexerResult(Yield::NEED_MORE_DATA); 515 } 516 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, " 521 "%d)\n", 522 this, width, height, stride)); 523 return LexerResult(TerminalState::FAILURE); 524 } 525 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); 529 530 Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect(); 531 if (invalidRect) { 532 PostInvalidation(invalidRect->mInputSpaceRect, 533 Some(invalidRect->mOutputSpaceRect)); 534 } 535 536 if (result == WriteState::FAILURE) { 537 MOZ_LOG(sWebPLog, LogLevel::Error, 538 ("[this=%p] nsWebPDecoder::ReadSingle -- write pixels error\n", 539 this)); 540 return LexerResult(TerminalState::FAILURE); 541 } 542 543 if (result == WriteState::FINISHED) { 544 MOZ_ASSERT(row == lastRow - 1, "There was more data to read?"); 545 complete = true; 546 break; 547 } 548 } 549 550 mLastRow = lastRow; 551 } while (!complete); 552 553 if (!complete) { 554 return LexerResult(Yield::NEED_MORE_DATA); 555 } 556 557 EndFrame(); 558 return LexerResult(TerminalState::SUCCESS); 559 } 560 561 LexerResult nsWebPDecoder::ReadMultiple(WebPDemuxer* aDemuxer, 562 bool aIsComplete) { 563 MOZ_ASSERT(!IsMetadataDecode()); 564 MOZ_ASSERT(aDemuxer); 565 566 MOZ_LOG(sWebPLog, LogLevel::Debug, 567 ("[this=%p] nsWebPDecoder::ReadMultiple\n", this)); 568 569 bool complete = aIsComplete; 570 WebPIterator iter; 571 auto rv = LexerResult(Yield::NEED_MORE_DATA); 572 if (WebPDemuxGetFrame(aDemuxer, mCurrentFrame + 1, &iter)) { 573 switch (iter.blend_method) { 574 case WEBP_MUX_BLEND: 575 mBlend = BlendMethod::OVER; 576 break; 577 case WEBP_MUX_NO_BLEND: 578 mBlend = BlendMethod::SOURCE; 579 break; 580 default: 581 MOZ_ASSERT_UNREACHABLE("Unhandled blend method"); 582 break; 583 } 584 585 switch (iter.dispose_method) { 586 case WEBP_MUX_DISPOSE_NONE: 587 mDisposal = DisposalMethod::KEEP; 588 break; 589 case WEBP_MUX_DISPOSE_BACKGROUND: 590 mDisposal = DisposalMethod::CLEAR; 591 break; 592 default: 593 MOZ_ASSERT_UNREACHABLE("Unhandled dispose method"); 594 break; 595 } 596 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, 601 iter.height); 602 603 rv = ReadSingle(iter.fragment.bytes, iter.fragment.size, frameRect); 604 complete = complete && !WebPDemuxNextFrame(&iter); 605 WebPDemuxReleaseIterator(&iter); 606 } 607 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); 614 } else { 615 PostDecodeDone(); 616 } 617 } 618 619 return rv; 620 } 621 622 Maybe<glean::impl::MemoryDistributionMetric> nsWebPDecoder::SpeedMetric() 623 const { 624 return Some(glean::image_decode::speed_webp); 625 } 626 627 } // namespace image 628 } // namespace mozilla