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