tor-browser

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

DownscalingFilter.h (10756B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      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 /**
      8 * DownscalingSurfaceFilter is a SurfaceFilter implementation for use with
      9 * SurfacePipe which performs Lanczos downscaling.
     10 *
     11 * It's in this header file, separated from the other SurfaceFilters, because
     12 * some preprocessor magic is necessary to ensure that there aren't compilation
     13 * issues on platforms where Skia is unavailable.
     14 */
     15 
     16 #ifndef mozilla_image_DownscalingFilter_h
     17 #define mozilla_image_DownscalingFilter_h
     18 
     19 #include <algorithm>
     20 #include <ctime>
     21 #include <stdint.h>
     22 
     23 #include "mozilla/Maybe.h"
     24 #include "mozilla/UniquePtr.h"
     25 #include "mozilla/gfx/2D.h"
     26 
     27 #include "mozilla/gfx/ConvolutionFilter.h"
     28 
     29 #include "SurfacePipe.h"
     30 
     31 namespace mozilla {
     32 namespace image {
     33 
     34 //////////////////////////////////////////////////////////////////////////////
     35 // DownscalingFilter
     36 //////////////////////////////////////////////////////////////////////////////
     37 
     38 template <typename Next>
     39 class DownscalingFilter;
     40 
     41 /**
     42 * A configuration struct for DownscalingConfig.
     43 */
     44 struct DownscalingConfig {
     45  template <typename Next>
     46  using Filter = DownscalingFilter<Next>;
     47  gfx::IntSize mInputSize;     /// The size of the input image. We'll downscale
     48                               /// from this size to the input size of the next
     49                               /// SurfaceFilter in the chain.
     50  gfx::SurfaceFormat mFormat;  /// The pixel format - BGRA or BGRX. (BGRX has
     51                               /// slightly better performance.)
     52 };
     53 
     54 /**
     55 * DownscalingFilter performs Lanczos downscaling, taking image input data at
     56 * one size and outputting it rescaled to a different size.
     57 *
     58 * The 'Next' template parameter specifies the next filter in the chain.
     59 */
     60 template <typename Next>
     61 class DownscalingFilter final : public SurfaceFilter {
     62 public:
     63  DownscalingFilter()
     64      : mWindowCapacity(0),
     65        mRowsInWindow(0),
     66        mInputRow(0),
     67        mOutputRow(0),
     68        mFormat(gfx::SurfaceFormat::UNKNOWN) {}
     69 
     70  ~DownscalingFilter() { ReleaseWindow(); }
     71 
     72  template <typename... Rest>
     73  nsresult Configure(const DownscalingConfig& aConfig, const Rest&... aRest) {
     74    nsresult rv = mNext.Configure(aRest...);
     75    if (NS_FAILED(rv)) {
     76      return rv;
     77    }
     78 
     79    if (mNext.InputSize() == aConfig.mInputSize) {
     80      NS_WARNING("Created a downscaler, but not downscaling?");
     81      return NS_ERROR_INVALID_ARG;
     82    }
     83    if (mNext.InputSize().width > aConfig.mInputSize.width) {
     84      NS_WARNING("Created a downscaler, but width is larger");
     85      return NS_ERROR_INVALID_ARG;
     86    }
     87    if (mNext.InputSize().height > aConfig.mInputSize.height) {
     88      NS_WARNING("Created a downscaler, but height is larger");
     89      return NS_ERROR_INVALID_ARG;
     90    }
     91    if (aConfig.mInputSize.width <= 0 || aConfig.mInputSize.height <= 0) {
     92      NS_WARNING("Invalid input size for DownscalingFilter");
     93      return NS_ERROR_INVALID_ARG;
     94    }
     95 
     96    mInputSize = aConfig.mInputSize;
     97    gfx::IntSize outputSize = mNext.InputSize();
     98    mScale =
     99        gfx::MatrixScalesDouble(double(mInputSize.width) / outputSize.width,
    100                                double(mInputSize.height) / outputSize.height);
    101    mFormat = aConfig.mFormat;
    102 
    103    ReleaseWindow();
    104 
    105    auto resizeMethod = gfx::ConvolutionFilter::ResizeMethod::LANCZOS3;
    106    if (!mXFilter.ComputeResizeFilter(resizeMethod, mInputSize.width,
    107                                      outputSize.width) ||
    108        !mYFilter.ComputeResizeFilter(resizeMethod, mInputSize.height,
    109                                      outputSize.height)) {
    110      NS_WARNING("Failed to compute filters for image downscaling");
    111      return NS_ERROR_OUT_OF_MEMORY;
    112    }
    113 
    114    // Allocate the buffer, which contains scanlines of the input image.
    115    mRowBuffer.reset(new (fallible)
    116                         uint8_t[PaddedWidthInBytes(mInputSize.width)]);
    117    if (MOZ_UNLIKELY(!mRowBuffer)) {
    118      return NS_ERROR_OUT_OF_MEMORY;
    119    }
    120 
    121    // Clear the buffer to avoid writing uninitialized memory to the output.
    122    memset(mRowBuffer.get(), 0, PaddedWidthInBytes(mInputSize.width));
    123 
    124    // Allocate the window, which contains horizontally downscaled scanlines.
    125    // (We can store scanlines which are already downscaled because our
    126    // downscaling filter is separable.)
    127    mWindowCapacity = mYFilter.MaxFilter();
    128    mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
    129    if (MOZ_UNLIKELY(!mWindow)) {
    130      return NS_ERROR_OUT_OF_MEMORY;
    131    }
    132 
    133    // Allocate the "window" of recent rows that we keep in memory as input for
    134    // the downscaling code. We intentionally iterate through the entire array
    135    // even if an allocation fails, to ensure that all the pointers in it are
    136    // either valid or nullptr. That in turn ensures that ReleaseWindow() can
    137    // clean up correctly.
    138    bool anyAllocationFailed = false;
    139    const size_t windowRowSizeInBytes = PaddedWidthInBytes(outputSize.width);
    140    for (int32_t i = 0; i < mWindowCapacity; ++i) {
    141      mWindow[i] = new (fallible) uint8_t[windowRowSizeInBytes];
    142      anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
    143    }
    144 
    145    if (MOZ_UNLIKELY(anyAllocationFailed)) {
    146      return NS_ERROR_OUT_OF_MEMORY;
    147    }
    148 
    149    ConfigureFilter(mInputSize, sizeof(uint32_t));
    150    return NS_OK;
    151  }
    152 
    153  Maybe<SurfaceInvalidRect> TakeInvalidRect() override {
    154    Maybe<SurfaceInvalidRect> invalidRect = mNext.TakeInvalidRect();
    155 
    156    if (invalidRect) {
    157      // Compute the input space invalid rect by scaling.
    158      invalidRect->mInputSpaceRect.ScaleRoundOut(mScale.xScale, mScale.yScale);
    159    }
    160 
    161    return invalidRect;
    162  }
    163 
    164 protected:
    165  uint8_t* DoResetToFirstRow() override {
    166    mNext.ResetToFirstRow();
    167 
    168    mInputRow = 0;
    169    mOutputRow = 0;
    170    mRowsInWindow = 0;
    171 
    172    return GetRowPointer();
    173  }
    174 
    175  uint8_t* DoAdvanceRowFromBuffer(const uint8_t* aInputRow) override {
    176    if (mInputRow >= mInputSize.height) {
    177      NS_WARNING("Advancing DownscalingFilter past the end of the input");
    178      return nullptr;
    179    }
    180 
    181    if (mOutputRow >= mNext.InputSize().height) {
    182      NS_WARNING("Advancing DownscalingFilter past the end of the output");
    183      return nullptr;
    184    }
    185 
    186    int32_t filterOffset = 0;
    187    int32_t filterLength = 0;
    188    mYFilter.GetFilterOffsetAndLength(mOutputRow, &filterOffset, &filterLength);
    189 
    190    int32_t inputRowToRead = filterOffset + mRowsInWindow;
    191    MOZ_ASSERT(mInputRow <= inputRowToRead, "Reading past end of input");
    192    if (mInputRow == inputRowToRead) {
    193      MOZ_RELEASE_ASSERT(mRowsInWindow < mWindowCapacity,
    194                         "Need more rows than capacity!");
    195      mXFilter.ConvolveHorizontally(aInputRow, mWindow[mRowsInWindow++],
    196                                    mFormat);
    197    }
    198 
    199    MOZ_ASSERT(mOutputRow < mNext.InputSize().height,
    200               "Writing past end of output");
    201 
    202    while (mRowsInWindow >= filterLength) {
    203      DownscaleInputRow();
    204 
    205      if (mOutputRow == mNext.InputSize().height) {
    206        break;  // We're done.
    207      }
    208 
    209      mYFilter.GetFilterOffsetAndLength(mOutputRow, &filterOffset,
    210                                        &filterLength);
    211    }
    212 
    213    mInputRow++;
    214 
    215    return mInputRow < mInputSize.height ? GetRowPointer() : nullptr;
    216  }
    217 
    218  uint8_t* DoAdvanceRow() override {
    219    return DoAdvanceRowFromBuffer(mRowBuffer.get());
    220  }
    221 
    222 private:
    223  uint8_t* GetRowPointer() const { return mRowBuffer.get(); }
    224 
    225  static size_t PaddedWidthInBytes(size_t aLogicalWidth) {
    226    // Convert from width in BGRA/BGRX pixels to width in bytes, padding
    227    // to handle overreads by the SIMD code inside Skia.
    228    return gfx::ConvolutionFilter::PadBytesForSIMD(aLogicalWidth *
    229                                                   sizeof(uint32_t));
    230  }
    231 
    232  void DownscaleInputRow() {
    233    MOZ_ASSERT(mOutputRow < mNext.InputSize().height,
    234               "Writing past end of output");
    235 
    236    int32_t filterOffset = 0;
    237    int32_t filterLength = 0;
    238    mYFilter.GetFilterOffsetAndLength(mOutputRow, &filterOffset, &filterLength);
    239 
    240    mNext.template WriteUnsafeComputedRow<uint32_t>([&](uint32_t* aRow,
    241                                                        uint32_t aLength) {
    242      mYFilter.ConvolveVertically(mWindow.get(),
    243                                  reinterpret_cast<uint8_t*>(aRow), mOutputRow,
    244                                  mXFilter.NumValues(), mFormat);
    245    });
    246 
    247    mOutputRow++;
    248 
    249    if (mOutputRow == mNext.InputSize().height) {
    250      return;  // We're done.
    251    }
    252 
    253    int32_t newFilterOffset = 0;
    254    int32_t newFilterLength = 0;
    255    mYFilter.GetFilterOffsetAndLength(mOutputRow, &newFilterOffset,
    256                                      &newFilterLength);
    257 
    258    int diff = newFilterOffset - filterOffset;
    259    MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?");
    260 
    261    // Shift the buffer. We're just moving pointers here, so this is cheap.
    262    mRowsInWindow -= diff;
    263    mRowsInWindow = std::clamp(mRowsInWindow, 0, mWindowCapacity);
    264 
    265    // If we already have enough rows to satisfy the filter, there is no need
    266    // to swap as we won't be writing more before the next convolution.
    267    if (filterLength > mRowsInWindow) {
    268      for (int32_t i = 0; i < mRowsInWindow; ++i) {
    269        std::swap(mWindow[i], mWindow[filterLength - mRowsInWindow + i]);
    270      }
    271    }
    272  }
    273 
    274  void ReleaseWindow() {
    275    if (!mWindow) {
    276      return;
    277    }
    278 
    279    for (int32_t i = 0; i < mWindowCapacity; ++i) {
    280      delete[] mWindow[i];
    281    }
    282 
    283    mWindow = nullptr;
    284    mWindowCapacity = 0;
    285  }
    286 
    287  Next mNext;  /// The next SurfaceFilter in the chain.
    288 
    289  gfx::IntSize mInputSize;         /// The size of the input image.
    290  gfx::MatrixScalesDouble mScale;  /// The scale factors in each dimension.
    291                                   /// Computed from @mInputSize and
    292                                   /// the next filter's input size.
    293 
    294  UniquePtr<uint8_t[]> mRowBuffer;  /// The buffer into which input is written.
    295  UniquePtr<uint8_t*[]> mWindow;    /// The last few rows which were written.
    296 
    297  gfx::ConvolutionFilter mXFilter;  /// The Lanczos filter in X.
    298  gfx::ConvolutionFilter mYFilter;  /// The Lanczos filter in Y.
    299 
    300  int32_t mWindowCapacity;  /// How many rows the window contains.
    301 
    302  int32_t mRowsInWindow;  /// How many rows we've buffered in the window.
    303  int32_t mInputRow;      /// The current row we're reading. (0-indexed)
    304  int32_t mOutputRow;     /// The current row we're writing. (0-indexed)
    305 
    306  gfx::SurfaceFormat mFormat;  /// The image format
    307 };
    308 
    309 }  // namespace image
    310 }  // namespace mozilla
    311 
    312 #endif  // mozilla_image_DownscalingFilter_h