diff --git a/image/SurfaceFilters.h b/image/SurfaceFilters.h index ab4339402161..d1315e117f97 100644 --- a/image/SurfaceFilters.h +++ b/image/SurfaceFilters.h @@ -20,6 +20,7 @@ #include "mozilla/Maybe.h" #include "mozilla/UniquePtr.h" #include "mozilla/gfx/2D.h" +#include "skia/src/core/SkBlitRow.h" #include "DownscalingFilter.h" #include "SurfaceCache.h" @@ -328,6 +329,379 @@ private: /// progressive display. }; +////////////////////////////////////////////////////////////////////////////// +// BlendAnimationFilter +////////////////////////////////////////////////////////////////////////////// + +template class BlendAnimationFilter; + +/** + * A configuration struct for BlendAnimationFilter. + */ +struct BlendAnimationConfig +{ + template using Filter = BlendAnimationFilter; + Decoder* mDecoder; /// The decoder producing the animation. +}; + +/** + * BlendAnimationFilter turns a partial image as part of an animation into a + * complete frame given its frame rect, blend method, and the base frame's + * data buffer, frame rect and disposal method. Any excess data caused by a + * frame rect not being contained by the output size will be discarded. + * + * The base frame is an already produced complete frame from the animation. + * It may be any previous frame depending on the disposal method, although + * most often it will be the immediate previous frame to the current we are + * generating. + * + * The 'Next' template parameter specifies the next filter in the chain. + */ +template +class BlendAnimationFilter final : public SurfaceFilter +{ +public: + BlendAnimationFilter() + : mRow(0) + , mRowLength(0) + , mOverProc(nullptr) + , mBaseFrameStartPtr(nullptr) + , mBaseFrameRowPtr(nullptr) + { } + + template + nsresult Configure(const BlendAnimationConfig& aConfig, const Rest&... aRest) + { + nsresult rv = mNext.Configure(aRest...); + if (NS_FAILED(rv)) { + return rv; + } + + if (!aConfig.mDecoder || !aConfig.mDecoder->ShouldBlendAnimation()) { + MOZ_ASSERT_UNREACHABLE("Expected image decoder that is blending!"); + return NS_ERROR_INVALID_ARG; + } + + imgFrame* currentFrame = aConfig.mDecoder->GetCurrentFrame(); + if (!currentFrame) { + MOZ_ASSERT_UNREACHABLE("Decoder must have current frame!"); + return NS_ERROR_FAILURE; + } + + mFrameRect = mUnclampedFrameRect = currentFrame->GetBlendRect(); + gfx::IntSize outputSize = mNext.InputSize(); + mRowLength = outputSize.width * sizeof(uint32_t); + + // Forbid frame rects with negative size. + if (mUnclampedFrameRect.width < 0 || mUnclampedFrameRect.height < 0) { + return NS_ERROR_FAILURE; + } + + // Clamp mFrameRect to the output size. + gfx::IntRect outputRect(0, 0, outputSize.width, outputSize.height); + mFrameRect = mFrameRect.Intersect(outputRect); + bool fullFrame = outputRect.IsEqualEdges(mFrameRect); + + // If there's no intersection, |mFrameRect| will be an empty rect positioned + // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is + // not what we want. Force it to (0, 0) in that case. + if (mFrameRect.IsEmpty()) { + mFrameRect.MoveTo(0, 0); + } + + BlendMethod blendMethod = currentFrame->GetBlendMethod(); + switch (blendMethod) { + default: + blendMethod = BlendMethod::SOURCE; + MOZ_FALLTHROUGH_ASSERT("Unexpected blend method!"); + case BlendMethod::SOURCE: + // Default, overwrites base frame data (if any) with new. + break; + case BlendMethod::OVER: + // OVER only has an impact on the output if we have new data to blend + // with. + if (mFrameRect.IsEmpty()) { + blendMethod = BlendMethod::SOURCE; + } + break; + } + + // Determine what we need to clear and what we need to copy. If this frame + // is a full frame and uses source blending, there is no need to consider + // the disposal method of the previous frame. + gfx::IntRect dirtyRect(outputRect); + if (!fullFrame || blendMethod != BlendMethod::SOURCE) { + const RawAccessFrameRef& restoreFrame = + aConfig.mDecoder->GetRestoreFrameRef(); + if (restoreFrame) { + MOZ_ASSERT(restoreFrame->GetImageSize() == outputSize); + MOZ_ASSERT(restoreFrame->IsFinished()); + + // We can safely use this pointer without holding a RawAccessFrameRef + // because the decoder will keep it alive for us. + mBaseFrameStartPtr = restoreFrame.Data(); + MOZ_ASSERT(mBaseFrameStartPtr); + + gfx::IntRect restoreBlendRect = restoreFrame->GetBoundedBlendRect(); + gfx::IntRect restoreDirtyRect = aConfig.mDecoder->GetRestoreDirtyRect(); + switch (restoreFrame->GetDisposalMethod()) { + default: + case DisposalMethod::RESTORE_PREVIOUS: + MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod"); + case DisposalMethod::NOT_SPECIFIED: + case DisposalMethod::KEEP: + dirtyRect = mFrameRect.Union(restoreDirtyRect); + break; + case DisposalMethod::CLEAR: + // We only need to clear if the rect is outside the frame rect (i.e. + // overwrites a non-overlapping area) or the blend method may cause + // us to combine old data and new. + if (!mFrameRect.Contains(restoreBlendRect) || + blendMethod == BlendMethod::OVER) { + mClearRect = restoreBlendRect; + } + + // If we are clearing the whole frame, we do not need to retain a + // reference to the base frame buffer. + if (outputRect.IsEqualEdges(mClearRect)) { + mBaseFrameStartPtr = nullptr; + } else { + dirtyRect = mFrameRect.Union(restoreDirtyRect).Union(mClearRect); + } + break; + } + } else if (!fullFrame) { + // This must be the first frame, clear everything. + mClearRect = outputRect; + } + } + + // The dirty rect, or delta between the current frame and the previous frame + // (chronologically, not necessarily the restore frame) is the last + // animation parameter we need to initialize the new frame with. + currentFrame->SetDirtyRect(dirtyRect); + + if (!mBaseFrameStartPtr) { + // Switch to SOURCE if no base frame to ensure we don't allocate an + // intermediate buffer below. OVER does nothing without the base frame + // data. + blendMethod = BlendMethod::SOURCE; + } + + // Skia provides arch-specific accelerated methods to perform blending. + // Note that this is an internal Skia API and may be prone to change, + // but we avoid the overhead of setting up Skia objects. + if (blendMethod == BlendMethod::OVER) { + mOverProc = SkBlitRow::Factory32(SkBlitRow::kSrcPixelAlpha_Flag32); + MOZ_ASSERT(mOverProc); + } + + // We don't need an intermediate buffer unless the unclamped frame rect + // width is larger than the clamped frame rect width. In that case, the + // caller will end up writing data that won't end up in the final image at + // all, and we'll need a buffer to give that data a place to go. + if (mFrameRect.width < mUnclampedFrameRect.width || mOverProc) { + mBuffer.reset(new (fallible) uint8_t[mUnclampedFrameRect.width * + sizeof(uint32_t)]); + if (MOZ_UNLIKELY(!mBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + memset(mBuffer.get(), 0, mUnclampedFrameRect.width * sizeof(uint32_t)); + } + + ConfigureFilter(mUnclampedFrameRect.Size(), sizeof(uint32_t)); + return NS_OK; + } + + Maybe TakeInvalidRect() override + { + return mNext.TakeInvalidRect(); + } + +protected: + uint8_t* DoResetToFirstRow() override + { + uint8_t* rowPtr = mNext.ResetToFirstRow(); + if (rowPtr == nullptr) { + mRow = mFrameRect.YMost(); + return nullptr; + } + + mRow = 0; + mBaseFrameRowPtr = mBaseFrameStartPtr; + + while (mRow < mFrameRect.y) { + WriteBaseFrameRow(); + AdvanceRowOutsideFrameRect(); + } + + // We're at the beginning of the frame rect now, so return if we're either + // ready for input or we're already done. + rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer(); + if (!mFrameRect.IsEmpty() || rowPtr == nullptr) { + // Note that the pointer we're returning is for the next row we're + // actually going to write to, but we may discard writes before that point + // if mRow < mFrameRect.y. + mRow = mUnclampedFrameRect.y; + WriteBaseFrameRow(); + return AdjustRowPointer(rowPtr); + } + + // We've finished the region specified by the frame rect, but the frame rect + // is empty, so we need to output the rest of the image immediately. Advance + // to the end of the next pipeline stage's buffer, outputting rows that are + // copied from the base frame and/or cleared. + WriteBaseFrameRowsUntilComplete(); + + mRow = mFrameRect.YMost(); + return nullptr; // We're done. + } + + uint8_t* DoAdvanceRow() override + { + uint8_t* rowPtr = nullptr; + + const int32_t currentRow = mRow; + mRow++; + + // The unclamped frame rect has a negative offset which means -y rows from + // the decoder need to be discarded before we advance properly. + if (currentRow >= 0 && mBaseFrameRowPtr) { + mBaseFrameRowPtr += mRowLength; + } + + if (currentRow < mFrameRect.y) { + // This row is outside of the frame rect, so just drop it on the floor. + rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer(); + return AdjustRowPointer(rowPtr); + } else if (NS_WARN_IF(currentRow >= mFrameRect.YMost())) { + return nullptr; + } + + // If we had to buffer, merge the data into the row. Otherwise we had the + // decoder write directly to the next stage's buffer. + if (mBuffer) { + int32_t width = mFrameRect.width; + uint32_t* dst = reinterpret_cast(mNext.CurrentRowPointer()); + uint32_t* src = reinterpret_cast(mBuffer.get()) - + std::min(mUnclampedFrameRect.x, 0); + dst += mFrameRect.x; + if (mOverProc) { + mOverProc(dst, src, width, 0xFF); + } else { + memcpy(dst, src, width * sizeof(uint32_t)); + } + rowPtr = mNext.AdvanceRow() ? mBuffer.get() : nullptr; + } else { + MOZ_ASSERT(!mOverProc); + rowPtr = mNext.AdvanceRow(); + } + + // If there's still more data coming or we're already done, just adjust the + // pointer and return. + if (mRow < mFrameRect.YMost() || rowPtr == nullptr) { + WriteBaseFrameRow(); + return AdjustRowPointer(rowPtr); + } + + // We've finished the region specified by the frame rect. Advance to the end + // of the next pipeline stage's buffer, outputting rows that are copied from + // the base frame and/or cleared. + WriteBaseFrameRowsUntilComplete(); + + return nullptr; // We're done. + } + +private: + void WriteBaseFrameRowsUntilComplete() + { + do { + WriteBaseFrameRow(); + } while (AdvanceRowOutsideFrameRect()); + } + + void WriteBaseFrameRow() + { + uint8_t* dest = mNext.CurrentRowPointer(); + if (!dest) { + return; + } + + if (!mBaseFrameRowPtr) { + // No base frame, so we are clearing everything. + memset(dest, 0, mRowLength); + } else if (mClearRect.height > 0 && + mClearRect.y <= mRow && + mClearRect.YMost() > mRow) { + // We have a base frame, but we are inside the area to be cleared. + // Only copy the data we need from the source. + size_t prefixLength = mClearRect.x * sizeof(uint32_t); + size_t clearLength = mClearRect.width * sizeof(uint32_t); + size_t postfixOffset = prefixLength + clearLength; + size_t postfixLength = mRowLength - postfixOffset; + MOZ_ASSERT(prefixLength + clearLength + postfixLength == mRowLength); + memcpy(dest, mBaseFrameRowPtr, prefixLength); + memset(dest + prefixLength, 0, clearLength); + memcpy(dest + postfixOffset, mBaseFrameRowPtr + postfixOffset, postfixLength); + } else { + memcpy(dest, mBaseFrameRowPtr, mRowLength); + } + } + + bool AdvanceRowOutsideFrameRect() + { + // The unclamped frame rect may have a negative offset however we should + // never be advancing the row via this path (otherwise mBaseFrameRowPtr + // will be wrong. + MOZ_ASSERT(mRow >= 0); + MOZ_ASSERT(mRow < mFrameRect.y || mRow >= mFrameRect.YMost()); + + mRow++; + if (mBaseFrameRowPtr) { + mBaseFrameRowPtr += mRowLength; + } + + return mNext.AdvanceRow() != nullptr; + } + + uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer) const + { + if (mBuffer) { + MOZ_ASSERT(aNextRowPointer == mBuffer.get() || aNextRowPointer == nullptr); + return aNextRowPointer; // No adjustment needed for an intermediate buffer. + } + + if (mFrameRect.IsEmpty() || + mRow >= mFrameRect.YMost() || + aNextRowPointer == nullptr) { + return nullptr; // Nothing left to write. + } + + MOZ_ASSERT(!mOverProc); + return aNextRowPointer + mFrameRect.x * sizeof(uint32_t); + } + + Next mNext; /// The next SurfaceFilter in the chain. + + gfx::IntRect mFrameRect; /// The surface subrect which contains data, + /// clamped to the image size. + gfx::IntRect mUnclampedFrameRect; /// The frame rect before clamping. + UniquePtr mBuffer; /// The intermediate buffer, if one is + /// necessary because the frame rect width + /// is larger than the image's logical width. + int32_t mRow; /// The row in unclamped frame rect space + /// that we're currently writing. + size_t mRowLength; /// Length in bytes of a row that is the input + /// for the next filter. + SkBlitRow::Proc32 mOverProc; /// Function pointer to perform over blending. + const uint8_t* mBaseFrameStartPtr; /// Starting row pointer to the base frame + /// data from which we copy pixel data from. + const uint8_t* mBaseFrameRowPtr; /// Current row pointer to the base frame + /// data. + gfx::IntRect mClearRect; /// The frame area to clear before blending + /// the current frame. +}; ////////////////////////////////////////////////////////////////////////////// // RemoveFrameRectFilter