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