Decoder.cpp (20724B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "Decoder.h" 7 8 #include "DecodePool.h" 9 #include "IDecodingTask.h" 10 #include "ISurfaceProvider.h" 11 #include "gfxPlatform.h" 12 #include "mozilla/gfx/2D.h" 13 #include "mozilla/gfx/Point.h" 14 #include "mozilla/ProfilerLabels.h" 15 #include "nsComponentManagerUtils.h" 16 #include "nsProxyRelease.h" 17 #include "nsServiceManagerUtils.h" 18 #include "mozilla/StaticPrefs_gfx.h" 19 20 using mozilla::gfx::IntPoint; 21 using mozilla::gfx::IntRect; 22 using mozilla::gfx::IntSize; 23 using mozilla::gfx::SurfaceFormat; 24 using namespace mozilla::gfx::CICP; 25 26 namespace mozilla { 27 namespace image { 28 29 class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final { 30 public: 31 explicit AutoRecordDecoderTelemetry(Decoder* aDecoder) : mDecoder(aDecoder) { 32 MOZ_ASSERT(mDecoder); 33 34 // Begin recording telemetry data. 35 mStartTime = TimeStamp::Now(); 36 } 37 38 ~AutoRecordDecoderTelemetry() { 39 // Finish telemetry. 40 mDecoder->mDecodeTime += (TimeStamp::Now() - mStartTime); 41 } 42 43 private: 44 Decoder* mDecoder; 45 TimeStamp mStartTime; 46 }; 47 48 Decoder::Decoder(RasterImage* aImage) 49 : mInProfile(nullptr), 50 mTransform(nullptr), 51 mImageData(nullptr), 52 mImageDataLength(0), 53 mCMSMode(gfxPlatform::GetCMSMode()), 54 mImage(aImage), 55 mFrameRecycler(nullptr), 56 mProgress(NoProgress), 57 mFrameCount(0), 58 mLoopLength(FrameTimeout::Zero()), 59 mDecoderFlags(DefaultDecoderFlags()), 60 mSurfaceFlags(DefaultSurfaceFlags()), 61 mInitialized(false), 62 mMetadataDecode(false), 63 mHaveExplicitOutputSize(false), 64 mInFrame(false), 65 mFinishedNewFrame(false), 66 mHasFrameToTake(false), 67 mReachedTerminalState(false), 68 mDecodeDone(false), 69 mError(false), 70 mShouldReportError(false), 71 mFinalizeFrames(true) {} 72 73 Decoder::~Decoder() { 74 MOZ_ASSERT(mProgress == NoProgress || !mImage, 75 "Destroying Decoder without taking all its progress changes"); 76 MOZ_ASSERT(mInvalidRect.IsEmpty() || !mImage, 77 "Destroying Decoder without taking all its invalidations"); 78 mInitialized = false; 79 80 if (mInProfile) { 81 // mTransform belongs to us only if mInProfile is non-null 82 if (mTransform) { 83 qcms_transform_release(mTransform); 84 } 85 qcms_profile_release(mInProfile); 86 } 87 88 if (mImage && !NS_IsMainThread()) { 89 // Dispatch mImage to main thread to prevent it from being destructed by the 90 // decode thread. 91 SurfaceCache::ReleaseImageOnMainThread(mImage.forget()); 92 } 93 } 94 95 void Decoder::SetSurfaceFlags(SurfaceFlags aSurfaceFlags) { 96 MOZ_ASSERT(!mInitialized); 97 MOZ_ASSERT(!(mSurfaceFlags & SurfaceFlags::NO_COLORSPACE_CONVERSION) || 98 !(mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE)); 99 mSurfaceFlags = aSurfaceFlags; 100 if (mSurfaceFlags & SurfaceFlags::NO_COLORSPACE_CONVERSION) { 101 mCMSMode = CMSMode::Off; 102 } 103 if (mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE) { 104 // CMSMode::TaggedOnly and CMSMode::All are equivalent when the 105 // TO_SRGB_COLORSPACE flag is set (for untagged images CMSMode::All assumes 106 // they are in sRGB space so it does nothing, which is same as what 107 // CMSMode::TaggedOnly does for untagged images). We just want to avoid 108 // CMSMode::Off so that the sRGB conversion actually happens. 109 mCMSMode = CMSMode::All; 110 } 111 } 112 113 qcms_profile* Decoder::GetCMSOutputProfile() const { 114 if (mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE) { 115 return gfxPlatform::GetCMSsRGBProfile(); 116 } 117 return gfxPlatform::GetCMSOutputProfile(); 118 } 119 120 qcms_transform* Decoder::GetCMSsRGBTransform(SurfaceFormat aFormat) const { 121 if (mSurfaceFlags & SurfaceFlags::TO_SRGB_COLORSPACE) { 122 // We want a transform to convert from sRGB to device space, but we are 123 // already using sRGB as our device space. That means we can skip 124 // color management entirely. 125 return nullptr; 126 } 127 if (qcms_profile_is_sRGB(gfxPlatform::GetCMSOutputProfile())) { 128 // Device space is sRGB so we can skip color management as well. 129 return nullptr; 130 } 131 132 switch (aFormat) { 133 case SurfaceFormat::B8G8R8A8: 134 case SurfaceFormat::B8G8R8X8: 135 return gfxPlatform::GetCMSBGRATransform(); 136 case SurfaceFormat::R8G8B8A8: 137 case SurfaceFormat::R8G8B8X8: 138 return gfxPlatform::GetCMSRGBATransform(); 139 case SurfaceFormat::R8G8B8: 140 return gfxPlatform::GetCMSRGBTransform(); 141 default: 142 MOZ_ASSERT_UNREACHABLE("Unsupported surface format!"); 143 return nullptr; 144 } 145 } 146 147 /* 148 * Common implementation of the decoder interface. 149 */ 150 151 nsresult Decoder::Init() { 152 // No re-initializing 153 MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!"); 154 155 // All decoders must have a SourceBufferIterator. 156 MOZ_ASSERT(mIterator); 157 158 // Metadata decoders must not set an output size. 159 MOZ_ASSERT_IF(mMetadataDecode, !mHaveExplicitOutputSize); 160 161 // All decoders must be anonymous except for metadata decoders. 162 // XXX(seth): Soon that exception will be removed. 163 MOZ_ASSERT_IF(mImage, IsMetadataDecode()); 164 165 // We can only request the frame count for metadata decoders. 166 MOZ_ASSERT_IF(WantsFrameCount(), IsMetadataDecode()); 167 168 // Implementation-specific initialization. 169 nsresult rv = InitInternal(); 170 171 mInitialized = true; 172 173 return rv; 174 } 175 176 LexerResult Decoder::Decode(IResumable* aOnResume /* = nullptr */) { 177 MOZ_ASSERT(mInitialized, "Should be initialized here"); 178 MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator"); 179 180 // If we're already done, don't attempt to keep decoding. 181 if (GetDecodeDone()) { 182 return LexerResult(HasError() ? TerminalState::FAILURE 183 : TerminalState::SUCCESS); 184 } 185 186 LexerResult lexerResult(TerminalState::FAILURE); 187 { 188 AUTO_PROFILER_LABEL_CATEGORY_PAIR_RELEVANT_FOR_JS(GRAPHICS_ImageDecoding); 189 AutoRecordDecoderTelemetry telemetry(this); 190 191 lexerResult = DoDecode(*mIterator, aOnResume); 192 }; 193 194 if (lexerResult.is<Yield>()) { 195 // We either need more data to continue (in which case either @aOnResume or 196 // the caller will reschedule us to run again later), or the decoder is 197 // yielding to allow the caller access to some intermediate output. 198 return lexerResult; 199 } 200 201 // We reached a terminal state; we're now done decoding. 202 MOZ_ASSERT(lexerResult.is<TerminalState>()); 203 mReachedTerminalState = true; 204 205 // If decoding failed, record that fact. 206 if (lexerResult.as<TerminalState>() == TerminalState::FAILURE) { 207 PostError(); 208 } 209 210 // Perform final cleanup. 211 CompleteDecode(); 212 213 return LexerResult(HasError() ? TerminalState::FAILURE 214 : TerminalState::SUCCESS); 215 } 216 217 LexerResult Decoder::TerminateFailure() { 218 PostError(); 219 220 // Perform final cleanup if need be. 221 if (!mReachedTerminalState) { 222 mReachedTerminalState = true; 223 CompleteDecode(); 224 } 225 226 return LexerResult(TerminalState::FAILURE); 227 } 228 229 bool Decoder::ShouldSyncDecode(size_t aByteLimit) { 230 MOZ_ASSERT(aByteLimit > 0); 231 MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator"); 232 233 return mIterator->RemainingBytesIsNoMoreThan(aByteLimit); 234 } 235 236 void Decoder::CompleteDecode() { 237 // Implementation-specific finalization. 238 nsresult rv = BeforeFinishInternal(); 239 if (NS_FAILED(rv)) { 240 PostError(); 241 } 242 243 rv = HasError() ? FinishWithErrorInternal() : FinishInternal(); 244 if (NS_FAILED(rv)) { 245 PostError(); 246 } 247 248 if (IsMetadataDecode()) { 249 // If this was a metadata decode and we never got a size, the decode failed. 250 if (!HasSize()) { 251 PostError(); 252 } 253 return; 254 } 255 256 // If the implementation left us mid-frame, finish that up. Note that it may 257 // have left us transparent. 258 if (mInFrame) { 259 PostHasTransparency(); 260 PostFrameStop(); 261 } 262 263 // If PostDecodeDone() has not been called, we may need to send teardown 264 // notifications if it is unrecoverable. 265 if (mDecodeDone) { 266 MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame"); 267 } else { 268 // We should always report an error to the console in this case. 269 mShouldReportError = true; 270 271 if (GetCompleteFrameCount() > 0) { 272 // We're usable if we have at least one complete frame, so do exactly 273 // what we should have when the decoder completed. 274 PostHasTransparency(); 275 PostDecodeDone(); 276 } else { 277 // We're not usable. Record some final progress indicating the error. 278 mProgress |= FLAG_DECODE_COMPLETE | FLAG_HAS_ERROR; 279 } 280 } 281 } 282 283 void Decoder::SetOutputSize(const OrientedIntSize& aSize) { 284 mOutputSize = Some(aSize); 285 mHaveExplicitOutputSize = true; 286 } 287 288 Maybe<OrientedIntSize> Decoder::ExplicitOutputSize() const { 289 MOZ_ASSERT_IF(mHaveExplicitOutputSize, mOutputSize); 290 return mHaveExplicitOutputSize ? mOutputSize : Nothing(); 291 } 292 293 Maybe<uint32_t> Decoder::TakeCompleteFrameCount() { 294 const bool finishedNewFrame = mFinishedNewFrame; 295 mFinishedNewFrame = false; 296 return finishedNewFrame ? Some(GetCompleteFrameCount()) : Nothing(); 297 } 298 299 DecoderFinalStatus Decoder::FinalStatus() const { 300 return DecoderFinalStatus(IsMetadataDecode(), GetDecodeDone(), HasError(), 301 ShouldReportError()); 302 } 303 304 DecoderTelemetry Decoder::Telemetry() const { 305 MOZ_ASSERT(mIterator); 306 return DecoderTelemetry(SpeedMetric(), mIterator ? mIterator->ByteCount() : 0, 307 mIterator ? mIterator->ChunkCount() : 0, mDecodeTime); 308 } 309 310 nsresult Decoder::AllocateFrame(const gfx::IntSize& aOutputSize, 311 gfx::SurfaceFormat aFormat, 312 const Maybe<AnimationParams>& aAnimParams) { 313 mCurrentFrame = AllocateFrameInternal(aOutputSize, aFormat, aAnimParams, 314 std::move(mCurrentFrame)); 315 316 if (mCurrentFrame) { 317 mHasFrameToTake = true; 318 319 mImageData = mCurrentFrame.Data(); 320 321 // We should now be on |aFrameNum|. (Note that we're comparing the frame 322 // number, which is zero-based, with the frame count, which is one-based.) 323 MOZ_ASSERT_IF(aAnimParams, aAnimParams->mFrameNum + 1 == mFrameCount); 324 325 // If we're past the first frame, PostIsAnimated() should've been called. 326 MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation()); 327 328 // Update our state to reflect the new frame. 329 MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!"); 330 mInFrame = true; 331 } else { 332 mImageData = nullptr; 333 mImageDataLength = 0; 334 } 335 336 return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE; 337 } 338 339 RawAccessFrameRef Decoder::AllocateFrameInternal( 340 const gfx::IntSize& aOutputSize, SurfaceFormat aFormat, 341 const Maybe<AnimationParams>& aAnimParams, 342 RawAccessFrameRef&& aPreviousFrame) { 343 if (HasError()) { 344 return RawAccessFrameRef(); 345 } 346 347 uint32_t frameNum = aAnimParams ? aAnimParams->mFrameNum : 0; 348 if (frameNum != mFrameCount) { 349 MOZ_ASSERT_UNREACHABLE("Allocating frames out of order"); 350 return RawAccessFrameRef(); 351 } 352 353 if (aOutputSize.width <= 0 || aOutputSize.height <= 0) { 354 NS_WARNING("Trying to add frame with zero or negative size"); 355 return RawAccessFrameRef(); 356 } 357 358 if (frameNum > 0) { 359 if (aPreviousFrame->GetDisposalMethod() != 360 DisposalMethod::RESTORE_PREVIOUS) { 361 // If the new restore frame is the direct previous frame, then we know 362 // the dirty rect is composed only of the current frame's blend rect and 363 // the restore frame's clear rect (if applicable) which are handled in 364 // filters. 365 mRestoreFrame = std::move(aPreviousFrame); 366 mRestoreDirtyRect.SetBox(0, 0, 0, 0); 367 } else { 368 // We only need the previous frame's dirty rect, because while there may 369 // have been several frames between us and mRestoreFrame, the only areas 370 // that changed are the restore frame's clear rect, the current frame 371 // blending rect, and the previous frame's blending rect. All else is 372 // forgotten due to us restoring the same frame again. 373 mRestoreDirtyRect = aPreviousFrame->GetBoundedBlendRect(); 374 } 375 } 376 377 RawAccessFrameRef ref; 378 379 // If we have a frame recycler, it must be for an animated image producing 380 // full frames. If the higher layers are discarding frames because of the 381 // memory footprint, then the recycler will allow us to reuse the buffers. 382 // Each frame should be the same size and have mostly the same properties. 383 if (mFrameRecycler) { 384 MOZ_ASSERT(aAnimParams); 385 386 ref = mFrameRecycler->RecycleFrame(mRecycleRect); 387 if (ref) { 388 // If the recycled frame is actually the current restore frame, we cannot 389 // use it. If the next restore frame is the new frame we are creating, in 390 // theory we could reuse it, but we would need to store the restore frame 391 // animation parameters elsewhere. For now we just drop it. 392 bool blocked = ref.get() == mRestoreFrame.get(); 393 if (!blocked) { 394 blocked = NS_FAILED( 395 ref->InitForDecoderRecycle(aAnimParams.ref(), &mImageDataLength)); 396 } 397 398 if (blocked) { 399 ref.reset(); 400 } 401 } 402 } 403 404 // Either the recycler had nothing to give us, or we don't have a recycler. 405 // Produce a new frame to store the data. 406 if (!ref) { 407 // There is no underlying data to reuse, so reset the recycle rect to be 408 // the full frame, to ensure the restore frame is fully copied. 409 mRecycleRect = IntRect(IntPoint(0, 0), aOutputSize); 410 411 bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA); 412 auto frame = MakeNotNull<RefPtr<imgFrame>>(); 413 if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFormat, nonPremult, 414 aAnimParams, bool(mFrameRecycler), 415 &mImageDataLength))) { 416 NS_WARNING("imgFrame::Init should succeed"); 417 return RawAccessFrameRef(); 418 } 419 420 ref = frame->RawAccessRef(gfx::DataSourceSurface::READ_WRITE); 421 if (!ref) { 422 frame->Abort(); 423 return RawAccessFrameRef(); 424 } 425 } 426 427 mFrameCount++; 428 429 return ref; 430 } 431 432 /* 433 * Hook stubs. Override these as necessary in decoder implementations. 434 */ 435 436 nsresult Decoder::InitInternal() { return NS_OK; } 437 nsresult Decoder::BeforeFinishInternal() { return NS_OK; } 438 nsresult Decoder::FinishInternal() { return NS_OK; } 439 440 nsresult Decoder::FinishWithErrorInternal() { 441 MOZ_ASSERT(!mInFrame); 442 return NS_OK; 443 } 444 445 /* 446 * Progress Notifications 447 */ 448 449 void Decoder::PostSize(int32_t aWidth, int32_t aHeight, 450 Orientation aOrientation, Resolution aResolution) { 451 // Validate. 452 MOZ_ASSERT(aWidth >= 0, "Width can't be negative!"); 453 MOZ_ASSERT(aHeight >= 0, "Height can't be negative!"); 454 455 // Set our intrinsic size. 456 mImageMetadata.SetSize(aWidth, aHeight, aOrientation, aResolution); 457 458 // Verify it is the expected size, if given. Note that this is only used by 459 // the ICO decoder for embedded image types, so only its subdecoders are 460 // required to handle failures in PostSize. 461 if (!IsExpectedSize()) { 462 PostError(); 463 return; 464 } 465 466 // Set our output size if it's not already set. 467 if (!mOutputSize) { 468 mOutputSize = Some(mImageMetadata.GetSize()); 469 } 470 471 MOZ_ASSERT(mOutputSize->width <= mImageMetadata.GetSize().width && 472 mOutputSize->height <= mImageMetadata.GetSize().height, 473 "Output size will result in upscaling"); 474 475 // Record this notification. 476 mProgress |= FLAG_SIZE_AVAILABLE; 477 } 478 479 void Decoder::PostHasTransparency() { mProgress |= FLAG_HAS_TRANSPARENCY; } 480 481 void Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout) { 482 mProgress |= FLAG_IS_ANIMATED; 483 mImageMetadata.SetHasAnimation(); 484 mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout); 485 } 486 487 void Decoder::PostFrameCount(uint32_t aFrameCount) { 488 mImageMetadata.SetFrameCount(aFrameCount); 489 } 490 491 void Decoder::PostFrameStop(Opacity aFrameOpacity) { 492 // We should be mid-frame 493 MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode"); 494 MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one"); 495 MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one"); 496 497 // Update our state. 498 mInFrame = false; 499 mFinishedNewFrame = true; 500 501 mCurrentFrame->Finish( 502 aFrameOpacity, mFinalizeFrames, 503 /* aOrientationSwapsWidthAndHeight = */ mImageMetadata.HasOrientation() && 504 mImageMetadata.GetOrientation().SwapsWidthAndHeight()); 505 506 mProgress |= FLAG_FRAME_COMPLETE; 507 508 mLoopLength += mCurrentFrame->GetTimeout(); 509 510 if (mFrameCount == 1) { 511 // If we're not sending partial invalidations, then we send an invalidation 512 // here when the first frame is complete. 513 if (!ShouldSendPartialInvalidations()) { 514 mInvalidRect.UnionRect(mInvalidRect, 515 OrientedIntRect(OrientedIntPoint(), Size())); 516 } 517 518 // If we dispose of the first frame by clearing it, then the first frame's 519 // refresh area is all of itself. RESTORE_PREVIOUS is invalid (assumed to 520 // be DISPOSE_CLEAR). 521 switch (mCurrentFrame->GetDisposalMethod()) { 522 default: 523 MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod"); 524 case DisposalMethod::CLEAR: 525 case DisposalMethod::CLEAR_ALL: 526 case DisposalMethod::RESTORE_PREVIOUS: 527 mFirstFrameRefreshArea = IntRect(IntPoint(), Size().ToUnknownSize()); 528 break; 529 case DisposalMethod::KEEP: 530 case DisposalMethod::NOT_SPECIFIED: 531 break; 532 } 533 } else { 534 // Some GIFs are huge but only have a small area that they animate. We only 535 // need to refresh that small area when frame 0 comes around again. 536 mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, 537 mCurrentFrame->GetBoundedBlendRect()); 538 } 539 } 540 541 void Decoder::PostInvalidation(const OrientedIntRect& aRect, 542 const Maybe<OrientedIntRect>& aRectAtOutputSize 543 /* = Nothing() */) { 544 // We should be mid-frame 545 MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!"); 546 MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!"); 547 548 // Record this invalidation, unless we're not sending partial invalidations 549 // or we're past the first frame. 550 if (ShouldSendPartialInvalidations() && mFrameCount == 1) { 551 mInvalidRect.UnionRect(mInvalidRect, aRect); 552 mCurrentFrame->ImageUpdated( 553 aRectAtOutputSize.valueOr(aRect).ToUnknownRect()); 554 } 555 } 556 557 void Decoder::PostLoopCount(int32_t aLoopCount) { 558 mImageMetadata.SetLoopCount(aLoopCount); 559 } 560 561 void Decoder::PostDecodeDone() { 562 MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode"); 563 MOZ_ASSERT(!mInFrame, "Can't be done decoding if we're mid-frame!"); 564 MOZ_ASSERT(!mDecodeDone, "Decode already done!"); 565 mDecodeDone = true; 566 567 // Some metadata that we track should take into account every frame in the 568 // image. If this is a first-frame-only decode, our accumulated loop length 569 // and first frame refresh area only includes the first frame, so it's not 570 // correct and we don't record it. 571 if (!IsFirstFrameDecode()) { 572 mImageMetadata.SetLoopLength(mLoopLength); 573 mImageMetadata.SetFirstFrameRefreshArea(mFirstFrameRefreshArea); 574 } 575 576 mProgress |= FLAG_DECODE_COMPLETE; 577 } 578 579 void Decoder::PostError() { 580 mError = true; 581 582 if (mInFrame) { 583 MOZ_ASSERT(mCurrentFrame); 584 MOZ_ASSERT(mFrameCount > 0); 585 mCurrentFrame->Abort(); 586 mInFrame = false; 587 --mFrameCount; 588 mHasFrameToTake = false; 589 } 590 } 591 592 /* static */ 593 uint8_t Decoder::ChooseTransferCharacteristics(uint8_t aTC) { 594 // Most apps, including Chrome 595 // (https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/color_space.cc;l=906;drc=2e47178120fb82aced74f8dbccf358aa13073a83), 596 // use the sRGB TC for BT.709 TC. We have a pref to provide that behaviour. 597 // Since BT.2020 uses the same TC we can also optionally use this behaviour 598 // for BT.2020. 599 const bool rec709GammaAsSrgb = 600 StaticPrefs::gfx_color_management_rec709_gamma_as_srgb(); 601 const bool rec2020GammaAsRec709 = 602 StaticPrefs::gfx_color_management_rec2020_gamma_as_rec709(); 603 switch (aTC) { 604 case TransferCharacteristics::TC_BT709: 605 case TransferCharacteristics::TC_BT601: 606 if (rec709GammaAsSrgb) { 607 return TransferCharacteristics::TC_SRGB; 608 } 609 break; 610 case TransferCharacteristics::TC_BT2020_10BIT: 611 case TransferCharacteristics::TC_BT2020_12BIT: 612 if (rec2020GammaAsRec709) { 613 if (rec709GammaAsSrgb) { 614 return TransferCharacteristics::TC_SRGB; 615 } 616 return TransferCharacteristics::TC_BT709; 617 } 618 break; 619 default: 620 break; 621 } 622 return aTC; 623 } 624 625 } // namespace image 626 } // namespace mozilla