tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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