DownscalingFilter.h 13 KB

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