Coverage Report

Created: 2018-09-25 14:53

/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