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/. */
8 * This header contains various SurfaceFilter implementations that apply
9 * transformations to image data, for usage with SurfacePipe.
12 #ifndef mozilla_image_SurfaceFilters_h
13 #define mozilla_image_SurfaceFilters_h
19 #include "mozilla/Likely.h"
20 #include "mozilla/Maybe.h"
21 #include "mozilla/UniquePtr.h"
22 #include "mozilla/gfx/2D.h"
23 #include "skia/src/core/SkBlitRow.h"
25 #include "DownscalingFilter.h"
26 #include "SurfaceCache.h"
27 #include "SurfacePipe.h"
32 //////////////////////////////////////////////////////////////////////////////
33 // DeinterlacingFilter
34 //////////////////////////////////////////////////////////////////////////////
36 template <typename PixelType
, typename Next
>
37 class DeinterlacingFilter
;
40 * A configuration struct for DeinterlacingFilter.
42 * The 'PixelType' template parameter should be either uint32_t (for output to a
43 * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
45 template <typename PixelType
>
46 struct DeinterlacingConfig
{
47 template <typename Next
>
48 using Filter
= DeinterlacingFilter
<PixelType
, Next
>;
49 bool mProgressiveDisplay
; /// If true, duplicate rows during deinterlacing
50 /// to make progressive display look better, at
51 /// the cost of some performance.
55 * DeinterlacingFilter performs deinterlacing by reordering the rows that are
58 * The 'PixelType' template parameter should be either uint32_t (for output to a
59 * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
61 * The 'Next' template parameter specifies the next filter in the chain.
63 template <typename PixelType
, typename Next
>
64 class DeinterlacingFilter final
: public SurfaceFilter
{
67 : mInputRow(0), mOutputRow(0), mPass(0), mProgressiveDisplay(true) {}
69 template <typename
... Rest
>
70 nsresult
Configure(const DeinterlacingConfig
<PixelType
>& aConfig
,
71 const Rest
&... aRest
) {
72 nsresult rv
= mNext
.Configure(aRest
...);
77 if (sizeof(PixelType
) == 1 && !mNext
.IsValidPalettedPipe()) {
78 NS_WARNING("Paletted DeinterlacingFilter used with non-paletted pipe?");
79 return NS_ERROR_INVALID_ARG
;
81 if (sizeof(PixelType
) == 4 && mNext
.IsValidPalettedPipe()) {
82 NS_WARNING("Non-paletted DeinterlacingFilter used with paletted pipe?");
83 return NS_ERROR_INVALID_ARG
;
86 gfx::IntSize outputSize
= mNext
.InputSize();
87 mProgressiveDisplay
= aConfig
.mProgressiveDisplay
;
89 const uint32_t bufferSize
=
90 outputSize
.width
* outputSize
.height
* sizeof(PixelType
);
92 // Use the size of the SurfaceCache as a heuristic to avoid gigantic
93 // allocations. Even if DownscalingFilter allowed us to allocate space for
94 // the output image, the deinterlacing buffer may still be too big, and
95 // fallible allocation won't always save us in the presence of overcommit.
96 if (!SurfaceCache::CanHold(bufferSize
)) {
97 return NS_ERROR_OUT_OF_MEMORY
;
100 // Allocate the buffer, which contains deinterlaced scanlines of the image.
101 // The buffer is necessary so that we can output rows which have already
102 // been deinterlaced again on subsequent passes. Since a later stage in the
103 // pipeline may be transforming the rows it receives (for example, by
104 // downscaling them), the rows may no longer exist in their original form on
105 // the surface itself.
106 mBuffer
.reset(new (fallible
) uint8_t[bufferSize
]);
107 if (MOZ_UNLIKELY(!mBuffer
)) {
108 return NS_ERROR_OUT_OF_MEMORY
;
111 // Clear the buffer to avoid writing uninitialized memory to the output.
112 memset(mBuffer
.get(), 0, bufferSize
);
114 ConfigureFilter(outputSize
, sizeof(PixelType
));
118 bool IsValidPalettedPipe() const override
{
119 return sizeof(PixelType
) == 1 && mNext
.IsValidPalettedPipe();
122 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
123 return mNext
.TakeInvalidRect();
127 uint8_t* DoResetToFirstRow() override
{
128 mNext
.ResetToFirstRow();
131 mOutputRow
= InterlaceOffset(mPass
);
132 return GetRowPointer(mOutputRow
);
135 uint8_t* DoAdvanceRow() override
{
137 return nullptr; // We already finished all passes.
139 if (mInputRow
>= InputSize().height
) {
140 return nullptr; // We already got all the input rows we expect.
143 // Duplicate from the first Haeberli row to the remaining Haeberli rows
144 // within the buffer.
146 HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, mOutputRow
),
147 HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
150 // Write the current set of Haeberli rows (which contains the current row)
151 // to the next stage in the pipeline.
152 OutputRows(HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, mOutputRow
),
153 HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
156 // Determine which output row the next input row corresponds to.
157 bool advancedPass
= false;
158 uint32_t stride
= InterlaceStride(mPass
);
159 int32_t nextOutputRow
= mOutputRow
+ stride
;
160 while (nextOutputRow
>= InputSize().height
) {
161 // Copy any remaining rows from the buffer.
163 OutputRows(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
,
164 InputSize(), mOutputRow
),
168 // We finished the current pass; advance to the next one.
171 return nullptr; // Finished all passes.
174 // Tell the next pipeline stage that we're starting the next pass.
175 mNext
.ResetToFirstRow();
177 // Update our state to reflect the pass change.
179 stride
= InterlaceStride(mPass
);
180 nextOutputRow
= InterlaceOffset(mPass
);
183 MOZ_ASSERT(nextOutputRow
>= 0);
184 MOZ_ASSERT(nextOutputRow
< InputSize().height
);
187 HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, nextOutputRow
) >= 0);
188 MOZ_ASSERT(HaeberliOutputStartRow(mPass
, mProgressiveDisplay
,
189 nextOutputRow
) < InputSize().height
);
190 MOZ_ASSERT(HaeberliOutputStartRow(mPass
, mProgressiveDisplay
,
191 nextOutputRow
) <= nextOutputRow
);
193 MOZ_ASSERT(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
194 nextOutputRow
) >= 0);
195 MOZ_ASSERT(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
196 nextOutputRow
) <= InputSize().height
);
197 MOZ_ASSERT(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
198 nextOutputRow
) > nextOutputRow
);
200 int32_t nextHaeberliOutputRow
=
201 HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, nextOutputRow
);
203 // Copy rows from the buffer until we reach the desired output row.
205 OutputRows(0, nextHaeberliOutputRow
);
207 OutputRows(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
209 nextHaeberliOutputRow
);
212 // Update our position within the buffer.
214 mOutputRow
= nextOutputRow
;
216 // We'll actually write to the first Haeberli output row, then copy it until
217 // we reach the last Haeberli output row. The assertions above make sure
218 // this always includes mOutputRow.
219 return GetRowPointer(nextHaeberliOutputRow
);
223 static uint32_t InterlaceOffset(uint32_t aPass
) {
224 MOZ_ASSERT(aPass
< 4, "Invalid pass");
225 static const uint8_t offset
[] = {0, 4, 2, 1};
226 return offset
[aPass
];
229 static uint32_t InterlaceStride(uint32_t aPass
) {
230 MOZ_ASSERT(aPass
< 4, "Invalid pass");
231 static const uint8_t stride
[] = {8, 8, 4, 2};
232 return stride
[aPass
];
235 static int32_t HaeberliOutputStartRow(uint32_t aPass
,
236 bool aProgressiveDisplay
,
237 int32_t aOutputRow
) {
238 MOZ_ASSERT(aPass
< 4, "Invalid pass");
239 static const uint8_t firstRowOffset
[] = {3, 1, 0, 0};
241 if (aProgressiveDisplay
) {
242 return std::max(aOutputRow
- firstRowOffset
[aPass
], 0);
248 static int32_t HaeberliOutputUntilRow(uint32_t aPass
,
249 bool aProgressiveDisplay
,
250 const gfx::IntSize
& aInputSize
,
251 int32_t aOutputRow
) {
252 MOZ_ASSERT(aPass
< 4, "Invalid pass");
253 static const uint8_t lastRowOffset
[] = {4, 2, 1, 0};
255 if (aProgressiveDisplay
) {
256 return std::min(aOutputRow
+ lastRowOffset
[aPass
],
257 aInputSize
.height
- 1) +
258 1; // Add one because this is an open interval on the right.
260 return aOutputRow
+ 1;
264 void DuplicateRows(int32_t aStart
, int32_t aUntil
) {
265 MOZ_ASSERT(aStart
>= 0);
266 MOZ_ASSERT(aUntil
>= 0);
268 if (aUntil
<= aStart
|| aStart
>= InputSize().height
) {
272 // The source row is the first row in the range.
273 const uint8_t* sourceRowPointer
= GetRowPointer(aStart
);
275 // We duplicate the source row into each subsequent row in the range.
276 for (int32_t destRow
= aStart
+ 1; destRow
< aUntil
; ++destRow
) {
277 uint8_t* destRowPointer
= GetRowPointer(destRow
);
278 memcpy(destRowPointer
, sourceRowPointer
,
279 InputSize().width
* sizeof(PixelType
));
283 void OutputRows(int32_t aStart
, int32_t aUntil
) {
284 MOZ_ASSERT(aStart
>= 0);
285 MOZ_ASSERT(aUntil
>= 0);
287 if (aUntil
<= aStart
|| aStart
>= InputSize().height
) {
291 for (int32_t rowToOutput
= aStart
; rowToOutput
< aUntil
; ++rowToOutput
) {
293 reinterpret_cast<PixelType
*>(GetRowPointer(rowToOutput
)));
297 uint8_t* GetRowPointer(uint32_t aRow
) const {
298 uint32_t offset
= aRow
* InputSize().width
* sizeof(PixelType
);
300 offset
< InputSize().width
* InputSize().height
* sizeof(PixelType
),
301 "Start of row is outside of image");
302 MOZ_ASSERT(offset
+ InputSize().width
* sizeof(PixelType
) <=
303 InputSize().width
* InputSize().height
* sizeof(PixelType
),
304 "End of row is outside of image");
305 return mBuffer
.get() + offset
;
308 Next mNext
; /// The next SurfaceFilter in the chain.
310 UniquePtr
<uint8_t[]> mBuffer
; /// The buffer used to store reordered rows.
311 int32_t mInputRow
; /// The current row we're reading. (0-indexed)
312 int32_t mOutputRow
; /// The current row we're writing. (0-indexed)
313 uint8_t mPass
; /// Which pass we're on. (0-indexed)
314 bool mProgressiveDisplay
; /// If true, duplicate rows to optimize for
315 /// progressive display.
318 //////////////////////////////////////////////////////////////////////////////
319 // BlendAnimationFilter
320 //////////////////////////////////////////////////////////////////////////////
322 template <typename Next
>
323 class BlendAnimationFilter
;
326 * A configuration struct for BlendAnimationFilter.
328 struct BlendAnimationConfig
{
329 template <typename Next
>
330 using Filter
= BlendAnimationFilter
<Next
>;
331 Decoder
* mDecoder
; /// The decoder producing the animation.
335 * BlendAnimationFilter turns a partial image as part of an animation into a
336 * complete frame given its frame rect, blend method, and the base frame's
337 * data buffer, frame rect and disposal method. Any excess data caused by a
338 * frame rect not being contained by the output size will be discarded.
340 * The base frame is an already produced complete frame from the animation.
341 * It may be any previous frame depending on the disposal method, although
342 * most often it will be the immediate previous frame to the current we are
345 * The 'Next' template parameter specifies the next filter in the chain.
347 template <typename Next
>
348 class BlendAnimationFilter final
: public SurfaceFilter
{
350 BlendAnimationFilter()
355 mRecycleRowOffset(0),
356 mRecycleRowLength(0),
359 mClearPrefixLength(0),
360 mClearInfixOffset(0),
361 mClearInfixLength(0),
362 mClearPostfixOffset(0),
363 mClearPostfixLength(0),
365 mBaseFrameStartPtr(nullptr),
366 mBaseFrameRowPtr(nullptr) {}
368 template <typename
... Rest
>
369 nsresult
Configure(const BlendAnimationConfig
& aConfig
,
370 const Rest
&... aRest
) {
371 nsresult rv
= mNext
.Configure(aRest
...);
376 if (!aConfig
.mDecoder
|| !aConfig
.mDecoder
->ShouldBlendAnimation()) {
377 MOZ_ASSERT_UNREACHABLE("Expected image decoder that is blending!");
378 return NS_ERROR_INVALID_ARG
;
381 imgFrame
* currentFrame
= aConfig
.mDecoder
->GetCurrentFrame();
383 MOZ_ASSERT_UNREACHABLE("Decoder must have current frame!");
384 return NS_ERROR_FAILURE
;
387 mFrameRect
= mUnclampedFrameRect
= currentFrame
->GetBlendRect();
388 gfx::IntSize outputSize
= mNext
.InputSize();
389 mRowLength
= outputSize
.width
* sizeof(uint32_t);
391 // Forbid frame rects with negative size.
392 if (mUnclampedFrameRect
.width
< 0 || mUnclampedFrameRect
.height
< 0) {
393 return NS_ERROR_FAILURE
;
396 // Clamp mFrameRect to the output size.
397 gfx::IntRect
outputRect(0, 0, outputSize
.width
, outputSize
.height
);
398 mFrameRect
= mFrameRect
.Intersect(outputRect
);
399 bool fullFrame
= outputRect
.IsEqualEdges(mFrameRect
);
401 // If there's no intersection, |mFrameRect| will be an empty rect positioned
402 // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
403 // not what we want. Force it to (0, 0) sized 0 x 0 in that case.
404 if (mFrameRect
.IsEmpty()) {
405 mFrameRect
.SetRect(0, 0, 0, 0);
408 BlendMethod blendMethod
= currentFrame
->GetBlendMethod();
409 switch (blendMethod
) {
411 blendMethod
= BlendMethod::SOURCE
;
412 MOZ_FALLTHROUGH_ASSERT("Unexpected blend method!");
413 case BlendMethod::SOURCE
:
414 // Default, overwrites base frame data (if any) with new.
416 case BlendMethod::OVER
:
417 // OVER only has an impact on the output if we have new data to blend
419 if (mFrameRect
.IsEmpty()) {
420 blendMethod
= BlendMethod::SOURCE
;
425 // Determine what we need to clear and what we need to copy. If this frame
426 // is a full frame and uses source blending, there is no need to consider
427 // the disposal method of the previous frame.
428 gfx::IntRect
dirtyRect(outputRect
);
429 gfx::IntRect clearRect
;
430 if (!fullFrame
|| blendMethod
!= BlendMethod::SOURCE
) {
431 const RawAccessFrameRef
& restoreFrame
=
432 aConfig
.mDecoder
->GetRestoreFrameRef();
434 MOZ_ASSERT(restoreFrame
->GetImageSize() == outputSize
);
435 MOZ_ASSERT(restoreFrame
->IsFinished());
437 // We can safely use this pointer without holding a RawAccessFrameRef
438 // because the decoder will keep it alive for us.
439 mBaseFrameStartPtr
= restoreFrame
.Data();
440 MOZ_ASSERT(mBaseFrameStartPtr
);
442 gfx::IntRect restoreBlendRect
= restoreFrame
->GetBoundedBlendRect();
443 gfx::IntRect restoreDirtyRect
= aConfig
.mDecoder
->GetRestoreDirtyRect();
444 switch (restoreFrame
->GetDisposalMethod()) {
446 case DisposalMethod::RESTORE_PREVIOUS
:
447 MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
448 case DisposalMethod::NOT_SPECIFIED
:
449 case DisposalMethod::KEEP
:
450 dirtyRect
= mFrameRect
.Union(restoreDirtyRect
);
452 case DisposalMethod::CLEAR
:
453 // We only need to clear if the rect is outside the frame rect (i.e.
454 // overwrites a non-overlapping area) or the blend method may cause
455 // us to combine old data and new.
456 if (!mFrameRect
.Contains(restoreBlendRect
) ||
457 blendMethod
== BlendMethod::OVER
) {
458 clearRect
= restoreBlendRect
;
461 // If we are clearing the whole frame, we do not need to retain a
462 // reference to the base frame buffer.
463 if (outputRect
.IsEqualEdges(clearRect
)) {
464 mBaseFrameStartPtr
= nullptr;
466 dirtyRect
= mFrameRect
.Union(restoreDirtyRect
).Union(clearRect
);
470 } else if (!fullFrame
) {
471 // This must be the first frame, clear everything.
472 clearRect
= outputRect
;
476 // We may be able to reuse parts of our underlying buffer that we are
477 // writing the new frame to. The recycle rect gives us the invalidation
478 // region which needs to be copied from the restore frame.
479 const gfx::IntRect
& recycleRect
= aConfig
.mDecoder
->GetRecycleRect();
480 mRecycleRow
= recycleRect
.y
;
481 mRecycleRowMost
= recycleRect
.YMost();
482 mRecycleRowOffset
= recycleRect
.x
* sizeof(uint32_t);
483 mRecycleRowLength
= recycleRect
.width
* sizeof(uint32_t);
485 if (!clearRect
.IsEmpty()) {
486 // The clear rect interacts with the recycle rect because we need to copy
487 // the prefix and postfix data from the base frame. The one thing we do
488 // know is that the infix area is always cleared explicitly.
489 mClearRow
= clearRect
.y
;
490 mClearRowMost
= clearRect
.YMost();
491 mClearInfixOffset
= clearRect
.x
* sizeof(uint32_t);
492 mClearInfixLength
= clearRect
.width
* sizeof(uint32_t);
494 // The recycle row offset is where we need to begin copying base frame
495 // data for a row. If this offset begins after or at the clear infix
496 // offset, then there is no prefix data at all.
497 if (mClearInfixOffset
> mRecycleRowOffset
) {
498 mClearPrefixLength
= mClearInfixOffset
- mRecycleRowOffset
;
501 // Similar to the prefix, if the postfix offset begins outside the recycle
502 // rect, then we know we already have all the data we need.
503 mClearPostfixOffset
= mClearInfixOffset
+ mClearInfixLength
;
504 size_t recycleRowEndOffset
= mRecycleRowOffset
+ mRecycleRowLength
;
505 if (mClearPostfixOffset
< recycleRowEndOffset
) {
506 mClearPostfixLength
= recycleRowEndOffset
- mClearPostfixOffset
;
510 // The dirty rect, or delta between the current frame and the previous frame
511 // (chronologically, not necessarily the restore frame) is the last
512 // animation parameter we need to initialize the new frame with.
513 currentFrame
->SetDirtyRect(dirtyRect
);
515 if (!mBaseFrameStartPtr
) {
516 // Switch to SOURCE if no base frame to ensure we don't allocate an
517 // intermediate buffer below. OVER does nothing without the base frame
519 blendMethod
= BlendMethod::SOURCE
;
522 // Skia provides arch-specific accelerated methods to perform blending.
523 // Note that this is an internal Skia API and may be prone to change,
524 // but we avoid the overhead of setting up Skia objects.
525 if (blendMethod
== BlendMethod::OVER
) {
526 mOverProc
= SkBlitRow::Factory32(SkBlitRow::kSrcPixelAlpha_Flag32
);
527 MOZ_ASSERT(mOverProc
);
530 // We don't need an intermediate buffer unless the unclamped frame rect
531 // width is larger than the clamped frame rect width. In that case, the
532 // caller will end up writing data that won't end up in the final image at
533 // all, and we'll need a buffer to give that data a place to go.
534 if (mFrameRect
.width
< mUnclampedFrameRect
.width
|| mOverProc
) {
535 mBuffer
.reset(new (fallible
)
536 uint8_t[mUnclampedFrameRect
.width
* sizeof(uint32_t)]);
537 if (MOZ_UNLIKELY(!mBuffer
)) {
538 return NS_ERROR_OUT_OF_MEMORY
;
541 memset(mBuffer
.get(), 0, mUnclampedFrameRect
.width
* sizeof(uint32_t));
544 ConfigureFilter(mUnclampedFrameRect
.Size(), sizeof(uint32_t));
548 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
549 return mNext
.TakeInvalidRect();
553 uint8_t* DoResetToFirstRow() override
{
554 uint8_t* rowPtr
= mNext
.ResetToFirstRow();
555 if (rowPtr
== nullptr) {
556 mRow
= mFrameRect
.YMost();
561 mBaseFrameRowPtr
= mBaseFrameStartPtr
;
563 while (mRow
< mFrameRect
.y
) {
565 AdvanceRowOutsideFrameRect();
568 // We're at the beginning of the frame rect now, so return if we're either
569 // ready for input or we're already done.
570 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
571 if (!mFrameRect
.IsEmpty() || rowPtr
== nullptr) {
572 // Note that the pointer we're returning is for the next row we're
573 // actually going to write to, but we may discard writes before that point
574 // if mRow < mFrameRect.y.
575 mRow
= mUnclampedFrameRect
.y
;
577 return AdjustRowPointer(rowPtr
);
580 // We've finished the region specified by the frame rect, but the frame rect
581 // is empty, so we need to output the rest of the image immediately. Advance
582 // to the end of the next pipeline stage's buffer, outputting rows that are
583 // copied from the base frame and/or cleared.
584 WriteBaseFrameRowsUntilComplete();
586 mRow
= mFrameRect
.YMost();
587 return nullptr; // We're done.
590 uint8_t* DoAdvanceRow() override
{
591 uint8_t* rowPtr
= nullptr;
593 const int32_t currentRow
= mRow
;
596 // The unclamped frame rect has a negative offset which means -y rows from
597 // the decoder need to be discarded before we advance properly.
598 if (currentRow
>= 0 && mBaseFrameRowPtr
) {
599 mBaseFrameRowPtr
+= mRowLength
;
602 if (currentRow
< mFrameRect
.y
) {
603 // This row is outside of the frame rect, so just drop it on the floor.
604 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
605 return AdjustRowPointer(rowPtr
);
606 } else if (NS_WARN_IF(currentRow
>= mFrameRect
.YMost())) {
610 // If we had to buffer, merge the data into the row. Otherwise we had the
611 // decoder write directly to the next stage's buffer.
613 int32_t width
= mFrameRect
.width
;
614 uint32_t* dst
= reinterpret_cast<uint32_t*>(mNext
.CurrentRowPointer());
615 uint32_t* src
= reinterpret_cast<uint32_t*>(mBuffer
.get()) -
616 std::min(mUnclampedFrameRect
.x
, 0);
619 mOverProc(dst
, src
, width
, 0xFF);
621 memcpy(dst
, src
, width
* sizeof(uint32_t));
623 rowPtr
= mNext
.AdvanceRow() ? mBuffer
.get() : nullptr;
625 MOZ_ASSERT(!mOverProc
);
626 rowPtr
= mNext
.AdvanceRow();
629 // If there's still more data coming or we're already done, just adjust the
630 // pointer and return.
631 if (mRow
< mFrameRect
.YMost() || rowPtr
== nullptr) {
633 return AdjustRowPointer(rowPtr
);
636 // We've finished the region specified by the frame rect. Advance to the end
637 // of the next pipeline stage's buffer, outputting rows that are copied from
638 // the base frame and/or cleared.
639 WriteBaseFrameRowsUntilComplete();
641 return nullptr; // We're done.
645 void WriteBaseFrameRowsUntilComplete() {
648 } while (AdvanceRowOutsideFrameRect());
651 void WriteBaseFrameRow() {
652 uint8_t* dest
= mNext
.CurrentRowPointer();
657 // No need to copy pixels from the base frame for rows that will not change
658 // between the recycled frame and the new frame.
659 bool needBaseFrame
= mRow
>= mRecycleRow
&& mRow
< mRecycleRowMost
;
661 if (!mBaseFrameRowPtr
) {
662 // No base frame, so we are clearing everything.
664 memset(dest
+ mRecycleRowOffset
, 0, mRecycleRowLength
);
666 } else if (mClearRow
<= mRow
&& mClearRowMost
> mRow
) {
667 // We have a base frame, but we are inside the area to be cleared.
668 // Only copy the data we need from the source.
670 memcpy(dest
+ mRecycleRowOffset
, mBaseFrameRowPtr
+ mRecycleRowOffset
,
672 memcpy(dest
+ mClearPostfixOffset
,
673 mBaseFrameRowPtr
+ mClearPostfixOffset
, mClearPostfixLength
);
675 memset(dest
+ mClearInfixOffset
, 0, mClearInfixLength
);
676 } else if (needBaseFrame
) {
677 memcpy(dest
+ mRecycleRowOffset
, mBaseFrameRowPtr
+ mRecycleRowOffset
,
682 bool AdvanceRowOutsideFrameRect() {
683 // The unclamped frame rect may have a negative offset however we should
684 // never be advancing the row via this path (otherwise mBaseFrameRowPtr
686 MOZ_ASSERT(mRow
>= 0);
687 MOZ_ASSERT(mRow
< mFrameRect
.y
|| mRow
>= mFrameRect
.YMost());
690 if (mBaseFrameRowPtr
) {
691 mBaseFrameRowPtr
+= mRowLength
;
694 return mNext
.AdvanceRow() != nullptr;
697 uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer
) const {
699 MOZ_ASSERT(aNextRowPointer
== mBuffer
.get() ||
700 aNextRowPointer
== nullptr);
701 return aNextRowPointer
; // No adjustment needed for an intermediate
705 if (mFrameRect
.IsEmpty() || mRow
>= mFrameRect
.YMost() ||
706 aNextRowPointer
== nullptr) {
707 return nullptr; // Nothing left to write.
710 MOZ_ASSERT(!mOverProc
);
711 return aNextRowPointer
+ mFrameRect
.x
* sizeof(uint32_t);
714 Next mNext
; /// The next SurfaceFilter in the chain.
716 gfx::IntRect mFrameRect
; /// The surface subrect which contains data,
717 /// clamped to the image size.
718 gfx::IntRect mUnclampedFrameRect
; /// The frame rect before clamping.
719 UniquePtr
<uint8_t[]> mBuffer
; /// The intermediate buffer, if one is
720 /// necessary because the frame rect width
721 /// is larger than the image's logical width.
722 int32_t mRow
; /// The row in unclamped frame rect space
723 /// that we're currently writing.
724 size_t mRowLength
; /// Length in bytes of a row that is the input
725 /// for the next filter.
726 int32_t mRecycleRow
; /// The starting row of the recycle rect.
727 int32_t mRecycleRowMost
; /// The ending row of the recycle rect.
728 size_t mRecycleRowOffset
; /// Row offset in bytes of the recycle rect.
729 size_t mRecycleRowLength
; /// Row length in bytes of the recycle rect.
731 /// The frame area to clear before blending the current frame.
732 int32_t mClearRow
; /// The starting row of the clear rect.
733 int32_t mClearRowMost
; /// The ending row of the clear rect.
734 size_t mClearPrefixLength
; /// Row length in bytes of clear prefix.
735 size_t mClearInfixOffset
; /// Row offset in bytes of clear area.
736 size_t mClearInfixLength
; /// Row length in bytes of clear area.
737 size_t mClearPostfixOffset
; /// Row offset in bytes of clear postfix.
738 size_t mClearPostfixLength
; /// Row length in bytes of clear postfix.
740 SkBlitRow::Proc32 mOverProc
; /// Function pointer to perform over blending.
742 mBaseFrameStartPtr
; /// Starting row pointer to the base frame
743 /// data from which we copy pixel data from.
744 const uint8_t* mBaseFrameRowPtr
; /// Current row pointer to the base frame
748 //////////////////////////////////////////////////////////////////////////////
749 // RemoveFrameRectFilter
750 //////////////////////////////////////////////////////////////////////////////
752 template <typename Next
>
753 class RemoveFrameRectFilter
;
756 * A configuration struct for RemoveFrameRectFilter.
758 struct RemoveFrameRectConfig
{
759 template <typename Next
>
760 using Filter
= RemoveFrameRectFilter
<Next
>;
761 gfx::IntRect mFrameRect
; /// The surface subrect which contains data.
765 * RemoveFrameRectFilter turns an image with a frame rect that does not match
766 * its logical size into an image with no frame rect. It does this by writing
767 * transparent pixels into any padding regions and throwing away excess data.
769 * The 'Next' template parameter specifies the next filter in the chain.
771 template <typename Next
>
772 class RemoveFrameRectFilter final
: public SurfaceFilter
{
774 RemoveFrameRectFilter() : mRow(0) {}
776 template <typename
... Rest
>
777 nsresult
Configure(const RemoveFrameRectConfig
& aConfig
,
778 const Rest
&... aRest
) {
779 nsresult rv
= mNext
.Configure(aRest
...);
784 if (mNext
.IsValidPalettedPipe()) {
785 NS_WARNING("RemoveFrameRectFilter used with paletted pipe?");
786 return NS_ERROR_INVALID_ARG
;
789 mFrameRect
= mUnclampedFrameRect
= aConfig
.mFrameRect
;
790 gfx::IntSize outputSize
= mNext
.InputSize();
792 // Forbid frame rects with negative size.
793 if (aConfig
.mFrameRect
.Width() < 0 || aConfig
.mFrameRect
.Height() < 0) {
794 return NS_ERROR_INVALID_ARG
;
797 // Clamp mFrameRect to the output size.
798 gfx::IntRect
outputRect(0, 0, outputSize
.width
, outputSize
.height
);
799 mFrameRect
= mFrameRect
.Intersect(outputRect
);
801 // If there's no intersection, |mFrameRect| will be an empty rect positioned
802 // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
803 // not what we want. Force it to (0, 0) in that case.
804 if (mFrameRect
.IsEmpty()) {
805 mFrameRect
.MoveTo(0, 0);
808 // We don't need an intermediate buffer unless the unclamped frame rect
809 // width is larger than the clamped frame rect width. In that case, the
810 // caller will end up writing data that won't end up in the final image at
811 // all, and we'll need a buffer to give that data a place to go.
812 if (mFrameRect
.Width() < mUnclampedFrameRect
.Width()) {
814 fallible
) uint8_t[mUnclampedFrameRect
.Width() * sizeof(uint32_t)]);
815 if (MOZ_UNLIKELY(!mBuffer
)) {
816 return NS_ERROR_OUT_OF_MEMORY
;
819 memset(mBuffer
.get(), 0, mUnclampedFrameRect
.Width() * sizeof(uint32_t));
822 ConfigureFilter(mUnclampedFrameRect
.Size(), sizeof(uint32_t));
826 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
827 return mNext
.TakeInvalidRect();
831 uint8_t* DoResetToFirstRow() override
{
832 uint8_t* rowPtr
= mNext
.ResetToFirstRow();
833 if (rowPtr
== nullptr) {
834 mRow
= mFrameRect
.YMost();
838 mRow
= mUnclampedFrameRect
.Y();
840 // Advance the next pipeline stage to the beginning of the frame rect,
841 // outputting blank rows.
842 if (mFrameRect
.Y() > 0) {
843 for (int32_t rowToOutput
= 0; rowToOutput
< mFrameRect
.Y();
845 mNext
.WriteEmptyRow();
849 // We're at the beginning of the frame rect now, so return if we're either
850 // ready for input or we're already done.
851 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
852 if (!mFrameRect
.IsEmpty() || rowPtr
== nullptr) {
853 // Note that the pointer we're returning is for the next row we're
854 // actually going to write to, but we may discard writes before that point
855 // if mRow < mFrameRect.y.
856 return AdjustRowPointer(rowPtr
);
859 // We've finished the region specified by the frame rect, but the frame rect
860 // is empty, so we need to output the rest of the image immediately. Advance
861 // to the end of the next pipeline stage's buffer, outputting blank rows.
862 while (mNext
.WriteEmptyRow() == WriteState::NEED_MORE_DATA
) {
865 mRow
= mFrameRect
.YMost();
866 return nullptr; // We're done.
869 uint8_t* DoAdvanceRow() override
{
870 uint8_t* rowPtr
= nullptr;
872 const int32_t currentRow
= mRow
;
875 if (currentRow
< mFrameRect
.Y()) {
876 // This row is outside of the frame rect, so just drop it on the floor.
877 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
878 return AdjustRowPointer(rowPtr
);
879 } else if (currentRow
>= mFrameRect
.YMost()) {
880 NS_WARNING("RemoveFrameRectFilter: Advancing past end of frame rect");
884 // If we had to buffer, copy the data. Otherwise, just advance the row.
886 // We write from the beginning of the buffer unless
887 // |mUnclampedFrameRect.x| is negative; if that's the case, we have to
888 // skip the portion of the unclamped frame rect that's outside the row.
889 uint32_t* source
= reinterpret_cast<uint32_t*>(mBuffer
.get()) -
890 std::min(mUnclampedFrameRect
.X(), 0);
892 // We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've
893 // already clamped these values to the size of the output, so we don't
894 // have to worry about bounds checking here (though WriteBuffer() will do
895 // it for us in any case).
897 mNext
.WriteBuffer(source
, mFrameRect
.X(), mFrameRect
.Width());
899 rowPtr
= state
== WriteState::NEED_MORE_DATA
? mBuffer
.get() : nullptr;
901 rowPtr
= mNext
.AdvanceRow();
904 // If there's still more data coming or we're already done, just adjust the
905 // pointer and return.
906 if (mRow
< mFrameRect
.YMost() || rowPtr
== nullptr) {
907 return AdjustRowPointer(rowPtr
);
910 // We've finished the region specified by the frame rect. Advance to the end
911 // of the next pipeline stage's buffer, outputting blank rows.
912 while (mNext
.WriteEmptyRow() == WriteState::NEED_MORE_DATA
) {
915 mRow
= mFrameRect
.YMost();
916 return nullptr; // We're done.
920 uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer
) const {
922 MOZ_ASSERT(aNextRowPointer
== mBuffer
.get() ||
923 aNextRowPointer
== nullptr);
924 return aNextRowPointer
; // No adjustment needed for an intermediate
928 if (mFrameRect
.IsEmpty() || mRow
>= mFrameRect
.YMost() ||
929 aNextRowPointer
== nullptr) {
930 return nullptr; // Nothing left to write.
933 return aNextRowPointer
+ mFrameRect
.X() * sizeof(uint32_t);
936 Next mNext
; /// The next SurfaceFilter in the chain.
938 gfx::IntRect mFrameRect
; /// The surface subrect which contains data,
939 /// clamped to the image size.
940 gfx::IntRect mUnclampedFrameRect
; /// The frame rect before clamping.
941 UniquePtr
<uint8_t[]> mBuffer
; /// The intermediate buffer, if one is
942 /// necessary because the frame rect width
943 /// is larger than the image's logical width.
944 int32_t mRow
; /// The row in unclamped frame rect space
945 /// that we're currently writing.
948 //////////////////////////////////////////////////////////////////////////////
949 // ADAM7InterpolatingFilter
950 //////////////////////////////////////////////////////////////////////////////
952 template <typename Next
>
953 class ADAM7InterpolatingFilter
;
956 * A configuration struct for ADAM7InterpolatingFilter.
958 struct ADAM7InterpolatingConfig
{
959 template <typename Next
>
960 using Filter
= ADAM7InterpolatingFilter
<Next
>;
964 * ADAM7InterpolatingFilter performs bilinear interpolation over an ADAM7
967 * ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new set
968 * of pixels in each block receives their final values, according to the
980 * When rendering the pixels that have not yet received their final values, we
981 * can get much better intermediate results if we interpolate between
982 * the pixels we *have* gotten so far. This filter performs bilinear
983 * interpolation by first performing linear interpolation horizontally for each
984 * "important" row (which we'll define as a row that has received any pixels
985 * with final values at all) and then performing linear interpolation vertically
986 * to produce pixel values for rows which aren't important on the current pass.
988 * Note that this filter totally ignores the data which is written to rows which
989 * aren't important on the current pass! It's fine to write nothing at all for
990 * these rows, although doing so won't cause any harm.
992 * XXX(seth): In bug 1280552 we'll add a SIMD implementation for this filter.
994 * The 'Next' template parameter specifies the next filter in the chain.
996 template <typename Next
>
997 class ADAM7InterpolatingFilter final
: public SurfaceFilter
{
999 ADAM7InterpolatingFilter()
1000 : mPass(0) // The current pass, in the range 1..7. Starts at 0 so that
1001 // DoResetToFirstRow() doesn't have to special case the first
1006 template <typename
... Rest
>
1007 nsresult
Configure(const ADAM7InterpolatingConfig
& aConfig
,
1008 const Rest
&... aRest
) {
1009 nsresult rv
= mNext
.Configure(aRest
...);
1010 if (NS_FAILED(rv
)) {
1014 if (mNext
.IsValidPalettedPipe()) {
1015 NS_WARNING("ADAM7InterpolatingFilter used with paletted pipe?");
1016 return NS_ERROR_INVALID_ARG
;
1019 // We have two intermediate buffers, one for the previous row with final
1020 // pixel values and one for the row that the previous filter in the chain is
1021 // currently writing to.
1022 size_t inputWidthInBytes
= mNext
.InputSize().width
* sizeof(uint32_t);
1023 mPreviousRow
.reset(new (fallible
) uint8_t[inputWidthInBytes
]);
1024 if (MOZ_UNLIKELY(!mPreviousRow
)) {
1025 return NS_ERROR_OUT_OF_MEMORY
;
1028 mCurrentRow
.reset(new (fallible
) uint8_t[inputWidthInBytes
]);
1029 if (MOZ_UNLIKELY(!mCurrentRow
)) {
1030 return NS_ERROR_OUT_OF_MEMORY
;
1033 memset(mPreviousRow
.get(), 0, inputWidthInBytes
);
1034 memset(mCurrentRow
.get(), 0, inputWidthInBytes
);
1036 ConfigureFilter(mNext
.InputSize(), sizeof(uint32_t));
1040 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
1041 return mNext
.TakeInvalidRect();
1045 uint8_t* DoResetToFirstRow() override
{
1047 mPass
= std::min(mPass
+ 1, 7);
1049 uint8_t* rowPtr
= mNext
.ResetToFirstRow();
1051 // Short circuit this filter on the final pass, since all pixels have
1052 // their final values at that point.
1056 return mCurrentRow
.get();
1059 uint8_t* DoAdvanceRow() override
{
1060 MOZ_ASSERT(0 < mPass
&& mPass
<= 7, "Invalid pass");
1062 int32_t currentRow
= mRow
;
1066 // On the final pass we short circuit this filter totally.
1067 return mNext
.AdvanceRow();
1070 const int32_t lastImportantRow
=
1071 LastImportantRow(InputSize().height
, mPass
);
1072 if (currentRow
> lastImportantRow
) {
1073 return nullptr; // This pass is already complete.
1076 if (!IsImportantRow(currentRow
, mPass
)) {
1077 // We just ignore whatever the caller gives us for these rows. We'll
1078 // interpolate them in later.
1079 return mCurrentRow
.get();
1082 // This is an important row. We need to perform horizontal interpolation for
1084 InterpolateHorizontally(mCurrentRow
.get(), InputSize().width
, mPass
);
1086 // Interpolate vertically between the previous important row and the current
1087 // important row. We skip this if the current row is 0 (which is always an
1088 // important row), because in that case there is no previous important row
1089 // to interpolate with.
1090 if (currentRow
!= 0) {
1091 InterpolateVertically(mPreviousRow
.get(), mCurrentRow
.get(), mPass
,
1095 // Write out the current row itself, which, being an important row, does not
1096 // need vertical interpolation.
1097 uint32_t* currentRowAsPixels
=
1098 reinterpret_cast<uint32_t*>(mCurrentRow
.get());
1099 mNext
.WriteBuffer(currentRowAsPixels
);
1101 if (currentRow
== lastImportantRow
) {
1102 // This is the last important row, which completes this pass. Note that
1103 // for very small images, this may be the first row! Since there won't be
1104 // another important row, there's nothing to interpolate with vertically,
1105 // so we just duplicate this row until the end of the image.
1106 while (mNext
.WriteBuffer(currentRowAsPixels
) ==
1107 WriteState::NEED_MORE_DATA
) {
1110 // All of the remaining rows in the image were determined above, so we're
1115 // The current row is now the previous important row; save it.
1116 Swap(mPreviousRow
, mCurrentRow
);
1118 MOZ_ASSERT(mRow
< InputSize().height
,
1119 "Reached the end of the surface without "
1120 "hitting the last important row?");
1122 return mCurrentRow
.get();
1126 static void InterpolateVertically(uint8_t* aPreviousRow
, uint8_t* aCurrentRow
,
1127 uint8_t aPass
, SurfaceFilter
& aNext
) {
1128 const float* weights
= InterpolationWeights(ImportantRowStride(aPass
));
1130 // We need to interpolate vertically to generate the rows between the
1131 // previous important row and the next one. Recall that important rows are
1132 // rows which contain at least some final pixels; see
1133 // InterpolateHorizontally() for some additional explanation as to what that
1134 // means. Note that we've already written out the previous important row, so
1135 // we start the iteration at 1.
1136 for (int32_t outRow
= 1; outRow
< ImportantRowStride(aPass
); ++outRow
) {
1137 const float weight
= weights
[outRow
];
1139 // We iterate through the previous and current important row every time we
1140 // write out an interpolated row, so we need to copy the pointers.
1141 uint8_t* prevRowBytes
= aPreviousRow
;
1142 uint8_t* currRowBytes
= aCurrentRow
;
1144 // Write out the interpolated pixels. Interpolation is componentwise.
1145 aNext
.template WritePixelsToRow
<uint32_t>([&] {
1147 auto* component
= reinterpret_cast<uint8_t*>(&pixel
);
1149 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1151 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1153 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1155 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1156 return AsVariant(pixel
);
1161 static void InterpolateHorizontally(uint8_t* aRow
, int32_t aWidth
,
1163 // Collect the data we'll need to perform horizontal interpolation. The
1164 // terminology here bears some explanation: a "final pixel" is a pixel which
1165 // has received its final value. On each pass, a new set of pixels receives
1166 // their final value; see the diagram above of the 8x8 pattern that ADAM7
1167 // uses. Any pixel which hasn't received its final value on this pass
1168 // derives its value from either horizontal or vertical interpolation
1170 const size_t finalPixelStride
= FinalPixelStride(aPass
);
1171 const size_t finalPixelStrideBytes
= finalPixelStride
* sizeof(uint32_t);
1172 const size_t lastFinalPixel
= LastFinalPixel(aWidth
, aPass
);
1173 const size_t lastFinalPixelBytes
= lastFinalPixel
* sizeof(uint32_t);
1174 const float* weights
= InterpolationWeights(finalPixelStride
);
1176 // Interpolate blocks of pixels which lie between two final pixels.
1177 // Horizontal interpolation is done in place, as we'll need the results
1178 // later when we vertically interpolate.
1179 for (size_t blockBytes
= 0; blockBytes
< lastFinalPixelBytes
;
1180 blockBytes
+= finalPixelStrideBytes
) {
1181 uint8_t* finalPixelA
= aRow
+ blockBytes
;
1182 uint8_t* finalPixelB
= aRow
+ blockBytes
+ finalPixelStrideBytes
;
1184 MOZ_ASSERT(finalPixelA
< aRow
+ aWidth
* sizeof(uint32_t),
1185 "Running off end of buffer");
1186 MOZ_ASSERT(finalPixelB
< aRow
+ aWidth
* sizeof(uint32_t),
1187 "Running off end of buffer");
1189 // Interpolate the individual pixels componentwise. Note that we start
1190 // iteration at 1 since we don't need to apply any interpolation to the
1191 // first pixel in the block, which has its final value.
1192 for (size_t pixelIndex
= 1; pixelIndex
< finalPixelStride
; ++pixelIndex
) {
1193 const float weight
= weights
[pixelIndex
];
1194 uint8_t* pixel
= aRow
+ blockBytes
+ pixelIndex
* sizeof(uint32_t);
1196 MOZ_ASSERT(pixel
< aRow
+ aWidth
* sizeof(uint32_t),
1197 "Running off end of buffer");
1199 for (size_t component
= 0; component
< sizeof(uint32_t); ++component
) {
1200 pixel
[component
] = InterpolateByte(finalPixelA
[component
],
1201 finalPixelB
[component
], weight
);
1206 // For the pixels after the last final pixel in the row, there isn't a
1207 // second final pixel to interpolate with, so just duplicate.
1208 uint32_t* rowPixels
= reinterpret_cast<uint32_t*>(aRow
);
1209 uint32_t pixelToDuplicate
= rowPixels
[lastFinalPixel
];
1210 for (int32_t pixelIndex
= lastFinalPixel
+ 1; pixelIndex
< aWidth
;
1212 MOZ_ASSERT(pixelIndex
< aWidth
, "Running off end of buffer");
1213 rowPixels
[pixelIndex
] = pixelToDuplicate
;
1217 static uint8_t InterpolateByte(uint8_t aByteA
, uint8_t aByteB
,
1219 return uint8_t(aByteA
* aWeight
+ aByteB
* (1.0f
- aWeight
));
1222 static int32_t ImportantRowStride(uint8_t aPass
) {
1223 MOZ_ASSERT(0 < aPass
&& aPass
<= 7, "Invalid pass");
1225 // The stride between important rows for each pass, with a dummy value for
1226 // the nonexistent pass 0.
1227 static int32_t strides
[] = {1, 8, 8, 4, 4, 2, 2, 1};
1229 return strides
[aPass
];
1232 static bool IsImportantRow(int32_t aRow
, uint8_t aPass
) {
1233 MOZ_ASSERT(aRow
>= 0);
1235 // Whether the row is important comes down to divisibility by the stride for
1236 // this pass, which is always a power of 2, so we can check using a mask.
1237 int32_t mask
= ImportantRowStride(aPass
) - 1;
1238 return (aRow
& mask
) == 0;
1241 static int32_t LastImportantRow(int32_t aHeight
, uint8_t aPass
) {
1242 MOZ_ASSERT(aHeight
> 0);
1244 // We can find the last important row using the same mask trick as above.
1245 int32_t lastRow
= aHeight
- 1;
1246 int32_t mask
= ImportantRowStride(aPass
) - 1;
1247 return lastRow
- (lastRow
& mask
);
1250 static size_t FinalPixelStride(uint8_t aPass
) {
1251 MOZ_ASSERT(0 < aPass
&& aPass
<= 7, "Invalid pass");
1253 // The stride between the final pixels in important rows for each pass, with
1254 // a dummy value for the nonexistent pass 0.
1255 static size_t strides
[] = {1, 8, 4, 4, 2, 2, 1, 1};
1257 return strides
[aPass
];
1260 static size_t LastFinalPixel(int32_t aWidth
, uint8_t aPass
) {
1261 MOZ_ASSERT(aWidth
>= 0);
1263 // Again, we can use the mask trick above to find the last important pixel.
1264 int32_t lastColumn
= aWidth
- 1;
1265 size_t mask
= FinalPixelStride(aPass
) - 1;
1266 return lastColumn
- (lastColumn
& mask
);
1269 static const float* InterpolationWeights(int32_t aStride
) {
1270 // Precalculated interpolation weights. These are used to interpolate
1271 // between final pixels or between important rows. Although no interpolation
1272 // is actually applied to the previous final pixel or important row value,
1273 // the arrays still start with 1.0f, which is always skipped, primarily
1274 // because otherwise |stride1Weights| would have zero elements.
1275 static float stride8Weights
[] = {1.0f
, 7 / 8.0f
, 6 / 8.0f
, 5 / 8.0f
,
1276 4 / 8.0f
, 3 / 8.0f
, 2 / 8.0f
, 1 / 8.0f
};
1277 static float stride4Weights
[] = {1.0f
, 3 / 4.0f
, 2 / 4.0f
, 1 / 4.0f
};
1278 static float stride2Weights
[] = {1.0f
, 1 / 2.0f
};
1279 static float stride1Weights
[] = {1.0f
};
1283 return stride8Weights
;
1285 return stride4Weights
;
1287 return stride2Weights
;
1289 return stride1Weights
;
1295 Next mNext
; /// The next SurfaceFilter in the chain.
1297 UniquePtr
<uint8_t[]>
1298 mPreviousRow
; /// The last important row (i.e., row with
1299 /// final pixel values) that got written to.
1300 UniquePtr
<uint8_t[]> mCurrentRow
; /// The row that's being written to right
1302 uint8_t mPass
; /// Which ADAM7 pass we're on. Valid passes
1303 /// are 1..7 during processing and 0 prior
1304 /// to configuraiton.
1305 int32_t mRow
; /// The row we're currently reading.
1308 } // namespace image
1309 } // namespace mozilla
1311 #endif // mozilla_image_SurfaceFilters_h