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