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 // ColorManagementFilter
34 //////////////////////////////////////////////////////////////////////////////
36 template <typename Next
>
37 class ColorManagementFilter
;
40 * A configuration struct for ColorManagementFilter.
42 struct ColorManagementConfig
{
43 template <typename Next
>
44 using Filter
= ColorManagementFilter
<Next
>;
45 qcms_transform
* mTransform
;
49 * ColorManagementFilter performs color transforms with qcms on rows written
52 * The 'Next' template parameter specifies the next filter in the chain.
54 template <typename Next
>
55 class ColorManagementFilter final
: public SurfaceFilter
{
57 ColorManagementFilter() : mTransform(nullptr) {}
59 template <typename
... Rest
>
60 nsresult
Configure(const ColorManagementConfig
& aConfig
,
61 const Rest
&... aRest
) {
62 nsresult rv
= mNext
.Configure(aRest
...);
67 if (!aConfig
.mTransform
) {
68 return NS_ERROR_INVALID_ARG
;
71 mTransform
= aConfig
.mTransform
;
72 ConfigureFilter(mNext
.InputSize(), sizeof(uint32_t));
76 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
77 return mNext
.TakeInvalidRect();
81 uint8_t* DoResetToFirstRow() override
{ return mNext
.ResetToFirstRow(); }
83 uint8_t* DoAdvanceRow() override
{
84 uint8_t* rowPtr
= mNext
.CurrentRowPointer();
85 qcms_transform_data(mTransform
, rowPtr
, rowPtr
, mNext
.InputSize().width
);
86 return mNext
.AdvanceRow();
89 Next mNext
; /// The next SurfaceFilter in the chain.
91 qcms_transform
* mTransform
;
94 //////////////////////////////////////////////////////////////////////////////
95 // DeinterlacingFilter
96 //////////////////////////////////////////////////////////////////////////////
98 template <typename PixelType
, typename Next
>
99 class DeinterlacingFilter
;
102 * A configuration struct for DeinterlacingFilter.
104 * The 'PixelType' template parameter should be either uint32_t (for output to a
105 * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
107 template <typename PixelType
>
108 struct DeinterlacingConfig
{
109 template <typename Next
>
110 using Filter
= DeinterlacingFilter
<PixelType
, Next
>;
111 bool mProgressiveDisplay
; /// If true, duplicate rows during deinterlacing
112 /// to make progressive display look better, at
113 /// the cost of some performance.
117 * DeinterlacingFilter performs deinterlacing by reordering the rows that are
120 * The 'PixelType' template parameter should be either uint32_t (for output to a
121 * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
123 * The 'Next' template parameter specifies the next filter in the chain.
125 template <typename PixelType
, typename Next
>
126 class DeinterlacingFilter final
: public SurfaceFilter
{
128 DeinterlacingFilter()
129 : mInputRow(0), mOutputRow(0), mPass(0), mProgressiveDisplay(true) {}
131 template <typename
... Rest
>
132 nsresult
Configure(const DeinterlacingConfig
<PixelType
>& aConfig
,
133 const Rest
&... aRest
) {
134 nsresult rv
= mNext
.Configure(aRest
...);
139 gfx::IntSize outputSize
= mNext
.InputSize();
140 mProgressiveDisplay
= aConfig
.mProgressiveDisplay
;
142 const uint32_t bufferSize
=
143 outputSize
.width
* outputSize
.height
* sizeof(PixelType
);
145 // Use the size of the SurfaceCache as a heuristic to avoid gigantic
146 // allocations. Even if DownscalingFilter allowed us to allocate space for
147 // the output image, the deinterlacing buffer may still be too big, and
148 // fallible allocation won't always save us in the presence of overcommit.
149 if (!SurfaceCache::CanHold(bufferSize
)) {
150 return NS_ERROR_OUT_OF_MEMORY
;
153 // Allocate the buffer, which contains deinterlaced scanlines of the image.
154 // The buffer is necessary so that we can output rows which have already
155 // been deinterlaced again on subsequent passes. Since a later stage in the
156 // pipeline may be transforming the rows it receives (for example, by
157 // downscaling them), the rows may no longer exist in their original form on
158 // the surface itself.
159 mBuffer
.reset(new (fallible
) uint8_t[bufferSize
]);
160 if (MOZ_UNLIKELY(!mBuffer
)) {
161 return NS_ERROR_OUT_OF_MEMORY
;
164 // Clear the buffer to avoid writing uninitialized memory to the output.
165 memset(mBuffer
.get(), 0, bufferSize
);
167 ConfigureFilter(outputSize
, sizeof(PixelType
));
171 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
172 return mNext
.TakeInvalidRect();
176 uint8_t* DoResetToFirstRow() override
{
177 mNext
.ResetToFirstRow();
180 mOutputRow
= InterlaceOffset(mPass
);
181 return GetRowPointer(mOutputRow
);
184 uint8_t* DoAdvanceRow() override
{
186 return nullptr; // We already finished all passes.
188 if (mInputRow
>= InputSize().height
) {
189 return nullptr; // We already got all the input rows we expect.
192 // Duplicate from the first Haeberli row to the remaining Haeberli rows
193 // within the buffer.
195 HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, mOutputRow
),
196 HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
199 // Write the current set of Haeberli rows (which contains the current row)
200 // to the next stage in the pipeline.
201 OutputRows(HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, mOutputRow
),
202 HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
205 // Determine which output row the next input row corresponds to.
206 bool advancedPass
= false;
207 uint32_t stride
= InterlaceStride(mPass
);
208 int32_t nextOutputRow
= mOutputRow
+ stride
;
209 while (nextOutputRow
>= InputSize().height
) {
210 // Copy any remaining rows from the buffer.
212 OutputRows(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
,
213 InputSize(), mOutputRow
),
217 // We finished the current pass; advance to the next one.
220 return nullptr; // Finished all passes.
223 // Tell the next pipeline stage that we're starting the next pass.
224 mNext
.ResetToFirstRow();
226 // Update our state to reflect the pass change.
228 stride
= InterlaceStride(mPass
);
229 nextOutputRow
= InterlaceOffset(mPass
);
232 MOZ_ASSERT(nextOutputRow
>= 0);
233 MOZ_ASSERT(nextOutputRow
< InputSize().height
);
236 HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, nextOutputRow
) >= 0);
237 MOZ_ASSERT(HaeberliOutputStartRow(mPass
, mProgressiveDisplay
,
238 nextOutputRow
) < InputSize().height
);
239 MOZ_ASSERT(HaeberliOutputStartRow(mPass
, mProgressiveDisplay
,
240 nextOutputRow
) <= nextOutputRow
);
242 MOZ_ASSERT(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
243 nextOutputRow
) >= 0);
244 MOZ_ASSERT(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
245 nextOutputRow
) <= InputSize().height
);
246 MOZ_ASSERT(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
247 nextOutputRow
) > nextOutputRow
);
249 int32_t nextHaeberliOutputRow
=
250 HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, nextOutputRow
);
252 // Copy rows from the buffer until we reach the desired output row.
254 OutputRows(0, nextHaeberliOutputRow
);
256 OutputRows(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
258 nextHaeberliOutputRow
);
261 // Update our position within the buffer.
263 mOutputRow
= nextOutputRow
;
265 // We'll actually write to the first Haeberli output row, then copy it until
266 // we reach the last Haeberli output row. The assertions above make sure
267 // this always includes mOutputRow.
268 return GetRowPointer(nextHaeberliOutputRow
);
272 static uint32_t InterlaceOffset(uint32_t aPass
) {
273 MOZ_ASSERT(aPass
< 4, "Invalid pass");
274 static const uint8_t offset
[] = {0, 4, 2, 1};
275 return offset
[aPass
];
278 static uint32_t InterlaceStride(uint32_t aPass
) {
279 MOZ_ASSERT(aPass
< 4, "Invalid pass");
280 static const uint8_t stride
[] = {8, 8, 4, 2};
281 return stride
[aPass
];
284 static int32_t HaeberliOutputStartRow(uint32_t aPass
,
285 bool aProgressiveDisplay
,
286 int32_t aOutputRow
) {
287 MOZ_ASSERT(aPass
< 4, "Invalid pass");
288 static const uint8_t firstRowOffset
[] = {3, 1, 0, 0};
290 if (aProgressiveDisplay
) {
291 return std::max(aOutputRow
- firstRowOffset
[aPass
], 0);
297 static int32_t HaeberliOutputUntilRow(uint32_t aPass
,
298 bool aProgressiveDisplay
,
299 const gfx::IntSize
& aInputSize
,
300 int32_t aOutputRow
) {
301 MOZ_ASSERT(aPass
< 4, "Invalid pass");
302 static const uint8_t lastRowOffset
[] = {4, 2, 1, 0};
304 if (aProgressiveDisplay
) {
305 return std::min(aOutputRow
+ lastRowOffset
[aPass
],
306 aInputSize
.height
- 1) +
307 1; // Add one because this is an open interval on the right.
309 return aOutputRow
+ 1;
313 void DuplicateRows(int32_t aStart
, int32_t aUntil
) {
314 MOZ_ASSERT(aStart
>= 0);
315 MOZ_ASSERT(aUntil
>= 0);
317 if (aUntil
<= aStart
|| aStart
>= InputSize().height
) {
321 // The source row is the first row in the range.
322 const uint8_t* sourceRowPointer
= GetRowPointer(aStart
);
324 // We duplicate the source row into each subsequent row in the range.
325 for (int32_t destRow
= aStart
+ 1; destRow
< aUntil
; ++destRow
) {
326 uint8_t* destRowPointer
= GetRowPointer(destRow
);
327 memcpy(destRowPointer
, sourceRowPointer
,
328 InputSize().width
* sizeof(PixelType
));
332 void OutputRows(int32_t aStart
, int32_t aUntil
) {
333 MOZ_ASSERT(aStart
>= 0);
334 MOZ_ASSERT(aUntil
>= 0);
336 if (aUntil
<= aStart
|| aStart
>= InputSize().height
) {
340 for (int32_t rowToOutput
= aStart
; rowToOutput
< aUntil
; ++rowToOutput
) {
342 reinterpret_cast<PixelType
*>(GetRowPointer(rowToOutput
)));
346 uint8_t* GetRowPointer(uint32_t aRow
) const {
347 uint32_t offset
= aRow
* InputSize().width
* sizeof(PixelType
);
349 offset
< InputSize().width
* InputSize().height
* sizeof(PixelType
),
350 "Start of row is outside of image");
351 MOZ_ASSERT(offset
+ InputSize().width
* sizeof(PixelType
) <=
352 InputSize().width
* InputSize().height
* sizeof(PixelType
),
353 "End of row is outside of image");
354 return mBuffer
.get() + offset
;
357 Next mNext
; /// The next SurfaceFilter in the chain.
359 UniquePtr
<uint8_t[]> mBuffer
; /// The buffer used to store reordered rows.
360 int32_t mInputRow
; /// The current row we're reading. (0-indexed)
361 int32_t mOutputRow
; /// The current row we're writing. (0-indexed)
362 uint8_t mPass
; /// Which pass we're on. (0-indexed)
363 bool mProgressiveDisplay
; /// If true, duplicate rows to optimize for
364 /// progressive display.
367 //////////////////////////////////////////////////////////////////////////////
368 // BlendAnimationFilter
369 //////////////////////////////////////////////////////////////////////////////
371 template <typename Next
>
372 class BlendAnimationFilter
;
375 * A configuration struct for BlendAnimationFilter.
377 struct BlendAnimationConfig
{
378 template <typename Next
>
379 using Filter
= BlendAnimationFilter
<Next
>;
380 Decoder
* mDecoder
; /// The decoder producing the animation.
384 * BlendAnimationFilter turns a partial image as part of an animation into a
385 * complete frame given its frame rect, blend method, and the base frame's
386 * data buffer, frame rect and disposal method. Any excess data caused by a
387 * frame rect not being contained by the output size will be discarded.
389 * The base frame is an already produced complete frame from the animation.
390 * It may be any previous frame depending on the disposal method, although
391 * most often it will be the immediate previous frame to the current we are
394 * The 'Next' template parameter specifies the next filter in the chain.
396 template <typename Next
>
397 class BlendAnimationFilter final
: public SurfaceFilter
{
399 BlendAnimationFilter()
404 mRecycleRowOffset(0),
405 mRecycleRowLength(0),
408 mClearPrefixLength(0),
409 mClearInfixOffset(0),
410 mClearInfixLength(0),
411 mClearPostfixOffset(0),
412 mClearPostfixLength(0),
414 mBaseFrameStartPtr(nullptr),
415 mBaseFrameRowPtr(nullptr) {}
417 template <typename
... Rest
>
418 nsresult
Configure(const BlendAnimationConfig
& aConfig
,
419 const Rest
&... aRest
) {
420 nsresult rv
= mNext
.Configure(aRest
...);
425 imgFrame
* currentFrame
= aConfig
.mDecoder
->GetCurrentFrame();
427 MOZ_ASSERT_UNREACHABLE("Decoder must have current frame!");
428 return NS_ERROR_FAILURE
;
431 mFrameRect
= mUnclampedFrameRect
= currentFrame
->GetBlendRect();
432 gfx::IntSize outputSize
= mNext
.InputSize();
433 mRowLength
= outputSize
.width
* sizeof(uint32_t);
435 // Forbid frame rects with negative size.
436 if (mUnclampedFrameRect
.width
< 0 || mUnclampedFrameRect
.height
< 0) {
437 return NS_ERROR_FAILURE
;
440 // Clamp mFrameRect to the output size.
441 gfx::IntRect
outputRect(0, 0, outputSize
.width
, outputSize
.height
);
442 mFrameRect
= mFrameRect
.Intersect(outputRect
);
443 bool fullFrame
= outputRect
.IsEqualEdges(mFrameRect
);
445 // If there's no intersection, |mFrameRect| will be an empty rect positioned
446 // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
447 // not what we want. Force it to (0, 0) sized 0 x 0 in that case.
448 if (mFrameRect
.IsEmpty()) {
449 mFrameRect
.SetRect(0, 0, 0, 0);
452 BlendMethod blendMethod
= currentFrame
->GetBlendMethod();
453 switch (blendMethod
) {
455 blendMethod
= BlendMethod::SOURCE
;
456 MOZ_FALLTHROUGH_ASSERT("Unexpected blend method!");
457 case BlendMethod::SOURCE
:
458 // Default, overwrites base frame data (if any) with new.
460 case BlendMethod::OVER
:
461 // OVER only has an impact on the output if we have new data to blend
463 if (mFrameRect
.IsEmpty()) {
464 blendMethod
= BlendMethod::SOURCE
;
469 // Determine what we need to clear and what we need to copy. If this frame
470 // is a full frame and uses source blending, there is no need to consider
471 // the disposal method of the previous frame.
472 gfx::IntRect
dirtyRect(outputRect
);
473 gfx::IntRect clearRect
;
474 if (!fullFrame
|| blendMethod
!= BlendMethod::SOURCE
) {
475 const RawAccessFrameRef
& restoreFrame
=
476 aConfig
.mDecoder
->GetRestoreFrameRef();
478 MOZ_ASSERT(restoreFrame
->GetSize() == outputSize
);
479 MOZ_ASSERT(restoreFrame
->IsFinished());
481 // We can safely use this pointer without holding a RawAccessFrameRef
482 // because the decoder will keep it alive for us.
483 mBaseFrameStartPtr
= restoreFrame
.Data();
484 MOZ_ASSERT(mBaseFrameStartPtr
);
486 gfx::IntRect restoreBlendRect
= restoreFrame
->GetBoundedBlendRect();
487 gfx::IntRect restoreDirtyRect
= aConfig
.mDecoder
->GetRestoreDirtyRect();
488 switch (restoreFrame
->GetDisposalMethod()) {
490 case DisposalMethod::RESTORE_PREVIOUS
:
491 MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
492 case DisposalMethod::NOT_SPECIFIED
:
493 case DisposalMethod::KEEP
:
494 dirtyRect
= mFrameRect
.Union(restoreDirtyRect
);
496 case DisposalMethod::CLEAR
:
497 // We only need to clear if the rect is outside the frame rect (i.e.
498 // overwrites a non-overlapping area) or the blend method may cause
499 // us to combine old data and new.
500 if (!mFrameRect
.Contains(restoreBlendRect
) ||
501 blendMethod
== BlendMethod::OVER
) {
502 clearRect
= restoreBlendRect
;
505 // If we are clearing the whole frame, we do not need to retain a
506 // reference to the base frame buffer.
507 if (outputRect
.IsEqualEdges(clearRect
)) {
508 mBaseFrameStartPtr
= nullptr;
510 dirtyRect
= mFrameRect
.Union(restoreDirtyRect
).Union(clearRect
);
514 } else if (!fullFrame
) {
515 // This must be the first frame, clear everything.
516 clearRect
= outputRect
;
520 // We may be able to reuse parts of our underlying buffer that we are
521 // writing the new frame to. The recycle rect gives us the invalidation
522 // region which needs to be copied from the restore frame.
523 const gfx::IntRect
& recycleRect
= aConfig
.mDecoder
->GetRecycleRect();
524 mRecycleRow
= recycleRect
.y
;
525 mRecycleRowMost
= recycleRect
.YMost();
526 mRecycleRowOffset
= recycleRect
.x
* sizeof(uint32_t);
527 mRecycleRowLength
= recycleRect
.width
* sizeof(uint32_t);
529 if (!clearRect
.IsEmpty()) {
530 // The clear rect interacts with the recycle rect because we need to copy
531 // the prefix and postfix data from the base frame. The one thing we do
532 // know is that the infix area is always cleared explicitly.
533 mClearRow
= clearRect
.y
;
534 mClearRowMost
= clearRect
.YMost();
535 mClearInfixOffset
= clearRect
.x
* sizeof(uint32_t);
536 mClearInfixLength
= clearRect
.width
* sizeof(uint32_t);
538 // The recycle row offset is where we need to begin copying base frame
539 // data for a row. If this offset begins after or at the clear infix
540 // offset, then there is no prefix data at all.
541 if (mClearInfixOffset
> mRecycleRowOffset
) {
542 mClearPrefixLength
= mClearInfixOffset
- mRecycleRowOffset
;
545 // Similar to the prefix, if the postfix offset begins outside the recycle
546 // rect, then we know we already have all the data we need.
547 mClearPostfixOffset
= mClearInfixOffset
+ mClearInfixLength
;
548 size_t recycleRowEndOffset
= mRecycleRowOffset
+ mRecycleRowLength
;
549 if (mClearPostfixOffset
< recycleRowEndOffset
) {
550 mClearPostfixLength
= recycleRowEndOffset
- mClearPostfixOffset
;
554 // The dirty rect, or delta between the current frame and the previous frame
555 // (chronologically, not necessarily the restore frame) is the last
556 // animation parameter we need to initialize the new frame with.
557 currentFrame
->SetDirtyRect(dirtyRect
);
559 if (!mBaseFrameStartPtr
) {
560 // Switch to SOURCE if no base frame to ensure we don't allocate an
561 // intermediate buffer below. OVER does nothing without the base frame
563 blendMethod
= BlendMethod::SOURCE
;
566 // Skia provides arch-specific accelerated methods to perform blending.
567 // Note that this is an internal Skia API and may be prone to change,
568 // but we avoid the overhead of setting up Skia objects.
569 if (blendMethod
== BlendMethod::OVER
) {
570 mOverProc
= SkBlitRow::Factory32(SkBlitRow::kSrcPixelAlpha_Flag32
);
571 MOZ_ASSERT(mOverProc
);
574 // We don't need an intermediate buffer unless the unclamped frame rect
575 // width is larger than the clamped frame rect width. In that case, the
576 // caller will end up writing data that won't end up in the final image at
577 // all, and we'll need a buffer to give that data a place to go.
578 if (mFrameRect
.width
< mUnclampedFrameRect
.width
|| mOverProc
) {
579 mBuffer
.reset(new (fallible
)
580 uint8_t[mUnclampedFrameRect
.width
* sizeof(uint32_t)]);
581 if (MOZ_UNLIKELY(!mBuffer
)) {
582 return NS_ERROR_OUT_OF_MEMORY
;
585 memset(mBuffer
.get(), 0, mUnclampedFrameRect
.width
* sizeof(uint32_t));
588 ConfigureFilter(mUnclampedFrameRect
.Size(), sizeof(uint32_t));
592 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
593 return mNext
.TakeInvalidRect();
597 uint8_t* DoResetToFirstRow() override
{
598 uint8_t* rowPtr
= mNext
.ResetToFirstRow();
599 if (rowPtr
== nullptr) {
600 mRow
= mFrameRect
.YMost();
605 mBaseFrameRowPtr
= mBaseFrameStartPtr
;
607 while (mRow
< mFrameRect
.y
) {
609 AdvanceRowOutsideFrameRect();
612 // We're at the beginning of the frame rect now, so return if we're either
613 // ready for input or we're already done.
614 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
615 if (!mFrameRect
.IsEmpty() || rowPtr
== nullptr) {
616 // Note that the pointer we're returning is for the next row we're
617 // actually going to write to, but we may discard writes before that point
618 // if mRow < mFrameRect.y.
619 mRow
= mUnclampedFrameRect
.y
;
621 return AdjustRowPointer(rowPtr
);
624 // We've finished the region specified by the frame rect, but the frame rect
625 // is empty, so we need to output the rest of the image immediately. Advance
626 // to the end of the next pipeline stage's buffer, outputting rows that are
627 // copied from the base frame and/or cleared.
628 WriteBaseFrameRowsUntilComplete();
630 mRow
= mFrameRect
.YMost();
631 return nullptr; // We're done.
634 uint8_t* DoAdvanceRow() override
{
635 uint8_t* rowPtr
= nullptr;
637 const int32_t currentRow
= mRow
;
640 // The unclamped frame rect has a negative offset which means -y rows from
641 // the decoder need to be discarded before we advance properly.
642 if (currentRow
>= 0 && mBaseFrameRowPtr
) {
643 mBaseFrameRowPtr
+= mRowLength
;
646 if (currentRow
< mFrameRect
.y
) {
647 // This row is outside of the frame rect, so just drop it on the floor.
648 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
649 return AdjustRowPointer(rowPtr
);
650 } else if (NS_WARN_IF(currentRow
>= mFrameRect
.YMost())) {
654 // If we had to buffer, merge the data into the row. Otherwise we had the
655 // decoder write directly to the next stage's buffer.
657 int32_t width
= mFrameRect
.width
;
658 uint32_t* dst
= reinterpret_cast<uint32_t*>(mNext
.CurrentRowPointer());
659 uint32_t* src
= reinterpret_cast<uint32_t*>(mBuffer
.get()) -
660 std::min(mUnclampedFrameRect
.x
, 0);
663 mOverProc(dst
, src
, width
, 0xFF);
665 memcpy(dst
, src
, width
* sizeof(uint32_t));
667 rowPtr
= mNext
.AdvanceRow() ? mBuffer
.get() : nullptr;
669 MOZ_ASSERT(!mOverProc
);
670 rowPtr
= mNext
.AdvanceRow();
673 // If there's still more data coming or we're already done, just adjust the
674 // pointer and return.
675 if (mRow
< mFrameRect
.YMost() || rowPtr
== nullptr) {
677 return AdjustRowPointer(rowPtr
);
680 // We've finished the region specified by the frame rect. Advance to the end
681 // of the next pipeline stage's buffer, outputting rows that are copied from
682 // the base frame and/or cleared.
683 WriteBaseFrameRowsUntilComplete();
685 return nullptr; // We're done.
689 void WriteBaseFrameRowsUntilComplete() {
692 } while (AdvanceRowOutsideFrameRect());
695 void WriteBaseFrameRow() {
696 uint8_t* dest
= mNext
.CurrentRowPointer();
701 // No need to copy pixels from the base frame for rows that will not change
702 // between the recycled frame and the new frame.
703 bool needBaseFrame
= mRow
>= mRecycleRow
&& mRow
< mRecycleRowMost
;
705 if (!mBaseFrameRowPtr
) {
706 // No base frame, so we are clearing everything.
708 memset(dest
+ mRecycleRowOffset
, 0, mRecycleRowLength
);
710 } else if (mClearRow
<= mRow
&& mClearRowMost
> mRow
) {
711 // We have a base frame, but we are inside the area to be cleared.
712 // Only copy the data we need from the source.
714 memcpy(dest
+ mRecycleRowOffset
, mBaseFrameRowPtr
+ mRecycleRowOffset
,
716 memcpy(dest
+ mClearPostfixOffset
,
717 mBaseFrameRowPtr
+ mClearPostfixOffset
, mClearPostfixLength
);
719 memset(dest
+ mClearInfixOffset
, 0, mClearInfixLength
);
720 } else if (needBaseFrame
) {
721 memcpy(dest
+ mRecycleRowOffset
, mBaseFrameRowPtr
+ mRecycleRowOffset
,
726 bool AdvanceRowOutsideFrameRect() {
727 // The unclamped frame rect may have a negative offset however we should
728 // never be advancing the row via this path (otherwise mBaseFrameRowPtr
730 MOZ_ASSERT(mRow
>= 0);
731 MOZ_ASSERT(mRow
< mFrameRect
.y
|| mRow
>= mFrameRect
.YMost());
734 if (mBaseFrameRowPtr
) {
735 mBaseFrameRowPtr
+= mRowLength
;
738 return mNext
.AdvanceRow() != nullptr;
741 uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer
) const {
743 MOZ_ASSERT(aNextRowPointer
== mBuffer
.get() ||
744 aNextRowPointer
== nullptr);
745 return aNextRowPointer
; // No adjustment needed for an intermediate
749 if (mFrameRect
.IsEmpty() || mRow
>= mFrameRect
.YMost() ||
750 aNextRowPointer
== nullptr) {
751 return nullptr; // Nothing left to write.
754 MOZ_ASSERT(!mOverProc
);
755 return aNextRowPointer
+ mFrameRect
.x
* sizeof(uint32_t);
758 Next mNext
; /// The next SurfaceFilter in the chain.
760 gfx::IntRect mFrameRect
; /// The surface subrect which contains data,
761 /// clamped to the image size.
762 gfx::IntRect mUnclampedFrameRect
; /// The frame rect before clamping.
763 UniquePtr
<uint8_t[]> mBuffer
; /// The intermediate buffer, if one is
764 /// necessary because the frame rect width
765 /// is larger than the image's logical width.
766 int32_t mRow
; /// The row in unclamped frame rect space
767 /// that we're currently writing.
768 size_t mRowLength
; /// Length in bytes of a row that is the input
769 /// for the next filter.
770 int32_t mRecycleRow
; /// The starting row of the recycle rect.
771 int32_t mRecycleRowMost
; /// The ending row of the recycle rect.
772 size_t mRecycleRowOffset
; /// Row offset in bytes of the recycle rect.
773 size_t mRecycleRowLength
; /// Row length in bytes of the recycle rect.
775 /// The frame area to clear before blending the current frame.
776 int32_t mClearRow
; /// The starting row of the clear rect.
777 int32_t mClearRowMost
; /// The ending row of the clear rect.
778 size_t mClearPrefixLength
; /// Row length in bytes of clear prefix.
779 size_t mClearInfixOffset
; /// Row offset in bytes of clear area.
780 size_t mClearInfixLength
; /// Row length in bytes of clear area.
781 size_t mClearPostfixOffset
; /// Row offset in bytes of clear postfix.
782 size_t mClearPostfixLength
; /// Row length in bytes of clear postfix.
784 SkBlitRow::Proc32 mOverProc
; /// Function pointer to perform over blending.
786 mBaseFrameStartPtr
; /// Starting row pointer to the base frame
787 /// data from which we copy pixel data from.
788 const uint8_t* mBaseFrameRowPtr
; /// Current row pointer to the base frame
792 //////////////////////////////////////////////////////////////////////////////
793 // RemoveFrameRectFilter
794 //////////////////////////////////////////////////////////////////////////////
796 template <typename Next
>
797 class RemoveFrameRectFilter
;
800 * A configuration struct for RemoveFrameRectFilter.
802 struct RemoveFrameRectConfig
{
803 template <typename Next
>
804 using Filter
= RemoveFrameRectFilter
<Next
>;
805 gfx::IntRect mFrameRect
; /// The surface subrect which contains data.
809 * RemoveFrameRectFilter turns an image with a frame rect that does not match
810 * its logical size into an image with no frame rect. It does this by writing
811 * transparent pixels into any padding regions and throwing away excess data.
813 * The 'Next' template parameter specifies the next filter in the chain.
815 template <typename Next
>
816 class RemoveFrameRectFilter final
: public SurfaceFilter
{
818 RemoveFrameRectFilter() : mRow(0) {}
820 template <typename
... Rest
>
821 nsresult
Configure(const RemoveFrameRectConfig
& aConfig
,
822 const Rest
&... aRest
) {
823 nsresult rv
= mNext
.Configure(aRest
...);
828 mFrameRect
= mUnclampedFrameRect
= aConfig
.mFrameRect
;
829 gfx::IntSize outputSize
= mNext
.InputSize();
831 // Forbid frame rects with negative size.
832 if (aConfig
.mFrameRect
.Width() < 0 || aConfig
.mFrameRect
.Height() < 0) {
833 return NS_ERROR_INVALID_ARG
;
836 // Clamp mFrameRect to the output size.
837 gfx::IntRect
outputRect(0, 0, outputSize
.width
, outputSize
.height
);
838 mFrameRect
= mFrameRect
.Intersect(outputRect
);
840 // If there's no intersection, |mFrameRect| will be an empty rect positioned
841 // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
842 // not what we want. Force it to (0, 0) in that case.
843 if (mFrameRect
.IsEmpty()) {
844 mFrameRect
.MoveTo(0, 0);
847 // We don't need an intermediate buffer unless the unclamped frame rect
848 // width is larger than the clamped frame rect width. In that case, the
849 // caller will end up writing data that won't end up in the final image at
850 // all, and we'll need a buffer to give that data a place to go.
851 if (mFrameRect
.Width() < mUnclampedFrameRect
.Width()) {
853 fallible
) uint8_t[mUnclampedFrameRect
.Width() * sizeof(uint32_t)]);
854 if (MOZ_UNLIKELY(!mBuffer
)) {
855 return NS_ERROR_OUT_OF_MEMORY
;
858 memset(mBuffer
.get(), 0, mUnclampedFrameRect
.Width() * sizeof(uint32_t));
861 ConfigureFilter(mUnclampedFrameRect
.Size(), sizeof(uint32_t));
865 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
866 return mNext
.TakeInvalidRect();
870 uint8_t* DoResetToFirstRow() override
{
871 uint8_t* rowPtr
= mNext
.ResetToFirstRow();
872 if (rowPtr
== nullptr) {
873 mRow
= mFrameRect
.YMost();
877 mRow
= mUnclampedFrameRect
.Y();
879 // Advance the next pipeline stage to the beginning of the frame rect,
880 // outputting blank rows.
881 if (mFrameRect
.Y() > 0) {
882 for (int32_t rowToOutput
= 0; rowToOutput
< mFrameRect
.Y();
884 mNext
.WriteEmptyRow();
888 // We're at the beginning of the frame rect now, so return if we're either
889 // ready for input or we're already done.
890 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
891 if (!mFrameRect
.IsEmpty() || rowPtr
== nullptr) {
892 // Note that the pointer we're returning is for the next row we're
893 // actually going to write to, but we may discard writes before that point
894 // if mRow < mFrameRect.y.
895 return AdjustRowPointer(rowPtr
);
898 // We've finished the region specified by the frame rect, but the frame rect
899 // is empty, so we need to output the rest of the image immediately. Advance
900 // to the end of the next pipeline stage's buffer, outputting blank rows.
901 while (mNext
.WriteEmptyRow() == WriteState::NEED_MORE_DATA
) {
904 mRow
= mFrameRect
.YMost();
905 return nullptr; // We're done.
908 uint8_t* DoAdvanceRow() override
{
909 uint8_t* rowPtr
= nullptr;
911 const int32_t currentRow
= mRow
;
914 if (currentRow
< mFrameRect
.Y()) {
915 // This row is outside of the frame rect, so just drop it on the floor.
916 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
917 return AdjustRowPointer(rowPtr
);
918 } else if (currentRow
>= mFrameRect
.YMost()) {
919 NS_WARNING("RemoveFrameRectFilter: Advancing past end of frame rect");
923 // If we had to buffer, copy the data. Otherwise, just advance the row.
925 // We write from the beginning of the buffer unless
926 // |mUnclampedFrameRect.x| is negative; if that's the case, we have to
927 // skip the portion of the unclamped frame rect that's outside the row.
928 uint32_t* source
= reinterpret_cast<uint32_t*>(mBuffer
.get()) -
929 std::min(mUnclampedFrameRect
.X(), 0);
931 // We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've
932 // already clamped these values to the size of the output, so we don't
933 // have to worry about bounds checking here (though WriteBuffer() will do
934 // it for us in any case).
936 mNext
.WriteBuffer(source
, mFrameRect
.X(), mFrameRect
.Width());
938 rowPtr
= state
== WriteState::NEED_MORE_DATA
? mBuffer
.get() : nullptr;
940 rowPtr
= mNext
.AdvanceRow();
943 // If there's still more data coming or we're already done, just adjust the
944 // pointer and return.
945 if (mRow
< mFrameRect
.YMost() || rowPtr
== nullptr) {
946 return AdjustRowPointer(rowPtr
);
949 // We've finished the region specified by the frame rect. Advance to the end
950 // of the next pipeline stage's buffer, outputting blank rows.
951 while (mNext
.WriteEmptyRow() == WriteState::NEED_MORE_DATA
) {
954 mRow
= mFrameRect
.YMost();
955 return nullptr; // We're done.
959 uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer
) const {
961 MOZ_ASSERT(aNextRowPointer
== mBuffer
.get() ||
962 aNextRowPointer
== nullptr);
963 return aNextRowPointer
; // No adjustment needed for an intermediate
967 if (mFrameRect
.IsEmpty() || mRow
>= mFrameRect
.YMost() ||
968 aNextRowPointer
== nullptr) {
969 return nullptr; // Nothing left to write.
972 return aNextRowPointer
+ mFrameRect
.X() * sizeof(uint32_t);
975 Next mNext
; /// The next SurfaceFilter in the chain.
977 gfx::IntRect mFrameRect
; /// The surface subrect which contains data,
978 /// clamped to the image size.
979 gfx::IntRect mUnclampedFrameRect
; /// The frame rect before clamping.
980 UniquePtr
<uint8_t[]> mBuffer
; /// The intermediate buffer, if one is
981 /// necessary because the frame rect width
982 /// is larger than the image's logical width.
983 int32_t mRow
; /// The row in unclamped frame rect space
984 /// that we're currently writing.
987 //////////////////////////////////////////////////////////////////////////////
988 // ADAM7InterpolatingFilter
989 //////////////////////////////////////////////////////////////////////////////
991 template <typename Next
>
992 class ADAM7InterpolatingFilter
;
995 * A configuration struct for ADAM7InterpolatingFilter.
997 struct ADAM7InterpolatingConfig
{
998 template <typename Next
>
999 using Filter
= ADAM7InterpolatingFilter
<Next
>;
1003 * ADAM7InterpolatingFilter performs bilinear interpolation over an ADAM7
1006 * ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new set
1007 * of pixels in each block receives their final values, according to the
1008 * following pattern:
1019 * When rendering the pixels that have not yet received their final values, we
1020 * can get much better intermediate results if we interpolate between
1021 * the pixels we *have* gotten so far. This filter performs bilinear
1022 * interpolation by first performing linear interpolation horizontally for each
1023 * "important" row (which we'll define as a row that has received any pixels
1024 * with final values at all) and then performing linear interpolation vertically
1025 * to produce pixel values for rows which aren't important on the current pass.
1027 * Note that this filter totally ignores the data which is written to rows which
1028 * aren't important on the current pass! It's fine to write nothing at all for
1029 * these rows, although doing so won't cause any harm.
1031 * XXX(seth): In bug 1280552 we'll add a SIMD implementation for this filter.
1033 * The 'Next' template parameter specifies the next filter in the chain.
1035 template <typename Next
>
1036 class ADAM7InterpolatingFilter final
: public SurfaceFilter
{
1038 ADAM7InterpolatingFilter()
1039 : mPass(0) // The current pass, in the range 1..7. Starts at 0 so that
1040 // DoResetToFirstRow() doesn't have to special case the first
1045 template <typename
... Rest
>
1046 nsresult
Configure(const ADAM7InterpolatingConfig
& aConfig
,
1047 const Rest
&... aRest
) {
1048 nsresult rv
= mNext
.Configure(aRest
...);
1049 if (NS_FAILED(rv
)) {
1053 // We have two intermediate buffers, one for the previous row with final
1054 // pixel values and one for the row that the previous filter in the chain is
1055 // currently writing to.
1056 size_t inputWidthInBytes
= mNext
.InputSize().width
* sizeof(uint32_t);
1057 mPreviousRow
.reset(new (fallible
) uint8_t[inputWidthInBytes
]);
1058 if (MOZ_UNLIKELY(!mPreviousRow
)) {
1059 return NS_ERROR_OUT_OF_MEMORY
;
1062 mCurrentRow
.reset(new (fallible
) uint8_t[inputWidthInBytes
]);
1063 if (MOZ_UNLIKELY(!mCurrentRow
)) {
1064 return NS_ERROR_OUT_OF_MEMORY
;
1067 memset(mPreviousRow
.get(), 0, inputWidthInBytes
);
1068 memset(mCurrentRow
.get(), 0, inputWidthInBytes
);
1070 ConfigureFilter(mNext
.InputSize(), sizeof(uint32_t));
1074 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
1075 return mNext
.TakeInvalidRect();
1079 uint8_t* DoResetToFirstRow() override
{
1081 mPass
= std::min(mPass
+ 1, 7);
1083 uint8_t* rowPtr
= mNext
.ResetToFirstRow();
1085 // Short circuit this filter on the final pass, since all pixels have
1086 // their final values at that point.
1090 return mCurrentRow
.get();
1093 uint8_t* DoAdvanceRow() override
{
1094 MOZ_ASSERT(0 < mPass
&& mPass
<= 7, "Invalid pass");
1096 int32_t currentRow
= mRow
;
1100 // On the final pass we short circuit this filter totally.
1101 return mNext
.AdvanceRow();
1104 const int32_t lastImportantRow
=
1105 LastImportantRow(InputSize().height
, mPass
);
1106 if (currentRow
> lastImportantRow
) {
1107 return nullptr; // This pass is already complete.
1110 if (!IsImportantRow(currentRow
, mPass
)) {
1111 // We just ignore whatever the caller gives us for these rows. We'll
1112 // interpolate them in later.
1113 return mCurrentRow
.get();
1116 // This is an important row. We need to perform horizontal interpolation for
1118 InterpolateHorizontally(mCurrentRow
.get(), InputSize().width
, mPass
);
1120 // Interpolate vertically between the previous important row and the current
1121 // important row. We skip this if the current row is 0 (which is always an
1122 // important row), because in that case there is no previous important row
1123 // to interpolate with.
1124 if (currentRow
!= 0) {
1125 InterpolateVertically(mPreviousRow
.get(), mCurrentRow
.get(), mPass
,
1129 // Write out the current row itself, which, being an important row, does not
1130 // need vertical interpolation.
1131 uint32_t* currentRowAsPixels
=
1132 reinterpret_cast<uint32_t*>(mCurrentRow
.get());
1133 mNext
.WriteBuffer(currentRowAsPixels
);
1135 if (currentRow
== lastImportantRow
) {
1136 // This is the last important row, which completes this pass. Note that
1137 // for very small images, this may be the first row! Since there won't be
1138 // another important row, there's nothing to interpolate with vertically,
1139 // so we just duplicate this row until the end of the image.
1140 while (mNext
.WriteBuffer(currentRowAsPixels
) ==
1141 WriteState::NEED_MORE_DATA
) {
1144 // All of the remaining rows in the image were determined above, so we're
1149 // The current row is now the previous important row; save it.
1150 Swap(mPreviousRow
, mCurrentRow
);
1152 MOZ_ASSERT(mRow
< InputSize().height
,
1153 "Reached the end of the surface without "
1154 "hitting the last important row?");
1156 return mCurrentRow
.get();
1160 static void InterpolateVertically(uint8_t* aPreviousRow
, uint8_t* aCurrentRow
,
1161 uint8_t aPass
, SurfaceFilter
& aNext
) {
1162 const float* weights
= InterpolationWeights(ImportantRowStride(aPass
));
1164 // We need to interpolate vertically to generate the rows between the
1165 // previous important row and the next one. Recall that important rows are
1166 // rows which contain at least some final pixels; see
1167 // InterpolateHorizontally() for some additional explanation as to what that
1168 // means. Note that we've already written out the previous important row, so
1169 // we start the iteration at 1.
1170 for (int32_t outRow
= 1; outRow
< ImportantRowStride(aPass
); ++outRow
) {
1171 const float weight
= weights
[outRow
];
1173 // We iterate through the previous and current important row every time we
1174 // write out an interpolated row, so we need to copy the pointers.
1175 uint8_t* prevRowBytes
= aPreviousRow
;
1176 uint8_t* currRowBytes
= aCurrentRow
;
1178 // Write out the interpolated pixels. Interpolation is componentwise.
1179 aNext
.template WritePixelsToRow
<uint32_t>([&] {
1181 auto* component
= reinterpret_cast<uint8_t*>(&pixel
);
1183 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1185 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1187 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1189 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1190 return AsVariant(pixel
);
1195 static void InterpolateHorizontally(uint8_t* aRow
, int32_t aWidth
,
1197 // Collect the data we'll need to perform horizontal interpolation. The
1198 // terminology here bears some explanation: a "final pixel" is a pixel which
1199 // has received its final value. On each pass, a new set of pixels receives
1200 // their final value; see the diagram above of the 8x8 pattern that ADAM7
1201 // uses. Any pixel which hasn't received its final value on this pass
1202 // derives its value from either horizontal or vertical interpolation
1204 const size_t finalPixelStride
= FinalPixelStride(aPass
);
1205 const size_t finalPixelStrideBytes
= finalPixelStride
* sizeof(uint32_t);
1206 const size_t lastFinalPixel
= LastFinalPixel(aWidth
, aPass
);
1207 const size_t lastFinalPixelBytes
= lastFinalPixel
* sizeof(uint32_t);
1208 const float* weights
= InterpolationWeights(finalPixelStride
);
1210 // Interpolate blocks of pixels which lie between two final pixels.
1211 // Horizontal interpolation is done in place, as we'll need the results
1212 // later when we vertically interpolate.
1213 for (size_t blockBytes
= 0; blockBytes
< lastFinalPixelBytes
;
1214 blockBytes
+= finalPixelStrideBytes
) {
1215 uint8_t* finalPixelA
= aRow
+ blockBytes
;
1216 uint8_t* finalPixelB
= aRow
+ blockBytes
+ finalPixelStrideBytes
;
1218 MOZ_ASSERT(finalPixelA
< aRow
+ aWidth
* sizeof(uint32_t),
1219 "Running off end of buffer");
1220 MOZ_ASSERT(finalPixelB
< aRow
+ aWidth
* sizeof(uint32_t),
1221 "Running off end of buffer");
1223 // Interpolate the individual pixels componentwise. Note that we start
1224 // iteration at 1 since we don't need to apply any interpolation to the
1225 // first pixel in the block, which has its final value.
1226 for (size_t pixelIndex
= 1; pixelIndex
< finalPixelStride
; ++pixelIndex
) {
1227 const float weight
= weights
[pixelIndex
];
1228 uint8_t* pixel
= aRow
+ blockBytes
+ pixelIndex
* sizeof(uint32_t);
1230 MOZ_ASSERT(pixel
< aRow
+ aWidth
* sizeof(uint32_t),
1231 "Running off end of buffer");
1233 for (size_t component
= 0; component
< sizeof(uint32_t); ++component
) {
1234 pixel
[component
] = InterpolateByte(finalPixelA
[component
],
1235 finalPixelB
[component
], weight
);
1240 // For the pixels after the last final pixel in the row, there isn't a
1241 // second final pixel to interpolate with, so just duplicate.
1242 uint32_t* rowPixels
= reinterpret_cast<uint32_t*>(aRow
);
1243 uint32_t pixelToDuplicate
= rowPixels
[lastFinalPixel
];
1244 for (int32_t pixelIndex
= lastFinalPixel
+ 1; pixelIndex
< aWidth
;
1246 MOZ_ASSERT(pixelIndex
< aWidth
, "Running off end of buffer");
1247 rowPixels
[pixelIndex
] = pixelToDuplicate
;
1251 static uint8_t InterpolateByte(uint8_t aByteA
, uint8_t aByteB
,
1253 return uint8_t(aByteA
* aWeight
+ aByteB
* (1.0f
- aWeight
));
1256 static int32_t ImportantRowStride(uint8_t aPass
) {
1257 MOZ_ASSERT(0 < aPass
&& aPass
<= 7, "Invalid pass");
1259 // The stride between important rows for each pass, with a dummy value for
1260 // the nonexistent pass 0.
1261 static int32_t strides
[] = {1, 8, 8, 4, 4, 2, 2, 1};
1263 return strides
[aPass
];
1266 static bool IsImportantRow(int32_t aRow
, uint8_t aPass
) {
1267 MOZ_ASSERT(aRow
>= 0);
1269 // Whether the row is important comes down to divisibility by the stride for
1270 // this pass, which is always a power of 2, so we can check using a mask.
1271 int32_t mask
= ImportantRowStride(aPass
) - 1;
1272 return (aRow
& mask
) == 0;
1275 static int32_t LastImportantRow(int32_t aHeight
, uint8_t aPass
) {
1276 MOZ_ASSERT(aHeight
> 0);
1278 // We can find the last important row using the same mask trick as above.
1279 int32_t lastRow
= aHeight
- 1;
1280 int32_t mask
= ImportantRowStride(aPass
) - 1;
1281 return lastRow
- (lastRow
& mask
);
1284 static size_t FinalPixelStride(uint8_t aPass
) {
1285 MOZ_ASSERT(0 < aPass
&& aPass
<= 7, "Invalid pass");
1287 // The stride between the final pixels in important rows for each pass, with
1288 // a dummy value for the nonexistent pass 0.
1289 static size_t strides
[] = {1, 8, 4, 4, 2, 2, 1, 1};
1291 return strides
[aPass
];
1294 static size_t LastFinalPixel(int32_t aWidth
, uint8_t aPass
) {
1295 MOZ_ASSERT(aWidth
>= 0);
1297 // Again, we can use the mask trick above to find the last important pixel.
1298 int32_t lastColumn
= aWidth
- 1;
1299 size_t mask
= FinalPixelStride(aPass
) - 1;
1300 return lastColumn
- (lastColumn
& mask
);
1303 static const float* InterpolationWeights(int32_t aStride
) {
1304 // Precalculated interpolation weights. These are used to interpolate
1305 // between final pixels or between important rows. Although no interpolation
1306 // is actually applied to the previous final pixel or important row value,
1307 // the arrays still start with 1.0f, which is always skipped, primarily
1308 // because otherwise |stride1Weights| would have zero elements.
1309 static float stride8Weights
[] = {1.0f
, 7 / 8.0f
, 6 / 8.0f
, 5 / 8.0f
,
1310 4 / 8.0f
, 3 / 8.0f
, 2 / 8.0f
, 1 / 8.0f
};
1311 static float stride4Weights
[] = {1.0f
, 3 / 4.0f
, 2 / 4.0f
, 1 / 4.0f
};
1312 static float stride2Weights
[] = {1.0f
, 1 / 2.0f
};
1313 static float stride1Weights
[] = {1.0f
};
1317 return stride8Weights
;
1319 return stride4Weights
;
1321 return stride2Weights
;
1323 return stride1Weights
;
1329 Next mNext
; /// The next SurfaceFilter in the chain.
1331 UniquePtr
<uint8_t[]>
1332 mPreviousRow
; /// The last important row (i.e., row with
1333 /// final pixel values) that got written to.
1334 UniquePtr
<uint8_t[]> mCurrentRow
; /// The row that's being written to right
1336 uint8_t mPass
; /// Which ADAM7 pass we're on. Valid passes
1337 /// are 1..7 during processing and 0 prior
1338 /// to configuraiton.
1339 int32_t mRow
; /// The row we're currently reading.
1342 } // namespace image
1343 } // namespace mozilla
1345 #endif // mozilla_image_SurfaceFilters_h