コード例 #1
0
ファイル: FrameAnimator.cpp プロジェクト: alphan102/gecko-dev
//******************************************************************************
// DoBlend gets called when the timer for animation get fired and we have to
// update the composited frame of the animation.
bool
FrameAnimator::DoBlend(IntRect* aDirtyRect,
                       uint32_t aPrevFrameIndex,
                       uint32_t aNextFrameIndex)
{
  RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
  RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);

  MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");

  AnimationData prevFrameData = prevFrame->GetAnimationData();
  if (prevFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
      !mCompositingPrevFrame) {
    prevFrameData.mDisposalMethod = DisposalMethod::CLEAR;
  }

  IntRect prevRect = prevFrameData.mBlendRect
                   ? prevFrameData.mRect.Intersect(*prevFrameData.mBlendRect)
                   : prevFrameData.mRect;

  bool isFullPrevFrame = prevRect.x == 0 && prevRect.y == 0 &&
                         prevRect.width == mSize.width &&
                         prevRect.height == mSize.height;

  // Optimization: DisposeClearAll if the previous frame is the same size as
  //               container and it's clearing itself
  if (isFullPrevFrame &&
      (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR)) {
    prevFrameData.mDisposalMethod = DisposalMethod::CLEAR_ALL;
  }

  AnimationData nextFrameData = nextFrame->GetAnimationData();

  IntRect nextRect = nextFrameData.mBlendRect
                   ? nextFrameData.mRect.Intersect(*nextFrameData.mBlendRect)
                   : nextFrameData.mRect;

  bool isFullNextFrame = nextRect.x == 0 && nextRect.y == 0 &&
                         nextRect.width == mSize.width &&
                         nextRect.height == mSize.height;

  if (!nextFrame->GetIsPaletted()) {
    // Optimization: Skip compositing if the previous frame wants to clear the
    //               whole image
    if (prevFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL) {
      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
      return true;
    }

    // Optimization: Skip compositing if this frame is the same size as the
    //               container and it's fully drawing over prev frame (no alpha)
    if (isFullNextFrame &&
        (nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
        !nextFrameData.mHasAlpha) {
      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
      return true;
    }
  }

  // Calculate area that needs updating
  switch (prevFrameData.mDisposalMethod) {
    default:
      MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
    case DisposalMethod::NOT_SPECIFIED:
    case DisposalMethod::KEEP:
      *aDirtyRect = nextRect;
      break;

    case DisposalMethod::CLEAR_ALL:
      // Whole image container is cleared
      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
      break;

    case DisposalMethod::CLEAR:
      // Calc area that needs to be redrawn (the combination of previous and
      // this frame)
      // XXX - This could be done with multiple framechanged calls
      //       Having prevFrame way at the top of the image, and nextFrame
      //       way at the bottom, and both frames being small, we'd be
      //       telling framechanged to refresh the whole image when only two
      //       small areas are needed.
      aDirtyRect->UnionRect(nextRect, prevRect);
      break;

    case DisposalMethod::RESTORE_PREVIOUS:
      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
      break;
  }

  // Optimization:
  //   Skip compositing if the last composited frame is this frame
  //   (Only one composited frame was made for this animation.  Example:
  //    Only Frame 3 of a 10 frame image required us to build a composite frame
  //    On the second loop, we do not need to rebuild the frame
  //    since it's still sitting in compositingFrame)
  if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
    return true;
  }

  bool needToBlankComposite = false;

  // Create the Compositing Frame
  if (!mCompositingFrame) {
    RefPtr<imgFrame> newFrame = new imgFrame;
    nsresult rv = newFrame->InitForDecoder(mSize,
                                           SurfaceFormat::B8G8R8A8);
    if (NS_FAILED(rv)) {
      mCompositingFrame.reset();
      return false;
    }
    mCompositingFrame = newFrame->RawAccessRef();
    needToBlankComposite = true;
  } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {

    // If we are not drawing on top of last composited frame,
    // then we are building a new composite frame, so let's clear it first.
    needToBlankComposite = true;
  }

  AnimationData compositingFrameData = mCompositingFrame->GetAnimationData();

  // More optimizations possible when next frame is not transparent
  // But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
  // this "no disposal" optimization is not possible,
  // because the frame in "after disposal operation" state
  // needs to be stored in compositingFrame, so it can be
  // copied into compositingPrevFrame later.
  bool doDisposal = true;
  if (!nextFrameData.mHasAlpha &&
      nextFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
    if (isFullNextFrame) {
      // Optimization: No need to dispose prev.frame when
      // next frame is full frame and not transparent.
      doDisposal = false;
      // No need to blank the composite frame
      needToBlankComposite = false;
    } else {
      if ((prevRect.x >= nextRect.x) && (prevRect.y >= nextRect.y) &&
          (prevRect.x + prevRect.width <= nextRect.x + nextRect.width) &&
          (prevRect.y + prevRect.height <= nextRect.y + nextRect.height)) {
        // Optimization: No need to dispose prev.frame when
        // next frame fully overlaps previous frame.
        doDisposal = false;
      }
    }
  }

  if (doDisposal) {
    // Dispose of previous: clear, restore, or keep (copy)
    switch (prevFrameData.mDisposalMethod) {
      case DisposalMethod::CLEAR:
        if (needToBlankComposite) {
          // If we just created the composite, it could have anything in its
          // buffer. Clear whole frame
          ClearFrame(compositingFrameData.mRawData,
                     compositingFrameData.mRect);
        } else {
          // Only blank out previous frame area (both color & Mask/Alpha)
          ClearFrame(compositingFrameData.mRawData,
                     compositingFrameData.mRect,
                     prevRect);
        }
        break;

      case DisposalMethod::CLEAR_ALL:
        ClearFrame(compositingFrameData.mRawData,
                   compositingFrameData.mRect);
        break;

      case DisposalMethod::RESTORE_PREVIOUS:
        // It would be better to copy only the area changed back to
        // compositingFrame.
        if (mCompositingPrevFrame) {
          AnimationData compositingPrevFrameData =
            mCompositingPrevFrame->GetAnimationData();

          CopyFrameImage(compositingPrevFrameData.mRawData,
                         compositingPrevFrameData.mRect,
                         compositingFrameData.mRawData,
                         compositingFrameData.mRect);

          // destroy only if we don't need it for this frame's disposal
          if (nextFrameData.mDisposalMethod !=
              DisposalMethod::RESTORE_PREVIOUS) {
            mCompositingPrevFrame.reset();
          }
        } else {
          ClearFrame(compositingFrameData.mRawData,
                     compositingFrameData.mRect);
        }
        break;

      default:
        MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
      case DisposalMethod::NOT_SPECIFIED:
      case DisposalMethod::KEEP:
        // Copy previous frame into compositingFrame before we put the new
        // frame on top
        // Assumes that the previous frame represents a full frame (it could be
        // smaller in size than the container, as long as the frame before it
        // erased itself)
        // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
        // will always be a valid frame number.
        if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
          if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
            // Just copy the bits
            CopyFrameImage(prevFrameData.mRawData,
                           prevRect,
                           compositingFrameData.mRawData,
                           compositingFrameData.mRect);
          } else {
            if (needToBlankComposite) {
              // Only blank composite when prev is transparent or not full.
              if (prevFrameData.mHasAlpha || !isFullPrevFrame) {
                ClearFrame(compositingFrameData.mRawData,
                           compositingFrameData.mRect);
              }
            }
            DrawFrameTo(prevFrameData.mRawData, prevFrameData.mRect,
                        prevFrameData.mPaletteDataLength,
                        prevFrameData.mHasAlpha,
                        compositingFrameData.mRawData,
                        compositingFrameData.mRect,
                        prevFrameData.mBlendMethod,
                        prevFrameData.mBlendRect);
          }
        }
    }
  } else if (needToBlankComposite) {
    // If we just created the composite, it could have anything in its
    // buffers. Clear them
    ClearFrame(compositingFrameData.mRawData,
               compositingFrameData.mRect);
  }

  // Check if the frame we are composing wants the previous image restored after
  // it is done. Don't store it (again) if last frame wanted its image restored
  // too
  if ((nextFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
      (prevFrameData.mDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
    // We are storing the whole image.
    // It would be better if we just stored the area that nextFrame is going to
    // overwrite.
    if (!mCompositingPrevFrame) {
      RefPtr<imgFrame> newFrame = new imgFrame;
      nsresult rv = newFrame->InitForDecoder(mSize,
                                             SurfaceFormat::B8G8R8A8);
      if (NS_FAILED(rv)) {
        mCompositingPrevFrame.reset();
        return false;
      }

      mCompositingPrevFrame = newFrame->RawAccessRef();
    }

    AnimationData compositingPrevFrameData =
      mCompositingPrevFrame->GetAnimationData();

    CopyFrameImage(compositingFrameData.mRawData,
                   compositingFrameData.mRect,
                   compositingPrevFrameData.mRawData,
                   compositingPrevFrameData.mRect);

    mCompositingPrevFrame->Finish();
  }

  // blit next frame into it's correct spot
  DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
              nextFrameData.mPaletteDataLength,
              nextFrameData.mHasAlpha,
              compositingFrameData.mRawData,
              compositingFrameData.mRect,
              nextFrameData.mBlendMethod,
              nextFrameData.mBlendRect);

  // Tell the image that it is fully 'downloaded'.
  mCompositingFrame->Finish();

  mLastCompositedFrameIndex = int32_t(aNextFrameIndex);

  return true;
}
コード例 #2
0
ファイル: ImageOps.cpp プロジェクト: MekliCZ/positron
/* static */ already_AddRefed<gfx::SourceSurface>
ImageOps::DecodeToSurface(nsIInputStream* aInputStream,
                          const nsACString& aMimeType,
                          uint32_t aFlags)
{
  MOZ_ASSERT(aInputStream);

  nsresult rv;

  // Prepare the input stream.
  nsCOMPtr<nsIInputStream> inputStream = aInputStream;
  if (!NS_InputStreamIsBuffered(aInputStream)) {
    nsCOMPtr<nsIInputStream> bufStream;
    rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
                                   aInputStream, 1024);
    if (NS_SUCCEEDED(rv)) {
      inputStream = bufStream;
    }
  }

  // Figure out how much data we've been passed.
  uint64_t length;
  rv = inputStream->Available(&length);
  if (NS_FAILED(rv) || length > UINT32_MAX) {
    return nullptr;
  }

  // Write the data into a SourceBuffer.
  RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
  sourceBuffer->ExpectLength(length);
  rv = sourceBuffer->AppendFromInputStream(inputStream, length);
  if (NS_FAILED(rv)) {
    return nullptr;
  }
  sourceBuffer->Complete(NS_OK);

  // Create a decoder.
  DecoderType decoderType =
    DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
  RefPtr<Decoder> decoder =
    DecoderFactory::CreateAnonymousDecoder(decoderType,
                                           sourceBuffer,
                                           ToSurfaceFlags(aFlags));
  if (!decoder) {
    return nullptr;
  }

  // Run the decoder synchronously.
  decoder->Decode();
  if (!decoder->GetDecodeDone() || decoder->HasError()) {
    return nullptr;
  }

  // Pull out the surface.
  RawAccessFrameRef frame = decoder->GetCurrentFrameRef();
  if (!frame) {
    return nullptr;
  }

  RefPtr<SourceSurface> surface = frame->GetSurface();
  if (!surface) {
    return nullptr;
  }

  return surface.forget();
}
コード例 #3
0
ファイル: FrameAnimator.cpp プロジェクト: alphan102/gecko-dev
RefreshResult
FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
{
  NS_ASSERTION(aTime <= TimeStamp::Now(),
               "Given time appears to be in the future");
  PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);

  RefreshResult ret;

  // Determine what the next frame is, taking into account looping.
  uint32_t currentFrameIndex = aState.mCurrentAnimationFrameIndex;
  uint32_t nextFrameIndex = currentFrameIndex + 1;

  // Check if we're at the end of the loop. (FrameCount() returns Nothing() if
  // we don't know the total count yet.)
  if (aState.FrameCount() == Some(nextFrameIndex)) {
    // If we are not looping forever, initialize the loop counter
    if (aState.mLoopRemainingCount < 0 && aState.LoopCount() >= 0) {
      aState.mLoopRemainingCount = aState.LoopCount();
    }

    // If animation mode is "loop once", or we're at end of loop counter,
    // it's time to stop animating.
    if (aState.mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
        aState.mLoopRemainingCount == 0) {
      ret.mAnimationFinished = true;
    }

    nextFrameIndex = 0;

    if (aState.mLoopRemainingCount > 0) {
      aState.mLoopRemainingCount--;
    }

    // If we're done, exit early.
    if (ret.mAnimationFinished) {
      return ret;
    }
  }

  if (nextFrameIndex >= aState.KnownFrameCount()) {
    // We've already advanced to the last decoded frame, nothing more we can do.
    // We're blocked by network/decoding from displaying the animation at the
    // rate specified, so that means the frame we are displaying (the latest
    // available) is the frame we want to be displaying at this time. So we
    // update the current animation time. If we didn't update the current
    // animation time then it could lag behind, which would indicate that we are
    // behind in the animation and should try to catch up. When we are done
    // decoding (and thus can loop around back to the start of the animation) we
    // would then jump to a random point in the animation to try to catch up.
    // But we were never behind in the animation.
    aState.mCurrentAnimationFrameTime = aTime;
    return ret;
  }

  // There can be frames in the surface cache with index >= KnownFrameCount()
  // which GetRawFrame() can access because an async decoder has decoded them,
  // but which AnimationState doesn't know about yet because we haven't received
  // the appropriate notification on the main thread. Make sure we stay in sync
  // with AnimationState.
  MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
  RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);

  // We should always check to see if we have the next frame even if we have
  // previously finished decoding. If we needed to redecode (e.g. due to a draw
  // failure) we would have discarded all the old frames and may not yet have
  // the new ones.
  if (!nextFrame || !nextFrame->IsFinished()) {
    // Uh oh, the frame we want to show is currently being decoded (partial)
    // Wait until the next refresh driver tick and try again
    return ret;
  }

  if (GetTimeoutForFrame(nextFrameIndex) == FrameTimeout::Forever()) {
    ret.mAnimationFinished = true;
  }

  if (nextFrameIndex == 0) {
    ret.mDirtyRect = aState.FirstFrameRefreshArea();
  } else {
    MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);

    // Change frame
    if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
      // something went wrong, move on to next
      NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
      nextFrame->SetCompositingFailed(true);
      aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);
      aState.mCurrentAnimationFrameIndex = nextFrameIndex;

      return ret;
    }

    nextFrame->SetCompositingFailed(false);
  }

  aState.mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime(aState);

  // If we can get closer to the current time by a multiple of the image's loop
  // time, we should. We can only do this if we're done decoding; otherwise, we
  // don't know the full loop length, and LoopLength() will have to return
  // FrameTimeout::Forever().
  FrameTimeout loopTime = aState.LoopLength();
  if (loopTime != FrameTimeout::Forever()) {
    TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime;
    if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) {
      // Explicitly use integer division to get the floor of the number of
      // loops.
      uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds())
                     / loopTime.AsMilliseconds();
      aState.mCurrentAnimationFrameTime +=
        TimeDuration::FromMilliseconds(loops * loopTime.AsMilliseconds());
    }
  }

  // Set currentAnimationFrameIndex at the last possible moment
  aState.mCurrentAnimationFrameIndex = nextFrameIndex;

  // If we're here, we successfully advanced the frame.
  ret.mFrameAdvanced = true;

  return ret;
}
コード例 #4
0
ファイル: Decoder.cpp プロジェクト: ashishrana7/firefox
RawAccessFrameRef
Decoder::InternalAddFrame(uint32_t aFrameNum,
                          const nsIntSize& aTargetSize,
                          const nsIntRect& aFrameRect,
                          uint32_t aDecodeFlags,
                          SurfaceFormat aFormat,
                          uint8_t aPaletteDepth,
                          imgFrame* aPreviousFrame)
{
  MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
  if (aFrameNum > mFrameCount) {
    return RawAccessFrameRef();
  }

  if (aTargetSize.width <= 0 || aTargetSize.height <= 0 ||
      aFrameRect.width <= 0 || aFrameRect.height <= 0) {
    NS_WARNING("Trying to add frame with zero or negative size");
    return RawAccessFrameRef();
  }

  if (!SurfaceCache::CanHold(aTargetSize.ToIntSize())) {
    NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
    return RawAccessFrameRef();
  }

  nsRefPtr<imgFrame> frame = new imgFrame();
  bool nonPremult =
    aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
  if (NS_FAILED(frame->InitForDecoder(aTargetSize, aFrameRect, aFormat,
                                      aPaletteDepth, nonPremult))) {
    NS_WARNING("imgFrame::Init should succeed");
    return RawAccessFrameRef();
  }

  RawAccessFrameRef ref = frame->RawAccessRef();
  if (!ref) {
    frame->Abort();
    return RawAccessFrameRef();
  }

  InsertOutcome outcome =
    SurfaceCache::Insert(frame, ImageKey(mImage.get()),
                         RasterSurfaceKey(aTargetSize.ToIntSize(),
                                          aDecodeFlags,
                                          aFrameNum),
                         Lifetime::Persistent);
  if (outcome != InsertOutcome::SUCCESS) {
    // We either hit InsertOutcome::FAILURE, which is a temporary failure due to
    // low memory (we know it's not permanent because we checked CanHold()
    // above), or InsertOutcome::FAILURE_ALREADY_PRESENT, which means that
    // another decoder beat us to decoding this frame. Either way, we should
    // abort this decoder rather than treat this as a real error.
    mDecodeAborted = true;
    ref->Abort();
    return RawAccessFrameRef();
  }

  nsIntRect refreshArea;

  if (aFrameNum == 1) {
    MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
    aPreviousFrame->SetRawAccessOnly();

    // If we dispose of the first frame by clearing it, then the first frame's
    // refresh area is all of itself.
    // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
    AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
    if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
        previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
        previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
      refreshArea = previousFrameData.mRect;
    }
  }

  if (aFrameNum > 0) {
    ref->SetRawAccessOnly();

    // Some GIFs are huge but only have a small area that they animate. We only
    // need to refresh that small area when frame 0 comes around again.
    refreshArea.UnionRect(refreshArea, frame->GetRect());
  }

  mFrameCount++;
  mImage->OnAddedFrame(mFrameCount, refreshArea);

  return ref;
}
コード例 #5
0
ファイル: Decoder.cpp プロジェクト: hcheruku/gecko-dev
RawAccessFrameRef
Decoder::AllocateFrameInternal(uint32_t aFrameNum,
                               const nsIntSize& aTargetSize,
                               const nsIntRect& aFrameRect,
                               uint32_t aDecodeFlags,
                               SurfaceFormat aFormat,
                               uint8_t aPaletteDepth,
                               imgFrame* aPreviousFrame)
{
  if (mDataError || NS_FAILED(mFailCode)) {
    return RawAccessFrameRef();
  }

  if (aFrameNum != mFrameCount) {
    MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
    return RawAccessFrameRef();
  }

  if (aTargetSize.width <= 0 || aTargetSize.height <= 0 ||
      aFrameRect.width <= 0 || aFrameRect.height <= 0) {
    NS_WARNING("Trying to add frame with zero or negative size");
    return RawAccessFrameRef();
  }

  const uint32_t bytesPerPixel = aPaletteDepth == 0 ? 4 : 1;
  if (!SurfaceCache::CanHold(aFrameRect.Size(), bytesPerPixel)) {
    NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
    return RawAccessFrameRef();
  }

  nsRefPtr<imgFrame> frame = new imgFrame();
  bool nonPremult =
    aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
  if (NS_FAILED(frame->InitForDecoder(aTargetSize, aFrameRect, aFormat,
                                      aPaletteDepth, nonPremult))) {
    NS_WARNING("imgFrame::Init should succeed");
    return RawAccessFrameRef();
  }

  RawAccessFrameRef ref = frame->RawAccessRef();
  if (!ref) {
    frame->Abort();
    return RawAccessFrameRef();
  }

  InsertOutcome outcome =
    SurfaceCache::Insert(frame, ImageKey(mImage.get()),
                         RasterSurfaceKey(aTargetSize,
                                          aDecodeFlags,
                                          aFrameNum),
                         Lifetime::Persistent);
  if (outcome == InsertOutcome::FAILURE) {
    // We couldn't insert the surface, almost certainly due to low memory. We
    // treat this as a permanent error to help the system recover; otherwise, we
    // might just end up attempting to decode this image again immediately.
    ref->Abort();
    return RawAccessFrameRef();
  } else if (outcome == InsertOutcome::FAILURE_ALREADY_PRESENT) {
    // Another decoder beat us to decoding this frame. We abort this decoder
    // rather than treat this as a real error.
    mDecodeAborted = true;
    ref->Abort();
    return RawAccessFrameRef();
  }

  nsIntRect refreshArea;

  if (aFrameNum == 1) {
    MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
    aPreviousFrame->SetRawAccessOnly();

    // If we dispose of the first frame by clearing it, then the first frame's
    // refresh area is all of itself.
    // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
    AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
    if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
        previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
        previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
      refreshArea = previousFrameData.mRect;
    }
  }

  if (aFrameNum > 0) {
    ref->SetRawAccessOnly();

    // Some GIFs are huge but only have a small area that they animate. We only
    // need to refresh that small area when frame 0 comes around again.
    refreshArea.UnionRect(refreshArea, frame->GetRect());
  }

  mFrameCount++;
  mImage->OnAddedFrame(mFrameCount, refreshArea);

  return ref;
}
コード例 #6
0
TEST_F(ImageSurfacePipeIntegration, PalettedSurfacePipe)
{
  // Create a SurfacePipe containing a PalettedSurfaceSink.
  RefPtr<Decoder> decoder = CreateTrivialDecoder();
  ASSERT_TRUE(decoder != nullptr);

  auto sink = MakeUnique<PalettedSurfaceSink>();
  nsresult rv =
    sink->Configure(PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
                                            IntRect(0, 0, 100, 100),
                                            SurfaceFormat::B8G8R8A8,
                                            8, false });
  ASSERT_TRUE(NS_SUCCEEDED(rv));

  SurfacePipe pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink);

  // Test that WritePixels() gets passed through to the underlying pipeline.
  {
    uint32_t count = 0;
    auto result = pipe.WritePixels<uint8_t>([&]() {
      ++count;
      return AsVariant(uint8_t(255));
    });
    EXPECT_EQ(WriteState::FINISHED, result);
    EXPECT_EQ(100u * 100u, count);
    CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
  }

  // Create a buffer the same size as one row of the surface, containing all
  // 255 pixels. We'll use this for the WriteBuffer() tests.
  uint8_t buffer[100];
  for (int i = 0; i < 100; ++i) {
    buffer[i] = 255;
  }

  // Test that WriteBuffer() gets passed through to the underlying pipeline.
  {
    uint32_t count = 0;
    WriteState result = WriteState::NEED_MORE_DATA;
    while (result == WriteState::NEED_MORE_DATA) {
      result = pipe.WriteBuffer(buffer);
      ++count;
    }
    EXPECT_EQ(WriteState::FINISHED, result);
    EXPECT_EQ(100u, count);
    CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
  }

  // Test that the 3 argument version of WriteBuffer() gets passed through to
  // the underlying pipeline.
  {
    uint32_t count = 0;
    WriteState result = WriteState::NEED_MORE_DATA;
    while (result == WriteState::NEED_MORE_DATA) {
      result = pipe.WriteBuffer(buffer, 0, 100);
      ++count;
    }
    EXPECT_EQ(WriteState::FINISHED, result);
    EXPECT_EQ(100u, count);
    CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
  }

  // Test that WriteEmptyRow() gets passed through to the underlying pipeline.
  {
    uint32_t count = 0;
    WriteState result = WriteState::NEED_MORE_DATA;
    while (result == WriteState::NEED_MORE_DATA) {
      result = pipe.WriteEmptyRow();
      ++count;
    }
    EXPECT_EQ(WriteState::FINISHED, result);
    EXPECT_EQ(100u, count);
    CheckPalettedSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0));
  }

  // Mark the frame as finished so we don't get an assertion.
  RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef();
  currentFrame->Finish();
}
コード例 #7
0
ファイル: FrameAnimator.cpp プロジェクト: cclauss/gecko-dev
FrameAnimator::RefreshResult
FrameAnimator::AdvanceFrame(TimeStamp aTime)
{
  NS_ASSERTION(aTime <= TimeStamp::Now(),
               "Given time appears to be in the future");
  PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);

  RefreshResult ret;

  // Determine what the next frame is, taking into account looping.
  uint32_t currentFrameIndex = mCurrentAnimationFrameIndex;
  uint32_t nextFrameIndex = currentFrameIndex + 1;

  if (mImage->GetNumFrames() == nextFrameIndex) {
    // We can only accurately determine if we are at the end of the loop if we are
    // done decoding, otherwise we don't know how many frames there will be.
    if (!mDoneDecoding) {
      // We've already advanced to the last decoded frame, nothing more we can do.
      // We're blocked by network/decoding from displaying the animation at the
      // rate specified, so that means the frame we are displaying (the latest
      // available) is the frame we want to be displaying at this time. So we
      // update the current animation time. If we didn't update the current
      // animation time then it could lag behind, which would indicate that we
      // are behind in the animation and should try to catch up. When we are
      // done decoding (and thus can loop around back to the start of the
      // animation) we would then jump to a random point in the animation to
      // try to catch up. But we were never behind in the animation.
      mCurrentAnimationFrameTime = aTime;
      return ret;
    }

    // End of an animation loop...

    // If we are not looping forever, initialize the loop counter
    if (mLoopRemainingCount < 0 && LoopCount() >= 0) {
      mLoopRemainingCount = LoopCount();
    }

    // If animation mode is "loop once", or we're at end of loop counter,
    // it's time to stop animating.
    if (mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
        mLoopRemainingCount == 0) {
      ret.animationFinished = true;
    }

    nextFrameIndex = 0;

    if (mLoopRemainingCount > 0) {
      mLoopRemainingCount--;
    }

    // If we're done, exit early.
    if (ret.animationFinished) {
      return ret;
    }
  }

  // There can be frames in the surface cache with index >= mImage->GetNumFrames()
  // that GetRawFrame can access because the decoding thread has decoded them, but
  // RasterImage hasn't acknowledged those frames yet. We don't want to go past
  // what RasterImage knows about so that we stay in sync with RasterImage. The code
  // above should obey this, the MOZ_ASSERT records this invariant.
  MOZ_ASSERT(nextFrameIndex < mImage->GetNumFrames());
  RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);

  // If we're done decoding, we know we've got everything we're going to get.
  // If we aren't, we only display fully-downloaded frames; everything else
  // gets delayed.
  bool canDisplay = mDoneDecoding ||
                    (nextFrame && nextFrame->IsFinished());

  if (!canDisplay) {
    // Uh oh, the frame we want to show is currently being decoded (partial)
    // Wait until the next refresh driver tick and try again
    return ret;
  }

  // Bad data
  if (GetTimeoutForFrame(nextFrameIndex) < 0) {
    ret.animationFinished = true;
    ret.error = true;
  }

  if (nextFrameIndex == 0) {
    ret.dirtyRect = mFirstFrameRefreshArea;
  } else {
    MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);

    // Change frame
    if (!DoBlend(&ret.dirtyRect, currentFrameIndex, nextFrameIndex)) {
      // something went wrong, move on to next
      NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
      nextFrame->SetCompositingFailed(true);
      mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
      mCurrentAnimationFrameIndex = nextFrameIndex;

      ret.error = true;
      return ret;
    }

    nextFrame->SetCompositingFailed(false);
  }

  mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();

  // If we can get closer to the current time by a multiple of the image's loop
  // time, we should. We need to be done decoding in order to know the full loop
  // time though!
  int32_t loopTime = GetSingleLoopTime();
  if (loopTime > 0) {
    // We shouldn't be advancing by a whole loop unless we are decoded and know
    // what a full loop actually is. GetSingleLoopTime should return -1 so this
    // never happens.
    MOZ_ASSERT(mDoneDecoding);
    TimeDuration delay = aTime - mCurrentAnimationFrameTime;
    if (delay.ToMilliseconds() > loopTime) {
      // Explicitly use integer division to get the floor of the number of
      // loops.
      uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds()) / loopTime;
      mCurrentAnimationFrameTime +=
        TimeDuration::FromMilliseconds(loops * loopTime);
    }
  }

  // Set currentAnimationFrameIndex at the last possible moment
  mCurrentAnimationFrameIndex = nextFrameIndex;

  // If we're here, we successfully advanced the frame.
  ret.frameAdvanced = true;

  return ret;
}