diff options
Diffstat (limited to 'image/Downscaler.cpp')
-rw-r--r-- | image/Downscaler.cpp | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/image/Downscaler.cpp b/image/Downscaler.cpp new file mode 100644 index 0000000000..5bf15f1469 --- /dev/null +++ b/image/Downscaler.cpp @@ -0,0 +1,302 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Downscaler.h" + +#include <algorithm> +#include <ctime> + +#include "mozilla/gfx/2D.h" + +using std::max; +using std::swap; + +namespace mozilla { + +using gfx::IntRect; + +namespace image { + +Downscaler::Downscaler(const nsIntSize& aTargetSize) + : mTargetSize(aTargetSize), + mOutputBuffer(nullptr), + mWindowCapacity(0), + mLinesInBuffer(0), + mPrevInvalidatedLine(0), + mCurrentOutLine(0), + mCurrentInLine(0), + mHasAlpha(true), + mFlipVertically(false) { + MOZ_ASSERT(mTargetSize.width > 0 && mTargetSize.height > 0, + "Invalid target size"); +} + +Downscaler::~Downscaler() { ReleaseWindow(); } + +void Downscaler::ReleaseWindow() { + if (!mWindow) { + return; + } + + for (int32_t i = 0; i < mWindowCapacity; ++i) { + delete[] mWindow[i]; + } + + mWindow = nullptr; + mWindowCapacity = 0; +} + +nsresult Downscaler::BeginFrame(const nsIntSize& aOriginalSize, + const Maybe<nsIntRect>& aFrameRect, + uint8_t* aOutputBuffer, bool aHasAlpha, + bool aFlipVertically /* = false */) { + MOZ_ASSERT(aOutputBuffer); + MOZ_ASSERT(mTargetSize != aOriginalSize, + "Created a downscaler, but not downscaling?"); + MOZ_ASSERT(mTargetSize.width <= aOriginalSize.width, + "Created a downscaler, but width is larger"); + MOZ_ASSERT(mTargetSize.height <= aOriginalSize.height, + "Created a downscaler, but height is larger"); + MOZ_ASSERT(aOriginalSize.width > 0 && aOriginalSize.height > 0, + "Invalid original size"); + + // Only downscale from reasonable sizes to avoid using too much memory/cpu + // downscaling and decoding. 1 << 20 == 1,048,576 seems a reasonable limit. + if (aOriginalSize.width > (1 << 20) || aOriginalSize.height > (1 << 20)) { + NS_WARNING("Trying to downscale image frame that is too large"); + return NS_ERROR_INVALID_ARG; + } + + mFrameRect = aFrameRect.valueOr(nsIntRect(nsIntPoint(), aOriginalSize)); + MOZ_ASSERT(mFrameRect.X() >= 0 && mFrameRect.Y() >= 0 && + mFrameRect.Width() >= 0 && mFrameRect.Height() >= 0, + "Frame rect must have non-negative components"); + MOZ_ASSERT(nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height) + .Contains(mFrameRect), + "Frame rect must fit inside image"); + MOZ_ASSERT_IF(!nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height) + .IsEqualEdges(mFrameRect), + aHasAlpha); + + mOriginalSize = aOriginalSize; + mScale = gfx::MatrixScalesDouble( + double(mOriginalSize.width) / mTargetSize.width, + double(mOriginalSize.height) / mTargetSize.height); + mOutputBuffer = aOutputBuffer; + mHasAlpha = aHasAlpha; + mFlipVertically = aFlipVertically; + + ReleaseWindow(); + + auto resizeMethod = gfx::ConvolutionFilter::ResizeMethod::LANCZOS3; + if (!mXFilter.ComputeResizeFilter(resizeMethod, mOriginalSize.width, + mTargetSize.width) || + !mYFilter.ComputeResizeFilter(resizeMethod, mOriginalSize.height, + mTargetSize.height)) { + NS_WARNING("Failed to compute filters for image downscaling"); + return NS_ERROR_OUT_OF_MEMORY; + } + + // Allocate the buffer, which contains scanlines of the original image. + // pad to handle overreads by the simd code + size_t bufferLen = gfx::ConvolutionFilter::PadBytesForSIMD( + mOriginalSize.width * sizeof(uint32_t)); + mRowBuffer.reset(new (fallible) uint8_t[bufferLen]); + if (MOZ_UNLIKELY(!mRowBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Zero buffer to keep valgrind happy. + memset(mRowBuffer.get(), 0, bufferLen); + + // Allocate the window, which contains horizontally downscaled scanlines. (We + // can store scanlines which are already downscale because our downscaling + // filter is separable.) + mWindowCapacity = mYFilter.MaxFilter(); + mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]); + if (MOZ_UNLIKELY(!mWindow)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + bool anyAllocationFailed = false; + // pad to handle overreads by the simd code + const size_t rowSize = gfx::ConvolutionFilter::PadBytesForSIMD( + mTargetSize.width * sizeof(uint32_t)); + for (int32_t i = 0; i < mWindowCapacity; ++i) { + mWindow[i] = new (fallible) uint8_t[rowSize]; + anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr; + } + + if (MOZ_UNLIKELY(anyAllocationFailed)) { + // We intentionally iterate through the entire array even if an allocation + // fails, to ensure that all the pointers in it are either valid or nullptr. + // That in turn ensures that ReleaseWindow() can clean up correctly. + return NS_ERROR_OUT_OF_MEMORY; + } + + ResetForNextProgressivePass(); + + return NS_OK; +} + +void Downscaler::SkipToRow(int32_t aRow) { + if (mCurrentInLine < aRow) { + ClearRow(); + do { + CommitRow(); + } while (mCurrentInLine < aRow); + } +} + +void Downscaler::ResetForNextProgressivePass() { + mPrevInvalidatedLine = 0; + mCurrentOutLine = 0; + mCurrentInLine = 0; + mLinesInBuffer = 0; + + if (mFrameRect.IsEmpty()) { + // Our frame rect is zero size; commit rows until the end of the image. + SkipToRow(mOriginalSize.height - 1); + } else { + // If we have a vertical offset, commit rows to shift us past it. + SkipToRow(mFrameRect.Y()); + } +} + +void Downscaler::ClearRestOfRow(uint32_t aStartingAtCol) { + MOZ_ASSERT(int64_t(aStartingAtCol) <= int64_t(mOriginalSize.width)); + uint32_t bytesToClear = + (mOriginalSize.width - aStartingAtCol) * sizeof(uint32_t); + memset(mRowBuffer.get() + (aStartingAtCol * sizeof(uint32_t)), 0, + bytesToClear); +} + +void Downscaler::CommitRow() { + MOZ_ASSERT(mOutputBuffer, "Should have a current frame"); + MOZ_ASSERT(mCurrentInLine < mOriginalSize.height, "Past end of input"); + + if (mCurrentOutLine < mTargetSize.height) { + int32_t filterOffset = 0; + int32_t filterLength = 0; + mYFilter.GetFilterOffsetAndLength(mCurrentOutLine, &filterOffset, + &filterLength); + + int32_t inLineToRead = filterOffset + mLinesInBuffer; + MOZ_ASSERT(mCurrentInLine <= inLineToRead, "Reading past end of input"); + if (mCurrentInLine == inLineToRead) { + MOZ_RELEASE_ASSERT(mLinesInBuffer < mWindowCapacity, + "Need more rows than capacity!"); + mXFilter.ConvolveHorizontally(mRowBuffer.get(), mWindow[mLinesInBuffer++], + mHasAlpha); + } + + MOZ_ASSERT(mCurrentOutLine < mTargetSize.height, + "Writing past end of output"); + + while (mLinesInBuffer >= filterLength) { + DownscaleInputLine(); + + if (mCurrentOutLine == mTargetSize.height) { + break; // We're done. + } + + mYFilter.GetFilterOffsetAndLength(mCurrentOutLine, &filterOffset, + &filterLength); + } + } + + mCurrentInLine += 1; + + // If we're at the end of the part of the original image that has data, commit + // rows to shift us to the end. + if (mCurrentInLine == (mFrameRect.Y() + mFrameRect.Height())) { + SkipToRow(mOriginalSize.height - 1); + } +} + +bool Downscaler::HasInvalidation() const { + return mCurrentOutLine > mPrevInvalidatedLine; +} + +DownscalerInvalidRect Downscaler::TakeInvalidRect() { + if (MOZ_UNLIKELY(!HasInvalidation())) { + return DownscalerInvalidRect(); + } + + DownscalerInvalidRect invalidRect; + + // Compute the target size invalid rect. + if (mFlipVertically) { + // We need to flip it. This will implicitly flip the original size invalid + // rect, since we compute it by scaling this rect. + invalidRect.mTargetSizeRect = + IntRect(0, mTargetSize.height - mCurrentOutLine, mTargetSize.width, + mCurrentOutLine - mPrevInvalidatedLine); + } else { + invalidRect.mTargetSizeRect = + IntRect(0, mPrevInvalidatedLine, mTargetSize.width, + mCurrentOutLine - mPrevInvalidatedLine); + } + + mPrevInvalidatedLine = mCurrentOutLine; + + // Compute the original size invalid rect. + invalidRect.mOriginalSizeRect = invalidRect.mTargetSizeRect; + invalidRect.mOriginalSizeRect.ScaleRoundOut(mScale.xScale, mScale.yScale); + + return invalidRect; +} + +void Downscaler::DownscaleInputLine() { + MOZ_ASSERT(mOutputBuffer); + MOZ_ASSERT(mCurrentOutLine < mTargetSize.height, + "Writing past end of output"); + + int32_t filterOffset = 0; + int32_t filterLength = 0; + mYFilter.GetFilterOffsetAndLength(mCurrentOutLine, &filterOffset, + &filterLength); + + int32_t currentOutLine = mFlipVertically + ? mTargetSize.height - (mCurrentOutLine + 1) + : mCurrentOutLine; + MOZ_ASSERT(currentOutLine >= 0); + + uint8_t* outputLine = + &mOutputBuffer[currentOutLine * mTargetSize.width * sizeof(uint32_t)]; + mYFilter.ConvolveVertically(mWindow.get(), outputLine, mCurrentOutLine, + mXFilter.NumValues(), mHasAlpha); + + mCurrentOutLine += 1; + + if (mCurrentOutLine == mTargetSize.height) { + // We're done. + return; + } + + int32_t newFilterOffset = 0; + int32_t newFilterLength = 0; + mYFilter.GetFilterOffsetAndLength(mCurrentOutLine, &newFilterOffset, + &newFilterLength); + + int diff = newFilterOffset - filterOffset; + MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?"); + + // Shift the buffer. We're just moving pointers here, so this is cheap. + mLinesInBuffer -= diff; + mLinesInBuffer = std::min(std::max(mLinesInBuffer, 0), mWindowCapacity); + + // If we already have enough rows to satisfy the filter, there is no need + // to swap as we won't be writing more before the next convolution. + if (filterLength > mLinesInBuffer) { + for (int32_t i = 0; i < mLinesInBuffer; ++i) { + swap(mWindow[i], mWindow[filterLength - mLinesInBuffer + i]); + } + } +} + +} // namespace image +} // namespace mozilla |