LCOV - code coverage report
Current view: top level - image - DownscalingFilter.h (source / functions) Hit Total Coverage
Test: output.info Lines: 0 121 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 12 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : #include "gfxPrefs.h"
      27             : 
      28             : #ifdef MOZ_ENABLE_SKIA
      29             : #include "mozilla/gfx/ConvolutionFilter.h"
      30             : #endif
      31             : 
      32             : #include "SurfacePipe.h"
      33             : 
      34             : namespace mozilla {
      35             : namespace image {
      36             : 
      37             : //////////////////////////////////////////////////////////////////////////////
      38             : // DownscalingFilter
      39             : //////////////////////////////////////////////////////////////////////////////
      40             : 
      41             : template <typename Next> class DownscalingFilter;
      42             : 
      43             : /**
      44             :  * A configuration struct for DownscalingConfig.
      45             :  */
      46             : struct DownscalingConfig
      47             : {
      48             :   template <typename Next> using Filter = DownscalingFilter<Next>;
      49             :   gfx::IntSize mInputSize;     /// The size of the input image. We'll downscale
      50             :                                /// from this size to the input size of the next
      51             :                                /// SurfaceFilter in the chain.
      52             :   gfx::SurfaceFormat mFormat;  /// The pixel format - BGRA or BGRX. (BGRX has
      53             :                                /// slightly better performance.)
      54             : };
      55             : 
      56             : #ifndef MOZ_ENABLE_SKIA
      57             : 
      58             : /**
      59             :  * DownscalingFilter requires Skia. This is a fallback implementation for
      60             :  * non-Skia builds that fails when Configure() is called (which will prevent
      61             :  * SurfacePipeFactory from returning an instance of it) and crashes if a caller
      62             :  * manually constructs an instance and attempts to actually use it. Callers
      63             :  * should avoid this by ensuring that they do not request downscaling in
      64             :  * non-Skia builds.
      65             :  */
      66             : template <typename Next>
      67             : class DownscalingFilter final : public SurfaceFilter
      68             : {
      69             : public:
      70             :   Maybe<SurfaceInvalidRect> TakeInvalidRect() override { return Nothing(); }
      71             : 
      72             :   template <typename... Rest>
      73             :   nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest)
      74             :   {
      75             :     return NS_ERROR_FAILURE;
      76             :   }
      77             : 
      78             : protected:
      79             :   uint8_t* DoResetToFirstRow() override { MOZ_CRASH(); return nullptr; }
      80             :   uint8_t* DoAdvanceRow() override { MOZ_CRASH(); return nullptr; }
      81             : };
      82             : 
      83             : #else
      84             : 
      85             : /**
      86             :  * DownscalingFilter performs Lanczos downscaling, taking image input data at one size
      87             :  * and outputting it rescaled to a different size.
      88             :  *
      89             :  * The 'Next' template parameter specifies the next filter in the chain.
      90             :  */
      91             : template <typename Next>
      92             : class DownscalingFilter final : public SurfaceFilter
      93             : {
      94             : public:
      95           0 :   DownscalingFilter()
      96             :     : mWindowCapacity(0)
      97             :     , mRowsInWindow(0)
      98             :     , mInputRow(0)
      99             :     , mOutputRow(0)
     100           0 :     , mHasAlpha(true)
     101             :   {
     102           0 :     MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(),
     103             :                "Downscaling even though downscale-during-decode is disabled?");
     104           0 :   }
     105             : 
     106           0 :   ~DownscalingFilter()
     107             :   {
     108           0 :     ReleaseWindow();
     109           0 :   }
     110             : 
     111             :   template <typename... Rest>
     112           0 :   nsresult Configure(const DownscalingConfig& aConfig, Rest... aRest)
     113             :   {
     114           0 :     nsresult rv = mNext.Configure(aRest...);
     115           0 :     if (NS_FAILED(rv)) {
     116           0 :       return rv;
     117             :     }
     118             : 
     119           0 :     if (mNext.IsValidPalettedPipe()) {
     120           0 :       NS_WARNING("Created a downscaler for a paletted surface?");
     121           0 :       return NS_ERROR_INVALID_ARG;
     122             :     }
     123           0 :     if (mNext.InputSize() == aConfig.mInputSize) {
     124           0 :       NS_WARNING("Created a downscaler, but not downscaling?");
     125           0 :       return NS_ERROR_INVALID_ARG;
     126             :     }
     127           0 :     if (mNext.InputSize().width > aConfig.mInputSize.width) {
     128           0 :       NS_WARNING("Created a downscaler, but width is larger");
     129           0 :       return NS_ERROR_INVALID_ARG;
     130             :     }
     131           0 :     if (mNext.InputSize().height > aConfig.mInputSize.height) {
     132           0 :       NS_WARNING("Created a downscaler, but height is larger");
     133           0 :       return NS_ERROR_INVALID_ARG;
     134             :     }
     135           0 :     if (aConfig.mInputSize.width <= 0 || aConfig.mInputSize.height <= 0) {
     136           0 :       NS_WARNING("Invalid input size for DownscalingFilter");
     137           0 :       return NS_ERROR_INVALID_ARG;
     138             :     }
     139             : 
     140           0 :     mInputSize = aConfig.mInputSize;
     141           0 :     gfx::IntSize outputSize = mNext.InputSize();
     142           0 :     mScale = gfxSize(double(mInputSize.width) / outputSize.width,
     143           0 :                      double(mInputSize.height) / outputSize.height);
     144           0 :     mHasAlpha = aConfig.mFormat == gfx::SurfaceFormat::B8G8R8A8;
     145             : 
     146           0 :     ReleaseWindow();
     147             : 
     148           0 :     auto resizeMethod = gfx::ConvolutionFilter::ResizeMethod::LANCZOS3;
     149           0 :     if (!mXFilter.ComputeResizeFilter(resizeMethod, mInputSize.width, outputSize.width) ||
     150           0 :         !mYFilter.ComputeResizeFilter(resizeMethod, mInputSize.height, outputSize.height)) {
     151           0 :       NS_WARNING("Failed to compute filters for image downscaling");
     152           0 :       return NS_ERROR_OUT_OF_MEMORY;
     153             :     }
     154             : 
     155             :     // Allocate the buffer, which contains scanlines of the input image.
     156           0 :     mRowBuffer.reset(new (fallible)
     157           0 :                        uint8_t[PaddedWidthInBytes(mInputSize.width)]);
     158           0 :     if (MOZ_UNLIKELY(!mRowBuffer)) {
     159           0 :       return NS_ERROR_OUT_OF_MEMORY;
     160             :     }
     161             : 
     162             :     // Clear the buffer to avoid writing uninitialized memory to the output.
     163           0 :     memset(mRowBuffer.get(), 0, PaddedWidthInBytes(mInputSize.width));
     164             : 
     165             :     // Allocate the window, which contains horizontally downscaled scanlines. (We
     166             :     // can store scanlines which are already downscaled because our downscaling
     167             :     // filter is separable.)
     168           0 :     mWindowCapacity = mYFilter.MaxFilter();
     169           0 :     mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
     170           0 :     if (MOZ_UNLIKELY(!mWindow)) {
     171           0 :       return NS_ERROR_OUT_OF_MEMORY;
     172             :     }
     173             : 
     174             :     // Allocate the "window" of recent rows that we keep in memory as input for
     175             :     // the downscaling code. We intentionally iterate through the entire array
     176             :     // even if an allocation fails, to ensure that all the pointers in it are
     177             :     // either valid or nullptr. That in turn ensures that ReleaseWindow() can
     178             :     // clean up correctly.
     179           0 :     bool anyAllocationFailed = false;
     180           0 :     const size_t windowRowSizeInBytes = PaddedWidthInBytes(outputSize.width);
     181           0 :     for (int32_t i = 0; i < mWindowCapacity; ++i) {
     182           0 :       mWindow[i] = new (fallible) uint8_t[windowRowSizeInBytes];
     183           0 :       anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
     184             :     }
     185             : 
     186           0 :     if (MOZ_UNLIKELY(anyAllocationFailed)) {
     187           0 :       return NS_ERROR_OUT_OF_MEMORY;
     188             :     }
     189             : 
     190           0 :     ConfigureFilter(mInputSize, sizeof(uint32_t));
     191           0 :     return NS_OK;
     192             :   }
     193             : 
     194           0 :   Maybe<SurfaceInvalidRect> TakeInvalidRect() override
     195             :   {
     196           0 :     Maybe<SurfaceInvalidRect> invalidRect = mNext.TakeInvalidRect();
     197             : 
     198           0 :     if (invalidRect) {
     199             :       // Compute the input space invalid rect by scaling.
     200           0 :       invalidRect->mInputSpaceRect.ScaleRoundOut(mScale.width, mScale.height);
     201             :     }
     202             : 
     203           0 :     return invalidRect;
     204             :   }
     205             : 
     206             : protected:
     207           0 :   uint8_t* DoResetToFirstRow() override
     208             :   {
     209           0 :     mNext.ResetToFirstRow();
     210             : 
     211           0 :     mInputRow = 0;
     212           0 :     mOutputRow = 0;
     213           0 :     mRowsInWindow = 0;
     214             : 
     215           0 :     return GetRowPointer();
     216             :   }
     217             : 
     218           0 :   uint8_t* DoAdvanceRow() override
     219             :   {
     220           0 :     if (mInputRow >= mInputSize.height) {
     221           0 :       NS_WARNING("Advancing DownscalingFilter past the end of the input");
     222           0 :       return nullptr;
     223             :     }
     224             : 
     225           0 :     if (mOutputRow >= mNext.InputSize().height) {
     226           0 :       NS_WARNING("Advancing DownscalingFilter past the end of the output");
     227           0 :       return nullptr;
     228             :     }
     229             : 
     230           0 :     int32_t filterOffset = 0;
     231           0 :     int32_t filterLength = 0;
     232           0 :     mYFilter.GetFilterOffsetAndLength(mOutputRow,
     233             :                                       &filterOffset, &filterLength);
     234             : 
     235           0 :     int32_t inputRowToRead = filterOffset + mRowsInWindow;
     236           0 :     MOZ_ASSERT(mInputRow <= inputRowToRead, "Reading past end of input");
     237           0 :     if (mInputRow == inputRowToRead) {
     238           0 :       mXFilter.ConvolveHorizontally(mRowBuffer.get(), mWindow[mRowsInWindow++], mHasAlpha);
     239             :     }
     240             : 
     241           0 :     MOZ_ASSERT(mOutputRow < mNext.InputSize().height,
     242             :                "Writing past end of output");
     243             : 
     244           0 :     while (mRowsInWindow == filterLength) {
     245           0 :       DownscaleInputRow();
     246             : 
     247           0 :       if (mOutputRow == mNext.InputSize().height) {
     248           0 :         break;  // We're done.
     249             :       }
     250             : 
     251           0 :       mYFilter.GetFilterOffsetAndLength(mOutputRow,
     252             :                                         &filterOffset, &filterLength);
     253             :     }
     254             : 
     255           0 :     mInputRow++;
     256             : 
     257           0 :     return mInputRow < mInputSize.height ? GetRowPointer()
     258           0 :                                          : nullptr;
     259             :   }
     260             : 
     261             : private:
     262           0 :   uint8_t* GetRowPointer() const { return mRowBuffer.get(); }
     263             : 
     264           0 :   static size_t PaddedWidthInBytes(size_t aLogicalWidth)
     265             :   {
     266             :     // Convert from width in BGRA/BGRX pixels to width in bytes, padding
     267             :     // to handle overreads by the SIMD code inside Skia.
     268           0 :     return gfx::ConvolutionFilter::PadBytesForSIMD(aLogicalWidth * sizeof(uint32_t));
     269             :   }
     270             : 
     271           0 :   void DownscaleInputRow()
     272             :   {
     273           0 :     MOZ_ASSERT(mOutputRow < mNext.InputSize().height,
     274             :                "Writing past end of output");
     275             : 
     276           0 :     int32_t filterOffset = 0;
     277           0 :     int32_t filterLength = 0;
     278           0 :     mYFilter.GetFilterOffsetAndLength(mOutputRow,
     279             :                                       &filterOffset, &filterLength);
     280             : 
     281           0 :     mNext.template WriteUnsafeComputedRow<uint32_t>([&](uint32_t* aRow,
     282           0 :                                                         uint32_t aLength) {
     283           0 :       mYFilter.ConvolveVertically(mWindow.get(), reinterpret_cast<uint8_t*>(aRow),
     284           0 :                                   mOutputRow, mXFilter.NumValues(), mHasAlpha);
     285           0 :     });
     286             : 
     287           0 :     mOutputRow++;
     288             : 
     289           0 :     if (mOutputRow == mNext.InputSize().height) {
     290           0 :       return;  // We're done.
     291             :     }
     292             : 
     293           0 :     int32_t newFilterOffset = 0;
     294           0 :     int32_t newFilterLength = 0;
     295           0 :     mYFilter.GetFilterOffsetAndLength(mOutputRow,
     296             :                                       &newFilterOffset, &newFilterLength);
     297             : 
     298           0 :     int diff = newFilterOffset - filterOffset;
     299           0 :     MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?");
     300             : 
     301             :     // Shift the buffer. We're just moving pointers here, so this is cheap.
     302           0 :     mRowsInWindow -= diff;
     303           0 :     mRowsInWindow = std::max(mRowsInWindow, 0);
     304           0 :     for (int32_t i = 0; i < mRowsInWindow; ++i) {
     305           0 :       std::swap(mWindow[i], mWindow[filterLength - mRowsInWindow + i]);
     306             :     }
     307             :   }
     308             : 
     309           0 :   void ReleaseWindow()
     310             :   {
     311           0 :     if (!mWindow) {
     312           0 :       return;
     313             :     }
     314             : 
     315           0 :     for (int32_t i = 0; i < mWindowCapacity; ++i) {
     316           0 :       delete[] mWindow[i];
     317             :     }
     318             : 
     319           0 :     mWindow = nullptr;
     320           0 :     mWindowCapacity = 0;
     321             :   }
     322             : 
     323             :   Next mNext;                       /// The next SurfaceFilter in the chain.
     324             : 
     325             :   gfx::IntSize mInputSize;          /// The size of the input image.
     326             :   gfxSize mScale;                   /// The scale factors in each dimension.
     327             :                                     /// Computed from @mInputSize and
     328             :                                     /// the next filter's input size.
     329             : 
     330             :   UniquePtr<uint8_t[]> mRowBuffer;  /// The buffer into which input is written.
     331             :   UniquePtr<uint8_t*[]> mWindow;    /// The last few rows which were written.
     332             : 
     333             :   gfx::ConvolutionFilter mXFilter;  /// The Lanczos filter in X.
     334             :   gfx::ConvolutionFilter mYFilter;  /// The Lanczos filter in Y.
     335             : 
     336             :   int32_t mWindowCapacity;  /// How many rows the window contains.
     337             : 
     338             :   int32_t mRowsInWindow;    /// How many rows we've buffered in the window.
     339             :   int32_t mInputRow;        /// The current row we're reading. (0-indexed)
     340             :   int32_t mOutputRow;       /// The current row we're writing. (0-indexed)
     341             : 
     342             :   bool mHasAlpha;           /// If true, the image has transparency.
     343             : };
     344             : 
     345             : #endif
     346             : 
     347             : } // namespace image
     348             : } // namespace mozilla
     349             : 
     350             : #endif // mozilla_image_DownscalingFilter_h

Generated by: LCOV version 1.13