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