Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 : /**
8 : * This header contains various SurfaceFilter implementations that apply
9 : * transformations to image data, for usage with SurfacePipe.
10 : */
11 :
12 : #ifndef mozilla_image_SurfaceFilters_h
13 : #define mozilla_image_SurfaceFilters_h
14 :
15 : #include <algorithm>
16 : #include <stdint.h>
17 : #include <string.h>
18 :
19 : #include "mozilla/Likely.h"
20 : #include "mozilla/Maybe.h"
21 : #include "mozilla/UniquePtr.h"
22 : #include "mozilla/gfx/2D.h"
23 :
24 : #include "DownscalingFilter.h"
25 : #include "SurfaceCache.h"
26 : #include "SurfacePipe.h"
27 :
28 : namespace mozilla {
29 : namespace image {
30 :
31 : //////////////////////////////////////////////////////////////////////////////
32 : // DeinterlacingFilter
33 : //////////////////////////////////////////////////////////////////////////////
34 :
35 : template <typename PixelType, typename Next> class DeinterlacingFilter;
36 :
37 : /**
38 : * A configuration struct for DeinterlacingFilter.
39 : *
40 : * The 'PixelType' template parameter should be either uint32_t (for output to a
41 : * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
42 : */
43 : template <typename PixelType>
44 : struct DeinterlacingConfig
45 : {
46 : template <typename Next> using Filter = DeinterlacingFilter<PixelType, Next>;
47 : bool mProgressiveDisplay; /// If true, duplicate rows during deinterlacing
48 : /// to make progressive display look better, at
49 : /// the cost of some performance.
50 : };
51 :
52 : /**
53 : * DeinterlacingFilter performs deinterlacing by reordering the rows that are
54 : * written to it.
55 : *
56 : * The 'PixelType' template parameter should be either uint32_t (for output to a
57 : * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
58 : *
59 : * The 'Next' template parameter specifies the next filter in the chain.
60 : */
61 : template <typename PixelType, typename Next>
62 0 : class DeinterlacingFilter final : public SurfaceFilter
63 : {
64 : public:
65 0 : DeinterlacingFilter()
66 : : mInputRow(0)
67 : , mOutputRow(0)
68 : , mPass(0)
69 0 : , mProgressiveDisplay(true)
70 0 : { }
71 :
72 : template <typename... Rest>
73 0 : nsresult Configure(const DeinterlacingConfig<PixelType>& aConfig, Rest... aRest)
74 : {
75 0 : nsresult rv = mNext.Configure(aRest...);
76 0 : if (NS_FAILED(rv)) {
77 0 : return rv;
78 : }
79 :
80 0 : if (sizeof(PixelType) == 1 && !mNext.IsValidPalettedPipe()) {
81 0 : NS_WARNING("Paletted DeinterlacingFilter used with non-paletted pipe?");
82 0 : return NS_ERROR_INVALID_ARG;
83 : }
84 0 : if (sizeof(PixelType) == 4 && mNext.IsValidPalettedPipe()) {
85 0 : NS_WARNING("Non-paletted DeinterlacingFilter used with paletted pipe?");
86 0 : return NS_ERROR_INVALID_ARG;
87 : }
88 :
89 0 : gfx::IntSize outputSize = mNext.InputSize();
90 0 : mProgressiveDisplay = aConfig.mProgressiveDisplay;
91 :
92 0 : const uint32_t bufferSize = outputSize.width *
93 0 : outputSize.height *
94 0 : sizeof(PixelType);
95 :
96 : // Use the size of the SurfaceCache as a heuristic to avoid gigantic
97 : // allocations. Even if DownscalingFilter allowed us to allocate space for
98 : // the output image, the deinterlacing buffer may still be too big, and
99 : // fallible allocation won't always save us in the presence of overcommit.
100 0 : if (!SurfaceCache::CanHold(bufferSize)) {
101 0 : return NS_ERROR_OUT_OF_MEMORY;
102 : }
103 :
104 : // Allocate the buffer, which contains deinterlaced scanlines of the image.
105 : // The buffer is necessary so that we can output rows which have already
106 : // been deinterlaced again on subsequent passes. Since a later stage in the
107 : // pipeline may be transforming the rows it receives (for example, by
108 : // downscaling them), the rows may no longer exist in their original form on
109 : // the surface itself.
110 0 : mBuffer.reset(new (fallible) uint8_t[bufferSize]);
111 0 : if (MOZ_UNLIKELY(!mBuffer)) {
112 0 : return NS_ERROR_OUT_OF_MEMORY;
113 : }
114 :
115 : // Clear the buffer to avoid writing uninitialized memory to the output.
116 0 : memset(mBuffer.get(), 0, bufferSize);
117 :
118 0 : ConfigureFilter(outputSize, sizeof(PixelType));
119 0 : return NS_OK;
120 : }
121 :
122 0 : bool IsValidPalettedPipe() const override
123 : {
124 0 : return sizeof(PixelType) == 1 && mNext.IsValidPalettedPipe();
125 : }
126 :
127 0 : Maybe<SurfaceInvalidRect> TakeInvalidRect() override
128 : {
129 0 : return mNext.TakeInvalidRect();
130 : }
131 :
132 : protected:
133 0 : uint8_t* DoResetToFirstRow() override
134 : {
135 0 : mNext.ResetToFirstRow();
136 0 : mPass = 0;
137 0 : mInputRow = 0;
138 0 : mOutputRow = InterlaceOffset(mPass);
139 0 : return GetRowPointer(mOutputRow);
140 : }
141 :
142 0 : uint8_t* DoAdvanceRow() override
143 : {
144 0 : if (mPass >= 4) {
145 0 : return nullptr; // We already finished all passes.
146 : }
147 0 : if (mInputRow >= InputSize().height) {
148 0 : return nullptr; // We already got all the input rows we expect.
149 : }
150 :
151 : // Duplicate from the first Haeberli row to the remaining Haeberli rows
152 : // within the buffer.
153 0 : DuplicateRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow),
154 0 : HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
155 : InputSize(), mOutputRow));
156 :
157 : // Write the current set of Haeberli rows (which contains the current row)
158 : // to the next stage in the pipeline.
159 0 : OutputRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow),
160 0 : HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
161 : InputSize(), mOutputRow));
162 :
163 : // Determine which output row the next input row corresponds to.
164 0 : bool advancedPass = false;
165 0 : uint32_t stride = InterlaceStride(mPass);
166 0 : int32_t nextOutputRow = mOutputRow + stride;
167 0 : while (nextOutputRow >= InputSize().height) {
168 : // Copy any remaining rows from the buffer.
169 0 : if (!advancedPass) {
170 0 : OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
171 : InputSize(), mOutputRow),
172 0 : InputSize().height);
173 : }
174 :
175 : // We finished the current pass; advance to the next one.
176 0 : mPass++;
177 0 : if (mPass >= 4) {
178 0 : return nullptr; // Finished all passes.
179 : }
180 :
181 : // Tell the next pipeline stage that we're starting the next pass.
182 0 : mNext.ResetToFirstRow();
183 :
184 : // Update our state to reflect the pass change.
185 0 : advancedPass = true;
186 0 : stride = InterlaceStride(mPass);
187 0 : nextOutputRow = InterlaceOffset(mPass);
188 : }
189 :
190 0 : MOZ_ASSERT(nextOutputRow >= 0);
191 0 : MOZ_ASSERT(nextOutputRow < InputSize().height);
192 :
193 0 : MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
194 : nextOutputRow) >= 0);
195 0 : MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
196 : nextOutputRow) < InputSize().height);
197 0 : MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
198 : nextOutputRow) <= nextOutputRow);
199 :
200 0 : MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
201 : InputSize(), nextOutputRow) >= 0);
202 0 : MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
203 : InputSize(), nextOutputRow)
204 : <= InputSize().height);
205 0 : MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
206 : InputSize(), nextOutputRow)
207 : > nextOutputRow);
208 :
209 : int32_t nextHaeberliOutputRow =
210 0 : HaeberliOutputStartRow(mPass, mProgressiveDisplay, nextOutputRow);
211 :
212 : // Copy rows from the buffer until we reach the desired output row.
213 0 : if (advancedPass) {
214 0 : OutputRows(0, nextHaeberliOutputRow);
215 : } else {
216 0 : OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
217 : InputSize(), mOutputRow),
218 : nextHaeberliOutputRow);
219 : }
220 :
221 : // Update our position within the buffer.
222 0 : mInputRow++;
223 0 : mOutputRow = nextOutputRow;
224 :
225 : // We'll actually write to the first Haeberli output row, then copy it until
226 : // we reach the last Haeberli output row. The assertions above make sure
227 : // this always includes mOutputRow.
228 0 : return GetRowPointer(nextHaeberliOutputRow);
229 : }
230 :
231 : private:
232 0 : static uint32_t InterlaceOffset(uint32_t aPass)
233 : {
234 0 : MOZ_ASSERT(aPass < 4, "Invalid pass");
235 : static const uint8_t offset[] = { 0, 4, 2, 1 };
236 0 : return offset[aPass];
237 : }
238 :
239 0 : static uint32_t InterlaceStride(uint32_t aPass)
240 : {
241 0 : MOZ_ASSERT(aPass < 4, "Invalid pass");
242 : static const uint8_t stride[] = { 8, 8, 4, 2 };
243 0 : return stride[aPass];
244 : }
245 :
246 0 : static int32_t HaeberliOutputStartRow(uint32_t aPass,
247 : bool aProgressiveDisplay,
248 : int32_t aOutputRow)
249 : {
250 0 : MOZ_ASSERT(aPass < 4, "Invalid pass");
251 : static const uint8_t firstRowOffset[] = { 3, 1, 0, 0 };
252 :
253 0 : if (aProgressiveDisplay) {
254 0 : return std::max(aOutputRow - firstRowOffset[aPass], 0);
255 : } else {
256 0 : return aOutputRow;
257 : }
258 : }
259 :
260 0 : static int32_t HaeberliOutputUntilRow(uint32_t aPass,
261 : bool aProgressiveDisplay,
262 : const gfx::IntSize& aInputSize,
263 : int32_t aOutputRow)
264 : {
265 0 : MOZ_ASSERT(aPass < 4, "Invalid pass");
266 : static const uint8_t lastRowOffset[] = { 4, 2, 1, 0 };
267 :
268 0 : if (aProgressiveDisplay) {
269 0 : return std::min(aOutputRow + lastRowOffset[aPass],
270 0 : aInputSize.height - 1)
271 0 : + 1; // Add one because this is an open interval on the right.
272 : } else {
273 0 : return aOutputRow + 1;
274 : }
275 : }
276 :
277 0 : void DuplicateRows(int32_t aStart, int32_t aUntil)
278 : {
279 0 : MOZ_ASSERT(aStart >= 0);
280 0 : MOZ_ASSERT(aUntil >= 0);
281 :
282 0 : if (aUntil <= aStart || aStart >= InputSize().height) {
283 0 : return;
284 : }
285 :
286 : // The source row is the first row in the range.
287 0 : const uint8_t* sourceRowPointer = GetRowPointer(aStart);
288 :
289 : // We duplicate the source row into each subsequent row in the range.
290 0 : for (int32_t destRow = aStart + 1 ; destRow < aUntil ; ++destRow) {
291 0 : uint8_t* destRowPointer = GetRowPointer(destRow);
292 0 : memcpy(destRowPointer, sourceRowPointer, InputSize().width * sizeof(PixelType));
293 : }
294 : }
295 :
296 0 : void OutputRows(int32_t aStart, int32_t aUntil)
297 : {
298 0 : MOZ_ASSERT(aStart >= 0);
299 0 : MOZ_ASSERT(aUntil >= 0);
300 :
301 0 : if (aUntil <= aStart || aStart >= InputSize().height) {
302 0 : return;
303 : }
304 :
305 0 : for (int32_t rowToOutput = aStart; rowToOutput < aUntil; ++rowToOutput) {
306 0 : mNext.WriteBuffer(reinterpret_cast<PixelType*>(GetRowPointer(rowToOutput)));
307 : }
308 : }
309 :
310 0 : uint8_t* GetRowPointer(uint32_t aRow) const
311 : {
312 0 : uint32_t offset = aRow * InputSize().width * sizeof(PixelType);
313 0 : MOZ_ASSERT(offset < InputSize().width * InputSize().height * sizeof(PixelType),
314 : "Start of row is outside of image");
315 0 : MOZ_ASSERT(offset + InputSize().width * sizeof(PixelType)
316 : <= InputSize().width * InputSize().height * sizeof(PixelType),
317 : "End of row is outside of image");
318 0 : return mBuffer.get() + offset;
319 : }
320 :
321 : Next mNext; /// The next SurfaceFilter in the chain.
322 :
323 : UniquePtr<uint8_t[]> mBuffer; /// The buffer used to store reordered rows.
324 : int32_t mInputRow; /// The current row we're reading. (0-indexed)
325 : int32_t mOutputRow; /// The current row we're writing. (0-indexed)
326 : uint8_t mPass; /// Which pass we're on. (0-indexed)
327 : bool mProgressiveDisplay; /// If true, duplicate rows to optimize for
328 : /// progressive display.
329 : };
330 :
331 :
332 : //////////////////////////////////////////////////////////////////////////////
333 : // RemoveFrameRectFilter
334 : //////////////////////////////////////////////////////////////////////////////
335 :
336 : template <typename Next> class RemoveFrameRectFilter;
337 :
338 : /**
339 : * A configuration struct for RemoveFrameRectFilter.
340 : */
341 : struct RemoveFrameRectConfig
342 : {
343 : template <typename Next> using Filter = RemoveFrameRectFilter<Next>;
344 : gfx::IntRect mFrameRect; /// The surface subrect which contains data.
345 : };
346 :
347 : /**
348 : * RemoveFrameRectFilter turns an image with a frame rect that does not match
349 : * its logical size into an image with no frame rect. It does this by writing
350 : * transparent pixels into any padding regions and throwing away excess data.
351 : *
352 : * The 'Next' template parameter specifies the next filter in the chain.
353 : */
354 : template <typename Next>
355 0 : class RemoveFrameRectFilter final : public SurfaceFilter
356 : {
357 : public:
358 0 : RemoveFrameRectFilter()
359 0 : : mRow(0)
360 0 : { }
361 :
362 : template <typename... Rest>
363 0 : nsresult Configure(const RemoveFrameRectConfig& aConfig, Rest... aRest)
364 : {
365 0 : nsresult rv = mNext.Configure(aRest...);
366 0 : if (NS_FAILED(rv)) {
367 0 : return rv;
368 : }
369 :
370 0 : if (mNext.IsValidPalettedPipe()) {
371 0 : NS_WARNING("RemoveFrameRectFilter used with paletted pipe?");
372 0 : return NS_ERROR_INVALID_ARG;
373 : }
374 :
375 0 : mFrameRect = mUnclampedFrameRect = aConfig.mFrameRect;
376 0 : gfx::IntSize outputSize = mNext.InputSize();
377 :
378 : // Forbid frame rects with negative size.
379 0 : if (aConfig.mFrameRect.width < 0 || aConfig.mFrameRect.height < 0) {
380 0 : return NS_ERROR_INVALID_ARG;
381 : }
382 :
383 : // Clamp mFrameRect to the output size.
384 0 : gfx::IntRect outputRect(0, 0, outputSize.width, outputSize.height);
385 0 : mFrameRect = mFrameRect.Intersect(outputRect);
386 :
387 : // If there's no intersection, |mFrameRect| will be an empty rect positioned
388 : // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
389 : // not what we want. Force it to (0, 0) in that case.
390 0 : if (mFrameRect.IsEmpty()) {
391 0 : mFrameRect.MoveTo(0, 0);
392 : }
393 :
394 : // We don't need an intermediate buffer unless the unclamped frame rect
395 : // width is larger than the clamped frame rect width. In that case, the
396 : // caller will end up writing data that won't end up in the final image at
397 : // all, and we'll need a buffer to give that data a place to go.
398 0 : if (mFrameRect.width < mUnclampedFrameRect.width) {
399 0 : mBuffer.reset(new (fallible) uint8_t[mUnclampedFrameRect.width *
400 : sizeof(uint32_t)]);
401 0 : if (MOZ_UNLIKELY(!mBuffer)) {
402 0 : return NS_ERROR_OUT_OF_MEMORY;
403 : }
404 :
405 0 : memset(mBuffer.get(), 0, mUnclampedFrameRect.width * sizeof(uint32_t));
406 : }
407 :
408 0 : ConfigureFilter(mUnclampedFrameRect.Size(), sizeof(uint32_t));
409 0 : return NS_OK;
410 : }
411 :
412 0 : Maybe<SurfaceInvalidRect> TakeInvalidRect() override
413 : {
414 0 : return mNext.TakeInvalidRect();
415 : }
416 :
417 : protected:
418 0 : uint8_t* DoResetToFirstRow() override
419 : {
420 0 : uint8_t* rowPtr = mNext.ResetToFirstRow();
421 0 : if (rowPtr == nullptr) {
422 0 : mRow = mFrameRect.YMost();
423 0 : return nullptr;
424 : }
425 :
426 0 : mRow = mUnclampedFrameRect.y;
427 :
428 : // Advance the next pipeline stage to the beginning of the frame rect,
429 : // outputting blank rows.
430 0 : if (mFrameRect.y > 0) {
431 0 : for (int32_t rowToOutput = 0; rowToOutput < mFrameRect.y ; ++rowToOutput) {
432 0 : mNext.WriteEmptyRow();
433 : }
434 : }
435 :
436 : // We're at the beginning of the frame rect now, so return if we're either
437 : // ready for input or we're already done.
438 0 : rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
439 0 : if (!mFrameRect.IsEmpty() || rowPtr == nullptr) {
440 : // Note that the pointer we're returning is for the next row we're
441 : // actually going to write to, but we may discard writes before that point
442 : // if mRow < mFrameRect.y.
443 0 : return AdjustRowPointer(rowPtr);
444 : }
445 :
446 : // We've finished the region specified by the frame rect, but the frame rect
447 : // is empty, so we need to output the rest of the image immediately. Advance
448 : // to the end of the next pipeline stage's buffer, outputting blank rows.
449 0 : while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { }
450 :
451 0 : mRow = mFrameRect.YMost();
452 0 : return nullptr; // We're done.
453 : }
454 :
455 0 : uint8_t* DoAdvanceRow() override
456 : {
457 0 : uint8_t* rowPtr = nullptr;
458 :
459 0 : const int32_t currentRow = mRow;
460 0 : mRow++;
461 :
462 0 : if (currentRow < mFrameRect.y) {
463 : // This row is outside of the frame rect, so just drop it on the floor.
464 0 : rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
465 0 : return AdjustRowPointer(rowPtr);
466 0 : } else if (currentRow >= mFrameRect.YMost()) {
467 0 : NS_WARNING("RemoveFrameRectFilter: Advancing past end of frame rect");
468 0 : return nullptr;
469 : }
470 :
471 : // If we had to buffer, copy the data. Otherwise, just advance the row.
472 0 : if (mBuffer) {
473 : // We write from the beginning of the buffer unless |mUnclampedFrameRect.x|
474 : // is negative; if that's the case, we have to skip the portion of the
475 : // unclamped frame rect that's outside the row.
476 0 : uint32_t* source = reinterpret_cast<uint32_t*>(mBuffer.get()) -
477 0 : std::min(mUnclampedFrameRect.x, 0);
478 :
479 : // We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've
480 : // already clamped these values to the size of the output, so we don't
481 : // have to worry about bounds checking here (though WriteBuffer() will do
482 : // it for us in any case).
483 0 : WriteState state = mNext.WriteBuffer(source, mFrameRect.x, mFrameRect.width);
484 :
485 0 : rowPtr = state == WriteState::NEED_MORE_DATA ? mBuffer.get()
486 : : nullptr;
487 : } else {
488 0 : rowPtr = mNext.AdvanceRow();
489 : }
490 :
491 : // If there's still more data coming or we're already done, just adjust the
492 : // pointer and return.
493 0 : if (mRow < mFrameRect.YMost() || rowPtr == nullptr) {
494 0 : return AdjustRowPointer(rowPtr);
495 : }
496 :
497 : // We've finished the region specified by the frame rect. Advance to the end
498 : // of the next pipeline stage's buffer, outputting blank rows.
499 0 : while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { }
500 :
501 0 : mRow = mFrameRect.YMost();
502 0 : return nullptr; // We're done.
503 : }
504 :
505 : private:
506 0 : uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer) const
507 : {
508 0 : if (mBuffer) {
509 0 : MOZ_ASSERT(aNextRowPointer == mBuffer.get() || aNextRowPointer == nullptr);
510 0 : return aNextRowPointer; // No adjustment needed for an intermediate buffer.
511 : }
512 :
513 0 : if (mFrameRect.IsEmpty() ||
514 0 : mRow >= mFrameRect.YMost() ||
515 : aNextRowPointer == nullptr) {
516 0 : return nullptr; // Nothing left to write.
517 : }
518 :
519 0 : return aNextRowPointer + mFrameRect.x * sizeof(uint32_t);
520 : }
521 :
522 : Next mNext; /// The next SurfaceFilter in the chain.
523 :
524 : gfx::IntRect mFrameRect; /// The surface subrect which contains data,
525 : /// clamped to the image size.
526 : gfx::IntRect mUnclampedFrameRect; /// The frame rect before clamping.
527 : UniquePtr<uint8_t[]> mBuffer; /// The intermediate buffer, if one is
528 : /// necessary because the frame rect width
529 : /// is larger than the image's logical width.
530 : int32_t mRow; /// The row in unclamped frame rect space
531 : /// that we're currently writing.
532 : };
533 :
534 :
535 : //////////////////////////////////////////////////////////////////////////////
536 : // ADAM7InterpolatingFilter
537 : //////////////////////////////////////////////////////////////////////////////
538 :
539 : template <typename Next> class ADAM7InterpolatingFilter;
540 :
541 : /**
542 : * A configuration struct for ADAM7InterpolatingFilter.
543 : */
544 : struct ADAM7InterpolatingConfig
545 : {
546 : template <typename Next> using Filter = ADAM7InterpolatingFilter<Next>;
547 : };
548 :
549 : /**
550 : * ADAM7InterpolatingFilter performs bilinear interpolation over an ADAM7
551 : * interlaced image.
552 : *
553 : * ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new set
554 : * of pixels in each block receives their final values, according to the
555 : * following pattern:
556 : *
557 : * 1 6 4 6 2 6 4 6
558 : * 7 7 7 7 7 7 7 7
559 : * 5 6 5 6 5 6 5 6
560 : * 7 7 7 7 7 7 7 7
561 : * 3 6 4 6 3 6 4 6
562 : * 7 7 7 7 7 7 7 7
563 : * 5 6 5 6 5 6 5 6
564 : * 7 7 7 7 7 7 7 7
565 : *
566 : * When rendering the pixels that have not yet received their final values, we
567 : * can get much better intermediate results if we interpolate between
568 : * the pixels we *have* gotten so far. This filter performs bilinear
569 : * interpolation by first performing linear interpolation horizontally for each
570 : * "important" row (which we'll define as a row that has received any pixels
571 : * with final values at all) and then performing linear interpolation vertically
572 : * to produce pixel values for rows which aren't important on the current pass.
573 : *
574 : * Note that this filter totally ignores the data which is written to rows which
575 : * aren't important on the current pass! It's fine to write nothing at all for
576 : * these rows, although doing so won't cause any harm.
577 : *
578 : * XXX(seth): In bug 1280552 we'll add a SIMD implementation for this filter.
579 : *
580 : * The 'Next' template parameter specifies the next filter in the chain.
581 : */
582 : template <typename Next>
583 0 : class ADAM7InterpolatingFilter final : public SurfaceFilter
584 : {
585 : public:
586 0 : ADAM7InterpolatingFilter()
587 : : mPass(0) // The current pass, in the range 1..7. Starts at 0 so that
588 : // DoResetToFirstRow() doesn't have to special case the first pass.
589 0 : , mRow(0)
590 0 : { }
591 :
592 : template <typename... Rest>
593 0 : nsresult Configure(const ADAM7InterpolatingConfig& aConfig, Rest... aRest)
594 : {
595 0 : nsresult rv = mNext.Configure(aRest...);
596 0 : if (NS_FAILED(rv)) {
597 0 : return rv;
598 : }
599 :
600 0 : if (mNext.IsValidPalettedPipe()) {
601 0 : NS_WARNING("ADAM7InterpolatingFilter used with paletted pipe?");
602 0 : return NS_ERROR_INVALID_ARG;
603 : }
604 :
605 : // We have two intermediate buffers, one for the previous row with final
606 : // pixel values and one for the row that the previous filter in the chain is
607 : // currently writing to.
608 0 : size_t inputWidthInBytes = mNext.InputSize().width * sizeof(uint32_t);
609 0 : mPreviousRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
610 0 : if (MOZ_UNLIKELY(!mPreviousRow)) {
611 0 : return NS_ERROR_OUT_OF_MEMORY;
612 : }
613 :
614 0 : mCurrentRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
615 0 : if (MOZ_UNLIKELY(!mCurrentRow)) {
616 0 : return NS_ERROR_OUT_OF_MEMORY;
617 : }
618 :
619 0 : memset(mPreviousRow.get(), 0, inputWidthInBytes);
620 0 : memset(mCurrentRow.get(), 0, inputWidthInBytes);
621 :
622 0 : ConfigureFilter(mNext.InputSize(), sizeof(uint32_t));
623 0 : return NS_OK;
624 : }
625 :
626 0 : Maybe<SurfaceInvalidRect> TakeInvalidRect() override
627 : {
628 0 : return mNext.TakeInvalidRect();
629 : }
630 :
631 : protected:
632 0 : uint8_t* DoResetToFirstRow() override
633 : {
634 0 : mRow = 0;
635 0 : mPass = std::min(mPass + 1, 7);
636 :
637 0 : uint8_t* rowPtr = mNext.ResetToFirstRow();
638 0 : if (mPass == 7) {
639 : // Short circuit this filter on the final pass, since all pixels have
640 : // their final values at that point.
641 0 : return rowPtr;
642 : }
643 :
644 0 : return mCurrentRow.get();
645 : }
646 :
647 0 : uint8_t* DoAdvanceRow() override
648 : {
649 0 : MOZ_ASSERT(0 < mPass && mPass <= 7, "Invalid pass");
650 :
651 0 : int32_t currentRow = mRow;
652 0 : ++mRow;
653 :
654 0 : if (mPass == 7) {
655 : // On the final pass we short circuit this filter totally.
656 0 : return mNext.AdvanceRow();
657 : }
658 :
659 0 : const int32_t lastImportantRow = LastImportantRow(InputSize().height, mPass);
660 0 : if (currentRow > lastImportantRow) {
661 0 : return nullptr; // This pass is already complete.
662 : }
663 :
664 0 : if (!IsImportantRow(currentRow, mPass)) {
665 : // We just ignore whatever the caller gives us for these rows. We'll
666 : // interpolate them in later.
667 0 : return mCurrentRow.get();
668 : }
669 :
670 : // This is an important row. We need to perform horizontal interpolation for
671 : // these rows.
672 0 : InterpolateHorizontally(mCurrentRow.get(), InputSize().width, mPass);
673 :
674 : // Interpolate vertically between the previous important row and the current
675 : // important row. We skip this if the current row is 0 (which is always an
676 : // important row), because in that case there is no previous important row
677 : // to interpolate with.
678 0 : if (currentRow != 0) {
679 0 : InterpolateVertically(mPreviousRow.get(), mCurrentRow.get(), mPass, mNext);
680 : }
681 :
682 : // Write out the current row itself, which, being an important row, does not
683 : // need vertical interpolation.
684 0 : uint32_t* currentRowAsPixels = reinterpret_cast<uint32_t*>(mCurrentRow.get());
685 0 : mNext.WriteBuffer(currentRowAsPixels);
686 :
687 0 : if (currentRow == lastImportantRow) {
688 : // This is the last important row, which completes this pass. Note that
689 : // for very small images, this may be the first row! Since there won't be
690 : // another important row, there's nothing to interpolate with vertically,
691 : // so we just duplicate this row until the end of the image.
692 0 : while (mNext.WriteBuffer(currentRowAsPixels) == WriteState::NEED_MORE_DATA) { }
693 :
694 : // All of the remaining rows in the image were determined above, so we're done.
695 0 : return nullptr;
696 : }
697 :
698 : // The current row is now the previous important row; save it.
699 0 : Swap(mPreviousRow, mCurrentRow);
700 :
701 0 : MOZ_ASSERT(mRow < InputSize().height, "Reached the end of the surface without "
702 : "hitting the last important row?");
703 :
704 0 : return mCurrentRow.get();
705 : }
706 :
707 : private:
708 0 : static void InterpolateVertically(uint8_t* aPreviousRow,
709 : uint8_t* aCurrentRow,
710 : uint8_t aPass,
711 : SurfaceFilter& aNext)
712 : {
713 0 : const float* weights = InterpolationWeights(ImportantRowStride(aPass));
714 :
715 : // We need to interpolate vertically to generate the rows between the
716 : // previous important row and the next one. Recall that important rows are
717 : // rows which contain at least some final pixels; see
718 : // InterpolateHorizontally() for some additional explanation as to what that
719 : // means. Note that we've already written out the previous important row, so
720 : // we start the iteration at 1.
721 0 : for (int32_t outRow = 1; outRow < ImportantRowStride(aPass); ++outRow) {
722 0 : const float weight = weights[outRow];
723 :
724 : // We iterate through the previous and current important row every time we
725 : // write out an interpolated row, so we need to copy the pointers.
726 0 : uint8_t* prevRowBytes = aPreviousRow;
727 0 : uint8_t* currRowBytes = aCurrentRow;
728 :
729 : // Write out the interpolated pixels. Interpolation is componentwise.
730 0 : aNext.template WritePixelsToRow<uint32_t>([&]{
731 0 : uint32_t pixel = 0;
732 0 : auto* component = reinterpret_cast<uint8_t*>(&pixel);
733 0 : *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
734 0 : *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
735 0 : *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
736 0 : *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
737 0 : return AsVariant(pixel);
738 : });
739 : }
740 0 : }
741 :
742 0 : static void InterpolateHorizontally(uint8_t* aRow, int32_t aWidth, uint8_t aPass)
743 : {
744 : // Collect the data we'll need to perform horizontal interpolation. The
745 : // terminology here bears some explanation: a "final pixel" is a pixel which
746 : // has received its final value. On each pass, a new set of pixels receives
747 : // their final value; see the diagram above of the 8x8 pattern that ADAM7
748 : // uses. Any pixel which hasn't received its final value on this pass
749 : // derives its value from either horizontal or vertical interpolation
750 : // instead.
751 0 : const size_t finalPixelStride = FinalPixelStride(aPass);
752 0 : const size_t finalPixelStrideBytes = finalPixelStride * sizeof(uint32_t);
753 0 : const size_t lastFinalPixel = LastFinalPixel(aWidth, aPass);
754 0 : const size_t lastFinalPixelBytes = lastFinalPixel * sizeof(uint32_t);
755 0 : const float* weights = InterpolationWeights(finalPixelStride);
756 :
757 : // Interpolate blocks of pixels which lie between two final pixels.
758 : // Horizontal interpolation is done in place, as we'll need the results
759 : // later when we vertically interpolate.
760 0 : for (size_t blockBytes = 0;
761 0 : blockBytes < lastFinalPixelBytes;
762 : blockBytes += finalPixelStrideBytes) {
763 0 : uint8_t* finalPixelA = aRow + blockBytes;
764 0 : uint8_t* finalPixelB = aRow + blockBytes + finalPixelStrideBytes;
765 :
766 0 : MOZ_ASSERT(finalPixelA < aRow + aWidth * sizeof(uint32_t),
767 : "Running off end of buffer");
768 0 : MOZ_ASSERT(finalPixelB < aRow + aWidth * sizeof(uint32_t),
769 : "Running off end of buffer");
770 :
771 : // Interpolate the individual pixels componentwise. Note that we start
772 : // iteration at 1 since we don't need to apply any interpolation to the
773 : // first pixel in the block, which has its final value.
774 0 : for (size_t pixelIndex = 1; pixelIndex < finalPixelStride; ++pixelIndex) {
775 0 : const float weight = weights[pixelIndex];
776 0 : uint8_t* pixel = aRow + blockBytes + pixelIndex * sizeof(uint32_t);
777 :
778 0 : MOZ_ASSERT(pixel < aRow + aWidth * sizeof(uint32_t), "Running off end of buffer");
779 :
780 0 : for (size_t component = 0; component < sizeof(uint32_t); ++component) {
781 0 : pixel[component] =
782 0 : InterpolateByte(finalPixelA[component], finalPixelB[component], weight);
783 : }
784 : }
785 : }
786 :
787 : // For the pixels after the last final pixel in the row, there isn't a
788 : // second final pixel to interpolate with, so just duplicate.
789 0 : uint32_t* rowPixels = reinterpret_cast<uint32_t*>(aRow);
790 0 : uint32_t pixelToDuplicate = rowPixels[lastFinalPixel];
791 0 : for (int32_t pixelIndex = lastFinalPixel + 1;
792 0 : pixelIndex < aWidth;
793 : ++pixelIndex) {
794 0 : MOZ_ASSERT(pixelIndex < aWidth, "Running off end of buffer");
795 0 : rowPixels[pixelIndex] = pixelToDuplicate;
796 : }
797 0 : }
798 :
799 0 : static uint8_t InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight)
800 : {
801 0 : return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight));
802 : }
803 :
804 0 : static int32_t ImportantRowStride(uint8_t aPass)
805 : {
806 0 : MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass");
807 :
808 : // The stride between important rows for each pass, with a dummy value for
809 : // the nonexistent pass 0.
810 : static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1 };
811 :
812 0 : return strides[aPass];
813 : }
814 :
815 0 : static bool IsImportantRow(int32_t aRow, uint8_t aPass)
816 : {
817 0 : MOZ_ASSERT(aRow >= 0);
818 :
819 : // Whether the row is important comes down to divisibility by the stride for
820 : // this pass, which is always a power of 2, so we can check using a mask.
821 0 : int32_t mask = ImportantRowStride(aPass) - 1;
822 0 : return (aRow & mask) == 0;
823 : }
824 :
825 0 : static int32_t LastImportantRow(int32_t aHeight, uint8_t aPass)
826 : {
827 0 : MOZ_ASSERT(aHeight > 0);
828 :
829 : // We can find the last important row using the same mask trick as above.
830 0 : int32_t lastRow = aHeight - 1;
831 0 : int32_t mask = ImportantRowStride(aPass) - 1;
832 0 : return lastRow - (lastRow & mask);
833 : }
834 :
835 0 : static size_t FinalPixelStride(uint8_t aPass)
836 : {
837 0 : MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass");
838 :
839 : // The stride between the final pixels in important rows for each pass, with
840 : // a dummy value for the nonexistent pass 0.
841 : static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1 };
842 :
843 0 : return strides[aPass];
844 : }
845 :
846 0 : static size_t LastFinalPixel(int32_t aWidth, uint8_t aPass)
847 : {
848 0 : MOZ_ASSERT(aWidth >= 0);
849 :
850 : // Again, we can use the mask trick above to find the last important pixel.
851 0 : int32_t lastColumn = aWidth - 1;
852 0 : size_t mask = FinalPixelStride(aPass) - 1;
853 0 : return lastColumn - (lastColumn & mask);
854 : }
855 :
856 0 : static const float* InterpolationWeights(int32_t aStride)
857 : {
858 : // Precalculated interpolation weights. These are used to interpolate
859 : // between final pixels or between important rows. Although no interpolation
860 : // is actually applied to the previous final pixel or important row value,
861 : // the arrays still start with 1.0f, which is always skipped, primarily
862 : // because otherwise |stride1Weights| would have zero elements.
863 : static float stride8Weights[] =
864 : { 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f };
865 : static float stride4Weights[] = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f };
866 : static float stride2Weights[] = { 1.0f, 1 / 2.0f };
867 : static float stride1Weights[] = { 1.0f };
868 :
869 0 : switch (aStride) {
870 0 : case 8: return stride8Weights;
871 0 : case 4: return stride4Weights;
872 0 : case 2: return stride2Weights;
873 0 : case 1: return stride1Weights;
874 0 : default: MOZ_CRASH();
875 : }
876 : }
877 :
878 : Next mNext; /// The next SurfaceFilter in the chain.
879 :
880 : UniquePtr<uint8_t[]> mPreviousRow; /// The last important row (i.e., row with
881 : /// final pixel values) that got written to.
882 : UniquePtr<uint8_t[]> mCurrentRow; /// The row that's being written to right
883 : /// now.
884 : uint8_t mPass; /// Which ADAM7 pass we're on. Valid passes
885 : /// are 1..7 during processing and 0 prior
886 : /// to configuraiton.
887 : int32_t mRow; /// The row we're currently reading.
888 : };
889 :
890 : } // namespace image
891 : } // namespace mozilla
892 :
893 : #endif // mozilla_image_SurfaceFilters_h
|