tor-browser

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

Downscaler.cpp (10300B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 *
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "Downscaler.h"
      8 
      9 #include <algorithm>
     10 #include <ctime>
     11 
     12 #include "mozilla/gfx/2D.h"
     13 #include "mozilla/fallible.h"
     14 
     15 using std::swap;
     16 
     17 namespace mozilla {
     18 
     19 using gfx::IntRect;
     20 
     21 namespace image {
     22 
     23 Downscaler::Downscaler(const nsIntSize& aTargetSize)
     24    : mTargetSize(aTargetSize),
     25      mOutputBuffer(nullptr),
     26      mWindowCapacity(0),
     27      mLinesInBuffer(0),
     28      mPrevInvalidatedLine(0),
     29      mCurrentOutLine(0),
     30      mCurrentInLine(0),
     31      mFormat(gfx::SurfaceFormat::UNKNOWN),
     32      mFlipVertically(false) {
     33  MOZ_ASSERT(mTargetSize.width > 0 && mTargetSize.height > 0,
     34             "Invalid target size");
     35 }
     36 
     37 Downscaler::~Downscaler() { ReleaseWindow(); }
     38 
     39 void Downscaler::ReleaseWindow() {
     40  if (!mWindow) {
     41    return;
     42  }
     43 
     44  for (int32_t i = 0; i < mWindowCapacity; ++i) {
     45    delete[] mWindow[i];
     46  }
     47 
     48  mWindow = nullptr;
     49  mWindowCapacity = 0;
     50 }
     51 
     52 nsresult Downscaler::BeginFrame(const nsIntSize& aOriginalSize,
     53                                const Maybe<nsIntRect>& aFrameRect,
     54                                uint8_t* aOutputBuffer,
     55                                gfx::SurfaceFormat aFormat,
     56                                bool aFlipVertically /* = false */) {
     57  MOZ_ASSERT(aOutputBuffer);
     58  MOZ_ASSERT(mTargetSize != aOriginalSize,
     59             "Created a downscaler, but not downscaling?");
     60  MOZ_ASSERT(mTargetSize.width <= aOriginalSize.width,
     61             "Created a downscaler, but width is larger");
     62  MOZ_ASSERT(mTargetSize.height <= aOriginalSize.height,
     63             "Created a downscaler, but height is larger");
     64  MOZ_ASSERT(aOriginalSize.width > 0 && aOriginalSize.height > 0,
     65             "Invalid original size");
     66 
     67  // Only downscale from reasonable sizes to avoid using too much memory/cpu
     68  // downscaling and decoding. 1 << 20 == 1,048,576 seems a reasonable limit.
     69  if (aOriginalSize.width > (1 << 20) || aOriginalSize.height > (1 << 20)) {
     70    NS_WARNING("Trying to downscale image frame that is too large");
     71    return NS_ERROR_INVALID_ARG;
     72  }
     73 
     74  mFrameRect = aFrameRect.valueOr(nsIntRect(nsIntPoint(), aOriginalSize));
     75  MOZ_ASSERT(mFrameRect.X() >= 0 && mFrameRect.Y() >= 0 &&
     76                 mFrameRect.Width() >= 0 && mFrameRect.Height() >= 0,
     77             "Frame rect must have non-negative components");
     78  MOZ_ASSERT(nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
     79                 .Contains(mFrameRect),
     80             "Frame rect must fit inside image");
     81  MOZ_ASSERT_IF(!nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
     82                     .IsEqualEdges(mFrameRect),
     83                !gfx::IsOpaque(aFormat));
     84 
     85  mOriginalSize = aOriginalSize;
     86  mScale = gfx::MatrixScalesDouble(
     87      double(mOriginalSize.width) / mTargetSize.width,
     88      double(mOriginalSize.height) / mTargetSize.height);
     89  mOutputBuffer = aOutputBuffer;
     90  mFormat = aFormat;
     91  mFlipVertically = aFlipVertically;
     92 
     93  ReleaseWindow();
     94 
     95  auto resizeMethod = gfx::ConvolutionFilter::ResizeMethod::LANCZOS3;
     96  if (!mXFilter.ComputeResizeFilter(resizeMethod, mOriginalSize.width,
     97                                    mTargetSize.width) ||
     98      !mYFilter.ComputeResizeFilter(resizeMethod, mOriginalSize.height,
     99                                    mTargetSize.height)) {
    100    NS_WARNING("Failed to compute filters for image downscaling");
    101    return NS_ERROR_OUT_OF_MEMORY;
    102  }
    103 
    104  // Allocate the buffer, which contains scanlines of the original image.
    105  // pad to handle overreads by the simd code
    106  size_t bufferLen = gfx::ConvolutionFilter::PadBytesForSIMD(
    107      mOriginalSize.width * sizeof(uint32_t));
    108  mRowBuffer.reset(new (fallible) uint8_t[bufferLen]);
    109  if (MOZ_UNLIKELY(!mRowBuffer)) {
    110    return NS_ERROR_OUT_OF_MEMORY;
    111  }
    112 
    113  // Zero buffer to keep valgrind happy.
    114  memset(mRowBuffer.get(), 0, bufferLen);
    115 
    116  // Allocate the window, which contains horizontally downscaled scanlines. (We
    117  // can store scanlines which are already downscale because our downscaling
    118  // filter is separable.)
    119  mWindowCapacity = mYFilter.MaxFilter();
    120  mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
    121  if (MOZ_UNLIKELY(!mWindow)) {
    122    return NS_ERROR_OUT_OF_MEMORY;
    123  }
    124 
    125  bool anyAllocationFailed = false;
    126  // pad to handle overreads by the simd code
    127  const size_t rowSize = gfx::ConvolutionFilter::PadBytesForSIMD(
    128      mTargetSize.width * sizeof(uint32_t));
    129  for (int32_t i = 0; i < mWindowCapacity; ++i) {
    130    mWindow[i] = new (fallible) uint8_t[rowSize];
    131    anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
    132  }
    133 
    134  if (MOZ_UNLIKELY(anyAllocationFailed)) {
    135    // We intentionally iterate through the entire array even if an allocation
    136    // fails, to ensure that all the pointers in it are either valid or nullptr.
    137    // That in turn ensures that ReleaseWindow() can clean up correctly.
    138    return NS_ERROR_OUT_OF_MEMORY;
    139  }
    140 
    141  ResetForNextProgressivePass();
    142 
    143  return NS_OK;
    144 }
    145 
    146 void Downscaler::SkipToRow(int32_t aRow) {
    147  if (mCurrentInLine < aRow) {
    148    ClearRow();
    149    do {
    150      CommitRow();
    151    } while (mCurrentInLine < aRow);
    152  }
    153 }
    154 
    155 void Downscaler::ResetForNextProgressivePass() {
    156  mPrevInvalidatedLine = 0;
    157  mCurrentOutLine = 0;
    158  mCurrentInLine = 0;
    159  mLinesInBuffer = 0;
    160 
    161  if (mFrameRect.IsEmpty()) {
    162    // Our frame rect is zero size; commit rows until the end of the image.
    163    SkipToRow(mOriginalSize.height - 1);
    164  } else {
    165    // If we have a vertical offset, commit rows to shift us past it.
    166    SkipToRow(mFrameRect.Y());
    167  }
    168 }
    169 
    170 void Downscaler::ClearRestOfRow(uint32_t aStartingAtCol) {
    171  MOZ_ASSERT(int64_t(aStartingAtCol) <= int64_t(mOriginalSize.width));
    172  uint32_t bytesToClear =
    173      (mOriginalSize.width - aStartingAtCol) * sizeof(uint32_t);
    174  memset(mRowBuffer.get() + (aStartingAtCol * sizeof(uint32_t)), 0,
    175         bytesToClear);
    176 }
    177 
    178 void Downscaler::CommitRow() {
    179  MOZ_ASSERT(mOutputBuffer, "Should have a current frame");
    180  MOZ_ASSERT(mCurrentInLine < mOriginalSize.height, "Past end of input");
    181 
    182  if (mCurrentOutLine < mTargetSize.height) {
    183    int32_t filterOffset = 0;
    184    int32_t filterLength = 0;
    185    mYFilter.GetFilterOffsetAndLength(mCurrentOutLine, &filterOffset,
    186                                      &filterLength);
    187 
    188    int32_t inLineToRead = filterOffset + mLinesInBuffer;
    189    MOZ_ASSERT(mCurrentInLine <= inLineToRead, "Reading past end of input");
    190    if (mCurrentInLine == inLineToRead) {
    191      MOZ_RELEASE_ASSERT(mLinesInBuffer < mWindowCapacity,
    192                         "Need more rows than capacity!");
    193      mXFilter.ConvolveHorizontally(mRowBuffer.get(), mWindow[mLinesInBuffer++],
    194                                    mFormat);
    195    }
    196 
    197    MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
    198               "Writing past end of output");
    199 
    200    while (mLinesInBuffer >= filterLength) {
    201      DownscaleInputLine();
    202 
    203      if (mCurrentOutLine == mTargetSize.height) {
    204        break;  // We're done.
    205      }
    206 
    207      mYFilter.GetFilterOffsetAndLength(mCurrentOutLine, &filterOffset,
    208                                        &filterLength);
    209    }
    210  }
    211 
    212  mCurrentInLine += 1;
    213 
    214  // If we're at the end of the part of the original image that has data, commit
    215  // rows to shift us to the end.
    216  if (mCurrentInLine == (mFrameRect.Y() + mFrameRect.Height())) {
    217    SkipToRow(mOriginalSize.height - 1);
    218  }
    219 }
    220 
    221 bool Downscaler::HasInvalidation() const {
    222  return mCurrentOutLine > mPrevInvalidatedLine;
    223 }
    224 
    225 DownscalerInvalidRect Downscaler::TakeInvalidRect() {
    226  if (MOZ_UNLIKELY(!HasInvalidation())) {
    227    return DownscalerInvalidRect();
    228  }
    229 
    230  DownscalerInvalidRect invalidRect;
    231 
    232  // Compute the target size invalid rect.
    233  if (mFlipVertically) {
    234    // We need to flip it. This will implicitly flip the original size invalid
    235    // rect, since we compute it by scaling this rect.
    236    invalidRect.mTargetSizeRect =
    237        IntRect(0, mTargetSize.height - mCurrentOutLine, mTargetSize.width,
    238                mCurrentOutLine - mPrevInvalidatedLine);
    239  } else {
    240    invalidRect.mTargetSizeRect =
    241        IntRect(0, mPrevInvalidatedLine, mTargetSize.width,
    242                mCurrentOutLine - mPrevInvalidatedLine);
    243  }
    244 
    245  mPrevInvalidatedLine = mCurrentOutLine;
    246 
    247  // Compute the original size invalid rect.
    248  invalidRect.mOriginalSizeRect = invalidRect.mTargetSizeRect;
    249  invalidRect.mOriginalSizeRect.ScaleRoundOut(mScale.xScale, mScale.yScale);
    250 
    251  return invalidRect;
    252 }
    253 
    254 void Downscaler::DownscaleInputLine() {
    255  MOZ_ASSERT(mOutputBuffer);
    256  MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
    257             "Writing past end of output");
    258 
    259  int32_t filterOffset = 0;
    260  int32_t filterLength = 0;
    261  mYFilter.GetFilterOffsetAndLength(mCurrentOutLine, &filterOffset,
    262                                    &filterLength);
    263 
    264  int32_t currentOutLine = mFlipVertically
    265                               ? mTargetSize.height - (mCurrentOutLine + 1)
    266                               : mCurrentOutLine;
    267  MOZ_ASSERT(currentOutLine >= 0);
    268 
    269  uint8_t* outputLine =
    270      &mOutputBuffer[currentOutLine * mTargetSize.width * sizeof(uint32_t)];
    271  mYFilter.ConvolveVertically(mWindow.get(), outputLine, mCurrentOutLine,
    272                              mXFilter.NumValues(), mFormat);
    273 
    274  mCurrentOutLine += 1;
    275 
    276  if (mCurrentOutLine == mTargetSize.height) {
    277    // We're done.
    278    return;
    279  }
    280 
    281  int32_t newFilterOffset = 0;
    282  int32_t newFilterLength = 0;
    283  mYFilter.GetFilterOffsetAndLength(mCurrentOutLine, &newFilterOffset,
    284                                    &newFilterLength);
    285 
    286  int diff = newFilterOffset - filterOffset;
    287  MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?");
    288 
    289  // Shift the buffer. We're just moving pointers here, so this is cheap.
    290  mLinesInBuffer -= diff;
    291  mLinesInBuffer = std::clamp(mLinesInBuffer, 0, mWindowCapacity);
    292 
    293  // If we already have enough rows to satisfy the filter, there is no need
    294  // to swap as we won't be writing more before the next convolution.
    295  if (filterLength > mLinesInBuffer) {
    296    for (int32_t i = 0; i < mLinesInBuffer; ++i) {
    297      swap(mWindow[i], mWindow[filterLength - mLinesInBuffer + i]);
    298    }
    299  }
    300 }
    301 
    302 }  // namespace image
    303 }  // namespace mozilla