Downscaler.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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. #include "Downscaler.h"
  7. #include <algorithm>
  8. #include <ctime>
  9. #include "gfxPrefs.h"
  10. #include "image_operations.h"
  11. #include "mozilla/gfx/2D.h"
  12. #include "mozilla/SSE.h"
  13. #include "mozilla/mips.h"
  14. #include "convolver.h"
  15. #include "skia/include/core/SkTypes.h"
  16. using std::max;
  17. using std::swap;
  18. namespace mozilla {
  19. using gfx::IntRect;
  20. namespace image {
  21. Downscaler::Downscaler(const nsIntSize& aTargetSize)
  22. : mTargetSize(aTargetSize)
  23. , mOutputBuffer(nullptr)
  24. , mXFilter(MakeUnique<skia::ConvolutionFilter1D>())
  25. , mYFilter(MakeUnique<skia::ConvolutionFilter1D>())
  26. , mWindowCapacity(0)
  27. , mHasAlpha(true)
  28. , mFlipVertically(false)
  29. {
  30. MOZ_ASSERT(gfxPrefs::ImageDownscaleDuringDecodeEnabled(),
  31. "Downscaling even though downscale-during-decode is disabled?");
  32. MOZ_ASSERT(mTargetSize.width > 0 && mTargetSize.height > 0,
  33. "Invalid target size");
  34. }
  35. Downscaler::~Downscaler()
  36. {
  37. ReleaseWindow();
  38. }
  39. void
  40. Downscaler::ReleaseWindow()
  41. {
  42. if (!mWindow) {
  43. return;
  44. }
  45. for (int32_t i = 0; i < mWindowCapacity; ++i) {
  46. delete[] mWindow[i];
  47. }
  48. mWindow = nullptr;
  49. mWindowCapacity = 0;
  50. }
  51. nsresult
  52. Downscaler::BeginFrame(const nsIntSize& aOriginalSize,
  53. const Maybe<nsIntRect>& aFrameRect,
  54. uint8_t* aOutputBuffer,
  55. bool aHasAlpha,
  56. bool aFlipVertically /* = false */)
  57. {
  58. MOZ_ASSERT(aOutputBuffer);
  59. MOZ_ASSERT(mTargetSize != aOriginalSize,
  60. "Created a downscaler, but not downscaling?");
  61. MOZ_ASSERT(mTargetSize.width <= aOriginalSize.width,
  62. "Created a downscaler, but width is larger");
  63. MOZ_ASSERT(mTargetSize.height <= aOriginalSize.height,
  64. "Created a downscaler, but height is larger");
  65. MOZ_ASSERT(aOriginalSize.width > 0 && aOriginalSize.height > 0,
  66. "Invalid original size");
  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. mFrameRect = aFrameRect.valueOr(nsIntRect(nsIntPoint(), aOriginalSize));
  74. MOZ_ASSERT(mFrameRect.x >= 0 && mFrameRect.y >= 0 &&
  75. mFrameRect.width >= 0 && mFrameRect.height >= 0,
  76. "Frame rect must have non-negative components");
  77. MOZ_ASSERT(nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
  78. .Contains(mFrameRect),
  79. "Frame rect must fit inside image");
  80. MOZ_ASSERT_IF(!nsIntRect(0, 0, aOriginalSize.width, aOriginalSize.height)
  81. .IsEqualEdges(mFrameRect),
  82. aHasAlpha);
  83. mOriginalSize = aOriginalSize;
  84. mScale = gfxSize(double(mOriginalSize.width) / mTargetSize.width,
  85. double(mOriginalSize.height) / mTargetSize.height);
  86. mOutputBuffer = aOutputBuffer;
  87. mHasAlpha = aHasAlpha;
  88. mFlipVertically = aFlipVertically;
  89. ReleaseWindow();
  90. auto resizeMethod = skia::ImageOperations::RESIZE_LANCZOS3;
  91. skia::resize::ComputeFilters(resizeMethod,
  92. mOriginalSize.width, mTargetSize.width,
  93. 0, mTargetSize.width,
  94. mXFilter.get());
  95. if (mXFilter->max_filter() <= 0 || mXFilter->num_values() != mTargetSize.width) {
  96. NS_WARNING("Failed to compute filters for image downscaling");
  97. return NS_ERROR_OUT_OF_MEMORY;
  98. }
  99. skia::resize::ComputeFilters(resizeMethod,
  100. mOriginalSize.height, mTargetSize.height,
  101. 0, mTargetSize.height,
  102. mYFilter.get());
  103. if (mYFilter->max_filter() <= 0 || mYFilter->num_values() != mTargetSize.height) {
  104. NS_WARNING("Failed to compute filters for image downscaling");
  105. return NS_ERROR_OUT_OF_MEMORY;
  106. }
  107. // Allocate the buffer, which contains scanlines of the original image.
  108. // pad by 15 to handle overreads by the simd code
  109. size_t bufferLen = mOriginalSize.width * sizeof(uint32_t) + 15;
  110. mRowBuffer.reset(new (fallible) uint8_t[bufferLen]);
  111. if (MOZ_UNLIKELY(!mRowBuffer)) {
  112. return NS_ERROR_OUT_OF_MEMORY;
  113. }
  114. // Zero buffer to keep valgrind happy.
  115. memset(mRowBuffer.get(), 0, bufferLen);
  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->max_filter();
  120. mWindow.reset(new (fallible) uint8_t*[mWindowCapacity]);
  121. if (MOZ_UNLIKELY(!mWindow)) {
  122. return NS_ERROR_OUT_OF_MEMORY;
  123. }
  124. bool anyAllocationFailed = false;
  125. // pad by 15 to handle overreads by the simd code
  126. const int rowSize = mTargetSize.width * sizeof(uint32_t) + 15;
  127. for (int32_t i = 0; i < mWindowCapacity; ++i) {
  128. mWindow[i] = new (fallible) uint8_t[rowSize];
  129. anyAllocationFailed = anyAllocationFailed || mWindow[i] == nullptr;
  130. }
  131. if (MOZ_UNLIKELY(anyAllocationFailed)) {
  132. // We intentionally iterate through the entire array even if an allocation
  133. // fails, to ensure that all the pointers in it are either valid or nullptr.
  134. // That in turn ensures that ReleaseWindow() can clean up correctly.
  135. return NS_ERROR_OUT_OF_MEMORY;
  136. }
  137. ResetForNextProgressivePass();
  138. return NS_OK;
  139. }
  140. void
  141. Downscaler::SkipToRow(int32_t aRow)
  142. {
  143. if (mCurrentInLine < aRow) {
  144. ClearRow();
  145. do {
  146. CommitRow();
  147. } while (mCurrentInLine < aRow);
  148. }
  149. }
  150. void
  151. Downscaler::ResetForNextProgressivePass()
  152. {
  153. mPrevInvalidatedLine = 0;
  154. mCurrentOutLine = 0;
  155. mCurrentInLine = 0;
  156. mLinesInBuffer = 0;
  157. if (mFrameRect.IsEmpty()) {
  158. // Our frame rect is zero size; commit rows until the end of the image.
  159. SkipToRow(mOriginalSize.height - 1);
  160. } else {
  161. // If we have a vertical offset, commit rows to shift us past it.
  162. SkipToRow(mFrameRect.y);
  163. }
  164. }
  165. static void
  166. GetFilterOffsetAndLength(UniquePtr<skia::ConvolutionFilter1D>& aFilter,
  167. int32_t aOutputImagePosition,
  168. int32_t* aFilterOffsetOut,
  169. int32_t* aFilterLengthOut)
  170. {
  171. MOZ_ASSERT(aOutputImagePosition < aFilter->num_values());
  172. aFilter->FilterForValue(aOutputImagePosition,
  173. aFilterOffsetOut,
  174. aFilterLengthOut);
  175. }
  176. void
  177. Downscaler::ClearRestOfRow(uint32_t aStartingAtCol)
  178. {
  179. MOZ_ASSERT(int64_t(aStartingAtCol) <= int64_t(mOriginalSize.width));
  180. uint32_t bytesToClear = (mOriginalSize.width - aStartingAtCol)
  181. * sizeof(uint32_t);
  182. memset(mRowBuffer.get() + (aStartingAtCol * sizeof(uint32_t)),
  183. 0, bytesToClear);
  184. }
  185. void
  186. Downscaler::CommitRow()
  187. {
  188. MOZ_ASSERT(mOutputBuffer, "Should have a current frame");
  189. MOZ_ASSERT(mCurrentInLine < mOriginalSize.height, "Past end of input");
  190. if (mCurrentOutLine < mTargetSize.height) {
  191. int32_t filterOffset = 0;
  192. int32_t filterLength = 0;
  193. GetFilterOffsetAndLength(mYFilter, mCurrentOutLine,
  194. &filterOffset, &filterLength);
  195. int32_t inLineToRead = filterOffset + mLinesInBuffer;
  196. MOZ_ASSERT(mCurrentInLine <= inLineToRead, "Reading past end of input");
  197. if (mCurrentInLine == inLineToRead) {
  198. MOZ_RELEASE_ASSERT(mLinesInBuffer < mWindowCapacity, "Need more rows than capacity!");
  199. skia::ConvolveHorizontally(mRowBuffer.get(), *mXFilter,
  200. mWindow[mLinesInBuffer++], mHasAlpha,
  201. supports_sse2() || supports_mmi());
  202. }
  203. MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
  204. "Writing past end of output");
  205. while (mLinesInBuffer >= filterLength) {
  206. DownscaleInputLine();
  207. if (mCurrentOutLine == mTargetSize.height) {
  208. break; // We're done.
  209. }
  210. GetFilterOffsetAndLength(mYFilter, mCurrentOutLine,
  211. &filterOffset, &filterLength);
  212. }
  213. }
  214. mCurrentInLine += 1;
  215. // If we're at the end of the part of the original image that has data, commit
  216. // rows to shift us to the end.
  217. if (mCurrentInLine == (mFrameRect.y + mFrameRect.height)) {
  218. SkipToRow(mOriginalSize.height - 1);
  219. }
  220. }
  221. bool
  222. Downscaler::HasInvalidation() const
  223. {
  224. return mCurrentOutLine > mPrevInvalidatedLine;
  225. }
  226. DownscalerInvalidRect
  227. Downscaler::TakeInvalidRect()
  228. {
  229. if (MOZ_UNLIKELY(!HasInvalidation())) {
  230. return DownscalerInvalidRect();
  231. }
  232. DownscalerInvalidRect invalidRect;
  233. // Compute the target size invalid rect.
  234. if (mFlipVertically) {
  235. // We need to flip it. This will implicitly flip the original size invalid
  236. // rect, since we compute it by scaling this rect.
  237. invalidRect.mTargetSizeRect =
  238. IntRect(0, mTargetSize.height - mCurrentOutLine,
  239. mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine);
  240. } else {
  241. invalidRect.mTargetSizeRect =
  242. IntRect(0, mPrevInvalidatedLine,
  243. mTargetSize.width, mCurrentOutLine - mPrevInvalidatedLine);
  244. }
  245. mPrevInvalidatedLine = mCurrentOutLine;
  246. // Compute the original size invalid rect.
  247. invalidRect.mOriginalSizeRect = invalidRect.mTargetSizeRect;
  248. invalidRect.mOriginalSizeRect.ScaleRoundOut(mScale.width, mScale.height);
  249. return invalidRect;
  250. }
  251. void
  252. Downscaler::DownscaleInputLine()
  253. {
  254. typedef skia::ConvolutionFilter1D::Fixed FilterValue;
  255. MOZ_ASSERT(mOutputBuffer);
  256. MOZ_ASSERT(mCurrentOutLine < mTargetSize.height,
  257. "Writing past end of output");
  258. int32_t filterOffset = 0;
  259. int32_t filterLength = 0;
  260. MOZ_ASSERT(mCurrentOutLine < mYFilter->num_values());
  261. auto filterValues =
  262. mYFilter->FilterForValue(mCurrentOutLine, &filterOffset, &filterLength);
  263. int32_t currentOutLine = mFlipVertically
  264. ? mTargetSize.height - (mCurrentOutLine + 1)
  265. : mCurrentOutLine;
  266. MOZ_ASSERT(currentOutLine >= 0);
  267. uint8_t* outputLine =
  268. &mOutputBuffer[currentOutLine * mTargetSize.width * sizeof(uint32_t)];
  269. skia::ConvolveVertically(static_cast<const FilterValue*>(filterValues),
  270. filterLength, mWindow.get(), mXFilter->num_values(),
  271. outputLine, mHasAlpha, supports_sse2() || supports_mmi());
  272. mCurrentOutLine += 1;
  273. if (mCurrentOutLine == mTargetSize.height) {
  274. // We're done.
  275. return;
  276. }
  277. int32_t newFilterOffset = 0;
  278. int32_t newFilterLength = 0;
  279. GetFilterOffsetAndLength(mYFilter, mCurrentOutLine,
  280. &newFilterOffset, &newFilterLength);
  281. int diff = newFilterOffset - filterOffset;
  282. MOZ_ASSERT(diff >= 0, "Moving backwards in the filter?");
  283. // Shift the buffer. We're just moving pointers here, so this is cheap.
  284. mLinesInBuffer -= diff;
  285. mLinesInBuffer = min(max(mLinesInBuffer, 0), mWindowCapacity);
  286. // If we already have enough rows to satisfy the filter, there is no need
  287. // to swap as we won't be writing more before the next convolution.
  288. if (filterLength > mLinesInBuffer) {
  289. for (int32_t i = 0; i < mLinesInBuffer; ++i) {
  290. swap(mWindow[i], mWindow[filterLength - mLinesInBuffer + i]);
  291. }
  292. }
  293. }
  294. } // namespace image
  295. } // namespace mozilla