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