123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- /* -*- 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 "gfxPrefs.h"
- #include "image_operations.h"
- #include "mozilla/gfx/2D.h"
- #include "mozilla/SSE.h"
- #include "mozilla/mips.h"
- #include "convolver.h"
- #include "skia/include/core/SkTypes.h"
- using std::max;
- using std::swap;
- namespace mozilla {
- using gfx::IntRect;
- namespace image {
- Downscaler::Downscaler(const nsIntSize& aTargetSize)
- : mTargetSize(aTargetSize)
- , mOutputBuffer(nullptr)
- , mXFilter(MakeUnique<skia::ConvolutionFilter1D>())
- , mYFilter(MakeUnique<skia::ConvolutionFilter1D>())
- , mWindowCapacity(0)
- , mHasAlpha(true)
- , mFlipVertically(false)
- {
- MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(),
- "Downscaling even though downscale-during-decode is disabled?");
- 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 = gfxSize(double(mOriginalSize.width) / mTargetSize.width,
- double(mOriginalSize.height) / mTargetSize.height);
- mOutputBuffer = aOutputBuffer;
- mHasAlpha = aHasAlpha;
- mFlipVertically = aFlipVertically;
- ReleaseWindow();
- auto resizeMethod = skia::ImageOperations::RESIZE_LANCZOS3;
- skia::resize::ComputeFilters(resizeMethod,
- mOriginalSize.width, mTargetSize.width,
- 0, mTargetSize.width,
- mXFilter.get());
- if (mXFilter->max_filter() <= 0 || mXFilter->num_values() != mTargetSize.width) {
- NS_WARNING("Failed to compute filters for image downscaling");
- return NS_ERROR_OUT_OF_MEMORY;
- }
- skia::resize::ComputeFilters(resizeMethod,
- mOriginalSize.height, mTargetSize.height,
- 0, mTargetSize.height,
- mYFilter.get());
- if (mYFilter->max_filter() <= 0 || mYFilter->num_values() != 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 by 15 to handle overreads by the simd code
- size_t bufferLen = mOriginalSize.width * sizeof(uint32_t) + 15;
- 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->max_filter();
- mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
- if (MOZ_UNLIKELY(!mWindow)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- bool anyAllocationFailed = false;
- // pad by 15 to handle overreads by the simd code
- const int rowSize = mTargetSize.width * sizeof(uint32_t) + 15;
- 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);
- }
- }
- static void
- GetFilterOffsetAndLength(UniquePtr<skia::ConvolutionFilter1D>& aFilter,
- int32_t aOutputImagePosition,
- int32_t* aFilterOffsetOut,
- int32_t* aFilterLengthOut)
- {
- MOZ_ASSERT(aOutputImagePosition < aFilter->num_values());
- aFilter->FilterForValue(aOutputImagePosition,
- aFilterOffsetOut,
- aFilterLengthOut);
- }
- 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;
- GetFilterOffsetAndLength(mYFilter, 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!");
- skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter,
- mWindow[mLinesInBuffer++], mHasAlpha,
- supports_sse2() || supports_mmi());
- }
- MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
- "Writing past end of output");
- while (mLinesInBuffer >= filterLength) {
- DownscaleInputLine();
- if (mCurrentOutLine == mTargetSize.height) {
- break; // We're done.
- }
- GetFilterOffsetAndLength(mYFilter, 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.width, mScale.height);
- return invalidRect;
- }
- void
- Downscaler::DownscaleInputLine()
- {
- typedef skia::ConvolutionFilter1D::Fixed FilterValue;
- MOZ_ASSERT(mOutputBuffer);
- MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
- "Writing past end of output");
- int32_t filterOffset = 0;
- int32_t filterLength = 0;
- MOZ_ASSERT(mCurrentOutLine < mYFilter->num_values());
- auto filterValues =
- mYFilter->FilterForValue(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)];
- skia::ConvolveVertically(static_cast<const FilterValue*>(filterValues),
- filterLength, mWindow.get(), mXFilter->num_values(),
- outputLine, mHasAlpha, supports_sse2() || supports_mmi());
- mCurrentOutLine += 1;
- if (mCurrentOutLine == mTargetSize.height) {
- // We're done.
- return;
- }
- int32_t newFilterOffset = 0;
- int32_t newFilterLength = 0;
- GetFilterOffsetAndLength(mYFilter, 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 = min(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
|