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 "mozilla/gfx/Swizzle.h"
24 #include "skia/src/core/SkBlitRow.h"
26 #include "DownscalingFilter.h"
27 #include "SurfaceCache.h"
28 #include "SurfacePipe.h"
33 //////////////////////////////////////////////////////////////////////////////
35 //////////////////////////////////////////////////////////////////////////////
37 template <typename Next
>
41 * A configuration struct for SwizzleFilter.
43 struct SwizzleConfig
{
44 template <typename Next
>
45 using Filter
= SwizzleFilter
<Next
>;
46 gfx::SurfaceFormat mInFormat
;
47 gfx::SurfaceFormat mOutFormat
;
48 bool mPremultiplyAlpha
;
52 * SwizzleFilter performs premultiplication, swizzling and unpacking on
53 * rows written to it. It can use accelerated methods to perform these
54 * operations if supported on the platform.
56 * The 'Next' template parameter specifies the next filter in the chain.
58 template <typename Next
>
59 class SwizzleFilter final
: public SurfaceFilter
{
61 SwizzleFilter() : mSwizzleFn(nullptr) {}
63 template <typename
... Rest
>
64 nsresult
Configure(const SwizzleConfig
& aConfig
, const Rest
&... aRest
) {
65 nsresult rv
= mNext
.Configure(aRest
...);
70 if (aConfig
.mPremultiplyAlpha
) {
71 mSwizzleFn
= gfx::PremultiplyRow(aConfig
.mInFormat
, aConfig
.mOutFormat
);
73 mSwizzleFn
= gfx::SwizzleRow(aConfig
.mInFormat
, aConfig
.mOutFormat
);
77 return NS_ERROR_INVALID_ARG
;
80 ConfigureFilter(mNext
.InputSize(), sizeof(uint32_t));
84 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
85 return mNext
.TakeInvalidRect();
89 uint8_t* DoResetToFirstRow() override
{ return mNext
.ResetToFirstRow(); }
91 uint8_t* DoAdvanceRowFromBuffer(const uint8_t* aInputRow
) override
{
92 uint8_t* rowPtr
= mNext
.CurrentRowPointer();
94 return nullptr; // We already got all the input rows we expect.
97 mSwizzleFn(aInputRow
, rowPtr
, mNext
.InputSize().width
);
98 return mNext
.AdvanceRow();
101 uint8_t* DoAdvanceRow() override
{
102 return DoAdvanceRowFromBuffer(mNext
.CurrentRowPointer());
105 Next mNext
; /// The next SurfaceFilter in the chain.
107 gfx::SwizzleRowFn mSwizzleFn
;
110 //////////////////////////////////////////////////////////////////////////////
111 // ColorManagementFilter
112 //////////////////////////////////////////////////////////////////////////////
114 template <typename Next
>
115 class ColorManagementFilter
;
118 * A configuration struct for ColorManagementFilter.
120 struct ColorManagementConfig
{
121 template <typename Next
>
122 using Filter
= ColorManagementFilter
<Next
>;
123 qcms_transform
* mTransform
;
127 * ColorManagementFilter performs color transforms with qcms on rows written
130 * The 'Next' template parameter specifies the next filter in the chain.
132 template <typename Next
>
133 class ColorManagementFilter final
: public SurfaceFilter
{
135 ColorManagementFilter() : mTransform(nullptr) {}
137 template <typename
... Rest
>
138 nsresult
Configure(const ColorManagementConfig
& aConfig
,
139 const Rest
&... aRest
) {
140 nsresult rv
= mNext
.Configure(aRest
...);
145 if (!aConfig
.mTransform
) {
146 return NS_ERROR_INVALID_ARG
;
149 mTransform
= aConfig
.mTransform
;
150 ConfigureFilter(mNext
.InputSize(), sizeof(uint32_t));
154 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
155 return mNext
.TakeInvalidRect();
159 uint8_t* DoResetToFirstRow() override
{ return mNext
.ResetToFirstRow(); }
161 uint8_t* DoAdvanceRowFromBuffer(const uint8_t* aInputRow
) override
{
162 qcms_transform_data(mTransform
, aInputRow
, mNext
.CurrentRowPointer(),
163 mNext
.InputSize().width
);
164 return mNext
.AdvanceRow();
167 uint8_t* DoAdvanceRow() override
{
168 return DoAdvanceRowFromBuffer(mNext
.CurrentRowPointer());
171 Next mNext
; /// The next SurfaceFilter in the chain.
173 qcms_transform
* mTransform
;
176 //////////////////////////////////////////////////////////////////////////////
177 // DeinterlacingFilter
178 //////////////////////////////////////////////////////////////////////////////
180 template <typename PixelType
, typename Next
>
181 class DeinterlacingFilter
;
184 * A configuration struct for DeinterlacingFilter.
186 * The 'PixelType' template parameter should be either uint32_t (for output to a
187 * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
189 template <typename PixelType
>
190 struct DeinterlacingConfig
{
191 template <typename Next
>
192 using Filter
= DeinterlacingFilter
<PixelType
, Next
>;
193 bool mProgressiveDisplay
; /// If true, duplicate rows during deinterlacing
194 /// to make progressive display look better, at
195 /// the cost of some performance.
199 * DeinterlacingFilter performs deinterlacing by reordering the rows that are
202 * The 'PixelType' template parameter should be either uint32_t (for output to a
203 * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
205 * The 'Next' template parameter specifies the next filter in the chain.
207 template <typename PixelType
, typename Next
>
208 class DeinterlacingFilter final
: public SurfaceFilter
{
210 DeinterlacingFilter()
211 : mInputRow(0), mOutputRow(0), mPass(0), mProgressiveDisplay(true) {}
213 template <typename
... Rest
>
214 nsresult
Configure(const DeinterlacingConfig
<PixelType
>& aConfig
,
215 const Rest
&... aRest
) {
216 nsresult rv
= mNext
.Configure(aRest
...);
221 gfx::IntSize outputSize
= mNext
.InputSize();
222 mProgressiveDisplay
= aConfig
.mProgressiveDisplay
;
224 const CheckedUint32 bufferSize
= CheckedUint32(outputSize
.width
) *
225 CheckedUint32(outputSize
.height
) *
226 CheckedUint32(sizeof(PixelType
));
228 // Use the size of the SurfaceCache as a heuristic to avoid gigantic
229 // allocations. Even if DownscalingFilter allowed us to allocate space for
230 // the output image, the deinterlacing buffer may still be too big, and
231 // fallible allocation won't always save us in the presence of overcommit.
232 if (!bufferSize
.isValid() || !SurfaceCache::CanHold(bufferSize
.value())) {
233 return NS_ERROR_OUT_OF_MEMORY
;
236 // Allocate the buffer, which contains deinterlaced scanlines of the image.
237 // The buffer is necessary so that we can output rows which have already
238 // been deinterlaced again on subsequent passes. Since a later stage in the
239 // pipeline may be transforming the rows it receives (for example, by
240 // downscaling them), the rows may no longer exist in their original form on
241 // the surface itself.
242 mBuffer
.reset(new (fallible
) uint8_t[bufferSize
.value()]);
243 if (MOZ_UNLIKELY(!mBuffer
)) {
244 return NS_ERROR_OUT_OF_MEMORY
;
247 // Clear the buffer to avoid writing uninitialized memory to the output.
248 memset(mBuffer
.get(), 0, bufferSize
.value());
250 ConfigureFilter(outputSize
, sizeof(PixelType
));
254 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
255 return mNext
.TakeInvalidRect();
259 uint8_t* DoResetToFirstRow() override
{
260 mNext
.ResetToFirstRow();
263 mOutputRow
= InterlaceOffset(mPass
);
264 return GetRowPointer(mOutputRow
);
267 uint8_t* DoAdvanceRowFromBuffer(const uint8_t* aInputRow
) override
{
268 CopyInputRow(aInputRow
);
269 return DoAdvanceRow();
272 uint8_t* DoAdvanceRow() override
{
274 return nullptr; // We already finished all passes.
276 if (mInputRow
>= InputSize().height
) {
277 return nullptr; // We already got all the input rows we expect.
280 // Duplicate from the first Haeberli row to the remaining Haeberli rows
281 // within the buffer.
283 HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, mOutputRow
),
284 HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
287 // Write the current set of Haeberli rows (which contains the current row)
288 // to the next stage in the pipeline.
289 OutputRows(HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, mOutputRow
),
290 HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
293 // Determine which output row the next input row corresponds to.
294 bool advancedPass
= false;
295 uint32_t stride
= InterlaceStride(mPass
);
296 int32_t nextOutputRow
= mOutputRow
+ stride
;
297 while (nextOutputRow
>= InputSize().height
) {
298 // Copy any remaining rows from the buffer.
300 OutputRows(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
,
301 InputSize(), mOutputRow
),
305 // We finished the current pass; advance to the next one.
308 return nullptr; // Finished all passes.
311 // Tell the next pipeline stage that we're starting the next pass.
312 mNext
.ResetToFirstRow();
314 // Update our state to reflect the pass change.
316 stride
= InterlaceStride(mPass
);
317 nextOutputRow
= InterlaceOffset(mPass
);
320 MOZ_ASSERT(nextOutputRow
>= 0);
321 MOZ_ASSERT(nextOutputRow
< InputSize().height
);
324 HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, nextOutputRow
) >= 0);
325 MOZ_ASSERT(HaeberliOutputStartRow(mPass
, mProgressiveDisplay
,
326 nextOutputRow
) < InputSize().height
);
327 MOZ_ASSERT(HaeberliOutputStartRow(mPass
, mProgressiveDisplay
,
328 nextOutputRow
) <= nextOutputRow
);
330 MOZ_ASSERT(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
331 nextOutputRow
) >= 0);
332 MOZ_ASSERT(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
333 nextOutputRow
) <= InputSize().height
);
334 MOZ_ASSERT(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
335 nextOutputRow
) > nextOutputRow
);
337 int32_t nextHaeberliOutputRow
=
338 HaeberliOutputStartRow(mPass
, mProgressiveDisplay
, nextOutputRow
);
340 // Copy rows from the buffer until we reach the desired output row.
342 OutputRows(0, nextHaeberliOutputRow
);
344 OutputRows(HaeberliOutputUntilRow(mPass
, mProgressiveDisplay
, InputSize(),
346 nextHaeberliOutputRow
);
349 // Update our position within the buffer.
351 mOutputRow
= nextOutputRow
;
353 // We'll actually write to the first Haeberli output row, then copy it until
354 // we reach the last Haeberli output row. The assertions above make sure
355 // this always includes mOutputRow.
356 return GetRowPointer(nextHaeberliOutputRow
);
360 static uint32_t InterlaceOffset(uint32_t aPass
) {
361 MOZ_ASSERT(aPass
< 4, "Invalid pass");
362 static const uint8_t offset
[] = {0, 4, 2, 1};
363 return offset
[aPass
];
366 static uint32_t InterlaceStride(uint32_t aPass
) {
367 MOZ_ASSERT(aPass
< 4, "Invalid pass");
368 static const uint8_t stride
[] = {8, 8, 4, 2};
369 return stride
[aPass
];
372 static int32_t HaeberliOutputStartRow(uint32_t aPass
,
373 bool aProgressiveDisplay
,
374 int32_t aOutputRow
) {
375 MOZ_ASSERT(aPass
< 4, "Invalid pass");
376 static const uint8_t firstRowOffset
[] = {3, 1, 0, 0};
378 if (aProgressiveDisplay
) {
379 return std::max(aOutputRow
- firstRowOffset
[aPass
], 0);
385 static int32_t HaeberliOutputUntilRow(uint32_t aPass
,
386 bool aProgressiveDisplay
,
387 const gfx::IntSize
& aInputSize
,
388 int32_t aOutputRow
) {
389 MOZ_ASSERT(aPass
< 4, "Invalid pass");
390 static const uint8_t lastRowOffset
[] = {4, 2, 1, 0};
392 if (aProgressiveDisplay
) {
393 return std::min(aOutputRow
+ lastRowOffset
[aPass
],
394 aInputSize
.height
- 1) +
395 1; // Add one because this is an open interval on the right.
397 return aOutputRow
+ 1;
401 void DuplicateRows(int32_t aStart
, int32_t aUntil
) {
402 MOZ_ASSERT(aStart
>= 0);
403 MOZ_ASSERT(aUntil
>= 0);
405 if (aUntil
<= aStart
|| aStart
>= InputSize().height
) {
409 // The source row is the first row in the range.
410 const uint8_t* sourceRowPointer
= GetRowPointer(aStart
);
412 // We duplicate the source row into each subsequent row in the range.
413 for (int32_t destRow
= aStart
+ 1; destRow
< aUntil
; ++destRow
) {
414 uint8_t* destRowPointer
= GetRowPointer(destRow
);
415 memcpy(destRowPointer
, sourceRowPointer
,
416 InputSize().width
* sizeof(PixelType
));
420 void OutputRows(int32_t aStart
, int32_t aUntil
) {
421 MOZ_ASSERT(aStart
>= 0);
422 MOZ_ASSERT(aUntil
>= 0);
424 if (aUntil
<= aStart
|| aStart
>= InputSize().height
) {
428 for (int32_t rowToOutput
= aStart
; rowToOutput
< aUntil
; ++rowToOutput
) {
430 reinterpret_cast<PixelType
*>(GetRowPointer(rowToOutput
)));
434 uint8_t* GetRowPointer(uint32_t aRow
) const {
436 uint64_t offset64
= uint64_t(aRow
) * uint64_t(InputSize().width
) *
437 uint64_t(sizeof(PixelType
));
438 uint64_t bufferLength
= uint64_t(InputSize().width
) *
439 uint64_t(InputSize().height
) *
440 uint64_t(sizeof(PixelType
));
441 MOZ_ASSERT(offset64
< bufferLength
, "Start of row is outside of image");
443 offset64
+ uint64_t(InputSize().width
) * uint64_t(sizeof(PixelType
)) <=
445 "End of row is outside of image");
447 uint32_t offset
= aRow
* InputSize().width
* sizeof(PixelType
);
448 return mBuffer
.get() + offset
;
451 Next mNext
; /// The next SurfaceFilter in the chain.
453 UniquePtr
<uint8_t[]> mBuffer
; /// The buffer used to store reordered rows.
454 int32_t mInputRow
; /// The current row we're reading. (0-indexed)
455 int32_t mOutputRow
; /// The current row we're writing. (0-indexed)
456 uint8_t mPass
; /// Which pass we're on. (0-indexed)
457 bool mProgressiveDisplay
; /// If true, duplicate rows to optimize for
458 /// progressive display.
461 //////////////////////////////////////////////////////////////////////////////
462 // BlendAnimationFilter
463 //////////////////////////////////////////////////////////////////////////////
465 template <typename Next
>
466 class BlendAnimationFilter
;
469 * A configuration struct for BlendAnimationFilter.
471 struct BlendAnimationConfig
{
472 template <typename Next
>
473 using Filter
= BlendAnimationFilter
<Next
>;
474 Decoder
* mDecoder
; /// The decoder producing the animation.
478 * BlendAnimationFilter turns a partial image as part of an animation into a
479 * complete frame given its frame rect, blend method, and the base frame's
480 * data buffer, frame rect and disposal method. Any excess data caused by a
481 * frame rect not being contained by the output size will be discarded.
483 * The base frame is an already produced complete frame from the animation.
484 * It may be any previous frame depending on the disposal method, although
485 * most often it will be the immediate previous frame to the current we are
488 * The 'Next' template parameter specifies the next filter in the chain.
490 template <typename Next
>
491 class BlendAnimationFilter final
: public SurfaceFilter
{
493 BlendAnimationFilter()
498 mRecycleRowOffset(0),
499 mRecycleRowLength(0),
502 mClearPrefixLength(0),
503 mClearInfixOffset(0),
504 mClearInfixLength(0),
505 mClearPostfixOffset(0),
506 mClearPostfixLength(0),
508 mBaseFrameStartPtr(nullptr),
509 mBaseFrameRowPtr(nullptr) {}
511 template <typename
... Rest
>
512 nsresult
Configure(const BlendAnimationConfig
& aConfig
,
513 const Rest
&... aRest
) {
514 nsresult rv
= mNext
.Configure(aRest
...);
519 imgFrame
* currentFrame
= aConfig
.mDecoder
->GetCurrentFrame();
521 MOZ_ASSERT_UNREACHABLE("Decoder must have current frame!");
522 return NS_ERROR_FAILURE
;
525 mFrameRect
= mUnclampedFrameRect
= currentFrame
->GetBlendRect();
526 gfx::IntSize outputSize
= mNext
.InputSize();
527 mRowLength
= outputSize
.width
* sizeof(uint32_t);
529 // Forbid frame rects with negative size.
530 if (mUnclampedFrameRect
.width
< 0 || mUnclampedFrameRect
.height
< 0) {
531 return NS_ERROR_FAILURE
;
534 // Clamp mFrameRect to the output size.
535 gfx::IntRect
outputRect(0, 0, outputSize
.width
, outputSize
.height
);
536 mFrameRect
= mFrameRect
.Intersect(outputRect
);
537 bool fullFrame
= outputRect
.IsEqualEdges(mFrameRect
);
539 // If there's no intersection, |mFrameRect| will be an empty rect positioned
540 // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
541 // not what we want. Force it to (0, 0) sized 0 x 0 in that case.
542 if (mFrameRect
.IsEmpty()) {
543 mFrameRect
.SetRect(0, 0, 0, 0);
546 BlendMethod blendMethod
= currentFrame
->GetBlendMethod();
547 switch (blendMethod
) {
549 blendMethod
= BlendMethod::SOURCE
;
550 MOZ_FALLTHROUGH_ASSERT("Unexpected blend method!");
551 case BlendMethod::SOURCE
:
552 // Default, overwrites base frame data (if any) with new.
554 case BlendMethod::OVER
:
555 // OVER only has an impact on the output if we have new data to blend
557 if (mFrameRect
.IsEmpty()) {
558 blendMethod
= BlendMethod::SOURCE
;
563 // Determine what we need to clear and what we need to copy. If this frame
564 // is a full frame and uses source blending, there is no need to consider
565 // the disposal method of the previous frame.
566 gfx::IntRect
dirtyRect(outputRect
);
567 gfx::IntRect clearRect
;
568 if (!fullFrame
|| blendMethod
!= BlendMethod::SOURCE
) {
569 const RawAccessFrameRef
& restoreFrame
=
570 aConfig
.mDecoder
->GetRestoreFrameRef();
572 MOZ_ASSERT(restoreFrame
->GetSize() == outputSize
);
573 MOZ_ASSERT(restoreFrame
->IsFinished());
575 // We can safely use this pointer without holding a RawAccessFrameRef
576 // because the decoder will keep it alive for us.
577 mBaseFrameStartPtr
= restoreFrame
.Data();
578 MOZ_ASSERT(mBaseFrameStartPtr
);
580 gfx::IntRect restoreBlendRect
= restoreFrame
->GetBoundedBlendRect();
581 gfx::IntRect restoreDirtyRect
= aConfig
.mDecoder
->GetRestoreDirtyRect();
582 switch (restoreFrame
->GetDisposalMethod()) {
584 case DisposalMethod::RESTORE_PREVIOUS
:
585 MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
586 case DisposalMethod::NOT_SPECIFIED
:
587 case DisposalMethod::KEEP
:
588 dirtyRect
= mFrameRect
.Union(restoreDirtyRect
);
590 case DisposalMethod::CLEAR
:
591 // We only need to clear if the rect is outside the frame rect (i.e.
592 // overwrites a non-overlapping area) or the blend method may cause
593 // us to combine old data and new.
594 if (!mFrameRect
.Contains(restoreBlendRect
) ||
595 blendMethod
== BlendMethod::OVER
) {
596 clearRect
= restoreBlendRect
;
599 // If we are clearing the whole frame, we do not need to retain a
600 // reference to the base frame buffer.
601 if (outputRect
.IsEqualEdges(clearRect
)) {
602 mBaseFrameStartPtr
= nullptr;
604 dirtyRect
= mFrameRect
.Union(restoreDirtyRect
).Union(clearRect
);
608 } else if (!fullFrame
) {
609 // This must be the first frame, clear everything.
610 clearRect
= outputRect
;
614 // We may be able to reuse parts of our underlying buffer that we are
615 // writing the new frame to. The recycle rect gives us the invalidation
616 // region which needs to be copied from the restore frame.
617 const gfx::IntRect
& recycleRect
= aConfig
.mDecoder
->GetRecycleRect();
618 mRecycleRow
= recycleRect
.y
;
619 mRecycleRowMost
= recycleRect
.YMost();
620 mRecycleRowOffset
= recycleRect
.x
* sizeof(uint32_t);
621 mRecycleRowLength
= recycleRect
.width
* sizeof(uint32_t);
623 if (!clearRect
.IsEmpty()) {
624 // The clear rect interacts with the recycle rect because we need to copy
625 // the prefix and postfix data from the base frame. The one thing we do
626 // know is that the infix area is always cleared explicitly.
627 mClearRow
= clearRect
.y
;
628 mClearRowMost
= clearRect
.YMost();
629 mClearInfixOffset
= clearRect
.x
* sizeof(uint32_t);
630 mClearInfixLength
= clearRect
.width
* sizeof(uint32_t);
632 // The recycle row offset is where we need to begin copying base frame
633 // data for a row. If this offset begins after or at the clear infix
634 // offset, then there is no prefix data at all.
635 if (mClearInfixOffset
> mRecycleRowOffset
) {
636 mClearPrefixLength
= mClearInfixOffset
- mRecycleRowOffset
;
639 // Similar to the prefix, if the postfix offset begins outside the recycle
640 // rect, then we know we already have all the data we need.
641 mClearPostfixOffset
= mClearInfixOffset
+ mClearInfixLength
;
642 size_t recycleRowEndOffset
= mRecycleRowOffset
+ mRecycleRowLength
;
643 if (mClearPostfixOffset
< recycleRowEndOffset
) {
644 mClearPostfixLength
= recycleRowEndOffset
- mClearPostfixOffset
;
648 // The dirty rect, or delta between the current frame and the previous frame
649 // (chronologically, not necessarily the restore frame) is the last
650 // animation parameter we need to initialize the new frame with.
651 currentFrame
->SetDirtyRect(dirtyRect
);
653 if (!mBaseFrameStartPtr
) {
654 // Switch to SOURCE if no base frame to ensure we don't allocate an
655 // intermediate buffer below. OVER does nothing without the base frame
657 blendMethod
= BlendMethod::SOURCE
;
660 // Skia provides arch-specific accelerated methods to perform blending.
661 // Note that this is an internal Skia API and may be prone to change,
662 // but we avoid the overhead of setting up Skia objects.
663 if (blendMethod
== BlendMethod::OVER
) {
664 mOverProc
= SkBlitRow::Factory32(SkBlitRow::kSrcPixelAlpha_Flag32
);
665 MOZ_ASSERT(mOverProc
);
668 // We don't need an intermediate buffer unless the unclamped frame rect
669 // width is larger than the clamped frame rect width. In that case, the
670 // caller will end up writing data that won't end up in the final image at
671 // all, and we'll need a buffer to give that data a place to go.
672 if (mFrameRect
.width
< mUnclampedFrameRect
.width
|| mOverProc
) {
673 mBuffer
.reset(new (fallible
)
674 uint8_t[mUnclampedFrameRect
.width
* sizeof(uint32_t)]);
675 if (MOZ_UNLIKELY(!mBuffer
)) {
676 return NS_ERROR_OUT_OF_MEMORY
;
679 memset(mBuffer
.get(), 0, mUnclampedFrameRect
.width
* sizeof(uint32_t));
682 ConfigureFilter(mUnclampedFrameRect
.Size(), sizeof(uint32_t));
686 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
687 return mNext
.TakeInvalidRect();
691 uint8_t* DoResetToFirstRow() override
{
692 uint8_t* rowPtr
= mNext
.ResetToFirstRow();
693 if (rowPtr
== nullptr) {
694 mRow
= mFrameRect
.YMost();
699 mBaseFrameRowPtr
= mBaseFrameStartPtr
;
701 while (mRow
< mFrameRect
.y
) {
703 AdvanceRowOutsideFrameRect();
706 // We're at the beginning of the frame rect now, so return if we're either
707 // ready for input or we're already done.
708 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
709 if (!mFrameRect
.IsEmpty() || rowPtr
== nullptr) {
710 // Note that the pointer we're returning is for the next row we're
711 // actually going to write to, but we may discard writes before that point
712 // if mRow < mFrameRect.y.
713 mRow
= mUnclampedFrameRect
.y
;
715 return AdjustRowPointer(rowPtr
);
718 // We've finished the region specified by the frame rect, but the frame rect
719 // is empty, so we need to output the rest of the image immediately. Advance
720 // to the end of the next pipeline stage's buffer, outputting rows that are
721 // copied from the base frame and/or cleared.
722 WriteBaseFrameRowsUntilComplete();
724 mRow
= mFrameRect
.YMost();
725 return nullptr; // We're done.
728 uint8_t* DoAdvanceRowFromBuffer(const uint8_t* aInputRow
) override
{
729 CopyInputRow(aInputRow
);
730 return DoAdvanceRow();
733 uint8_t* DoAdvanceRow() override
{
734 uint8_t* rowPtr
= nullptr;
736 const int32_t currentRow
= mRow
;
739 // The unclamped frame rect has a negative offset which means -y rows from
740 // the decoder need to be discarded before we advance properly.
741 if (currentRow
>= 0 && mBaseFrameRowPtr
) {
742 mBaseFrameRowPtr
+= mRowLength
;
745 if (currentRow
< mFrameRect
.y
) {
746 // This row is outside of the frame rect, so just drop it on the floor.
747 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
748 return AdjustRowPointer(rowPtr
);
749 } else if (NS_WARN_IF(currentRow
>= mFrameRect
.YMost())) {
753 // If we had to buffer, merge the data into the row. Otherwise we had the
754 // decoder write directly to the next stage's buffer.
756 int32_t width
= mFrameRect
.width
;
757 uint32_t* dst
= reinterpret_cast<uint32_t*>(mNext
.CurrentRowPointer());
758 uint32_t* src
= reinterpret_cast<uint32_t*>(mBuffer
.get()) -
759 std::min(mUnclampedFrameRect
.x
, 0);
762 mOverProc(dst
, src
, width
, 0xFF);
764 memcpy(dst
, src
, width
* sizeof(uint32_t));
766 rowPtr
= mNext
.AdvanceRow() ? mBuffer
.get() : nullptr;
768 MOZ_ASSERT(!mOverProc
);
769 rowPtr
= mNext
.AdvanceRow();
772 // If there's still more data coming or we're already done, just adjust the
773 // pointer and return.
774 if (mRow
< mFrameRect
.YMost() || rowPtr
== nullptr) {
776 return AdjustRowPointer(rowPtr
);
779 // We've finished the region specified by the frame rect. Advance to the end
780 // of the next pipeline stage's buffer, outputting rows that are copied from
781 // the base frame and/or cleared.
782 WriteBaseFrameRowsUntilComplete();
784 return nullptr; // We're done.
788 void WriteBaseFrameRowsUntilComplete() {
791 } while (AdvanceRowOutsideFrameRect());
794 void WriteBaseFrameRow() {
795 uint8_t* dest
= mNext
.CurrentRowPointer();
800 // No need to copy pixels from the base frame for rows that will not change
801 // between the recycled frame and the new frame.
802 bool needBaseFrame
= mRow
>= mRecycleRow
&& mRow
< mRecycleRowMost
;
804 if (!mBaseFrameRowPtr
) {
805 // No base frame, so we are clearing everything.
807 memset(dest
+ mRecycleRowOffset
, 0, mRecycleRowLength
);
809 } else if (mClearRow
<= mRow
&& mClearRowMost
> mRow
) {
810 // We have a base frame, but we are inside the area to be cleared.
811 // Only copy the data we need from the source.
813 memcpy(dest
+ mRecycleRowOffset
, mBaseFrameRowPtr
+ mRecycleRowOffset
,
815 memcpy(dest
+ mClearPostfixOffset
,
816 mBaseFrameRowPtr
+ mClearPostfixOffset
, mClearPostfixLength
);
818 memset(dest
+ mClearInfixOffset
, 0, mClearInfixLength
);
819 } else if (needBaseFrame
) {
820 memcpy(dest
+ mRecycleRowOffset
, mBaseFrameRowPtr
+ mRecycleRowOffset
,
825 bool AdvanceRowOutsideFrameRect() {
826 // The unclamped frame rect may have a negative offset however we should
827 // never be advancing the row via this path (otherwise mBaseFrameRowPtr
829 MOZ_ASSERT(mRow
>= 0);
830 MOZ_ASSERT(mRow
< mFrameRect
.y
|| mRow
>= mFrameRect
.YMost());
833 if (mBaseFrameRowPtr
) {
834 mBaseFrameRowPtr
+= mRowLength
;
837 return mNext
.AdvanceRow() != nullptr;
840 uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer
) const {
842 MOZ_ASSERT(aNextRowPointer
== mBuffer
.get() ||
843 aNextRowPointer
== nullptr);
844 return aNextRowPointer
; // No adjustment needed for an intermediate
848 if (mFrameRect
.IsEmpty() || mRow
>= mFrameRect
.YMost() ||
849 aNextRowPointer
== nullptr) {
850 return nullptr; // Nothing left to write.
853 MOZ_ASSERT(!mOverProc
);
854 return aNextRowPointer
+ mFrameRect
.x
* sizeof(uint32_t);
857 Next mNext
; /// The next SurfaceFilter in the chain.
859 gfx::IntRect mFrameRect
; /// The surface subrect which contains data,
860 /// clamped to the image size.
861 gfx::IntRect mUnclampedFrameRect
; /// The frame rect before clamping.
862 UniquePtr
<uint8_t[]> mBuffer
; /// The intermediate buffer, if one is
863 /// necessary because the frame rect width
864 /// is larger than the image's logical width.
865 int32_t mRow
; /// The row in unclamped frame rect space
866 /// that we're currently writing.
867 size_t mRowLength
; /// Length in bytes of a row that is the input
868 /// for the next filter.
869 int32_t mRecycleRow
; /// The starting row of the recycle rect.
870 int32_t mRecycleRowMost
; /// The ending row of the recycle rect.
871 size_t mRecycleRowOffset
; /// Row offset in bytes of the recycle rect.
872 size_t mRecycleRowLength
; /// Row length in bytes of the recycle rect.
874 /// The frame area to clear before blending the current frame.
875 int32_t mClearRow
; /// The starting row of the clear rect.
876 int32_t mClearRowMost
; /// The ending row of the clear rect.
877 size_t mClearPrefixLength
; /// Row length in bytes of clear prefix.
878 size_t mClearInfixOffset
; /// Row offset in bytes of clear area.
879 size_t mClearInfixLength
; /// Row length in bytes of clear area.
880 size_t mClearPostfixOffset
; /// Row offset in bytes of clear postfix.
881 size_t mClearPostfixLength
; /// Row length in bytes of clear postfix.
883 SkBlitRow::Proc32 mOverProc
; /// Function pointer to perform over blending.
885 mBaseFrameStartPtr
; /// Starting row pointer to the base frame
886 /// data from which we copy pixel data from.
887 const uint8_t* mBaseFrameRowPtr
; /// Current row pointer to the base frame
891 //////////////////////////////////////////////////////////////////////////////
892 // RemoveFrameRectFilter
893 //////////////////////////////////////////////////////////////////////////////
895 template <typename Next
>
896 class RemoveFrameRectFilter
;
899 * A configuration struct for RemoveFrameRectFilter.
901 struct RemoveFrameRectConfig
{
902 template <typename Next
>
903 using Filter
= RemoveFrameRectFilter
<Next
>;
904 gfx::IntRect mFrameRect
; /// The surface subrect which contains data.
908 * RemoveFrameRectFilter turns an image with a frame rect that does not match
909 * its logical size into an image with no frame rect. It does this by writing
910 * transparent pixels into any padding regions and throwing away excess data.
912 * The 'Next' template parameter specifies the next filter in the chain.
914 template <typename Next
>
915 class RemoveFrameRectFilter final
: public SurfaceFilter
{
917 RemoveFrameRectFilter() : mRow(0) {}
919 template <typename
... Rest
>
920 nsresult
Configure(const RemoveFrameRectConfig
& aConfig
,
921 const Rest
&... aRest
) {
922 nsresult rv
= mNext
.Configure(aRest
...);
927 mFrameRect
= mUnclampedFrameRect
= aConfig
.mFrameRect
;
928 gfx::IntSize outputSize
= mNext
.InputSize();
930 // Forbid frame rects with negative size.
931 if (aConfig
.mFrameRect
.Width() < 0 || aConfig
.mFrameRect
.Height() < 0) {
932 return NS_ERROR_INVALID_ARG
;
935 // Clamp mFrameRect to the output size.
936 gfx::IntRect
outputRect(0, 0, outputSize
.width
, outputSize
.height
);
937 mFrameRect
= mFrameRect
.Intersect(outputRect
);
939 // If there's no intersection, |mFrameRect| will be an empty rect positioned
940 // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
941 // not what we want. Force it to (0, 0) in that case.
942 if (mFrameRect
.IsEmpty()) {
943 mFrameRect
.MoveTo(0, 0);
946 // We don't need an intermediate buffer unless the unclamped frame rect
947 // width is larger than the clamped frame rect width. In that case, the
948 // caller will end up writing data that won't end up in the final image at
949 // all, and we'll need a buffer to give that data a place to go.
950 if (mFrameRect
.Width() < mUnclampedFrameRect
.Width()) {
952 fallible
) uint8_t[mUnclampedFrameRect
.Width() * sizeof(uint32_t)]);
953 if (MOZ_UNLIKELY(!mBuffer
)) {
954 return NS_ERROR_OUT_OF_MEMORY
;
957 memset(mBuffer
.get(), 0, mUnclampedFrameRect
.Width() * sizeof(uint32_t));
960 ConfigureFilter(mUnclampedFrameRect
.Size(), sizeof(uint32_t));
964 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
965 return mNext
.TakeInvalidRect();
969 uint8_t* DoResetToFirstRow() override
{
970 uint8_t* rowPtr
= mNext
.ResetToFirstRow();
971 if (rowPtr
== nullptr) {
972 mRow
= mFrameRect
.YMost();
976 mRow
= mUnclampedFrameRect
.Y();
978 // Advance the next pipeline stage to the beginning of the frame rect,
979 // outputting blank rows.
980 if (mFrameRect
.Y() > 0) {
981 for (int32_t rowToOutput
= 0; rowToOutput
< mFrameRect
.Y();
983 mNext
.WriteEmptyRow();
987 // We're at the beginning of the frame rect now, so return if we're either
988 // ready for input or we're already done.
989 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
990 if (!mFrameRect
.IsEmpty() || rowPtr
== nullptr) {
991 // Note that the pointer we're returning is for the next row we're
992 // actually going to write to, but we may discard writes before that point
993 // if mRow < mFrameRect.y.
994 return AdjustRowPointer(rowPtr
);
997 // We've finished the region specified by the frame rect, but the frame rect
998 // is empty, so we need to output the rest of the image immediately. Advance
999 // to the end of the next pipeline stage's buffer, outputting blank rows.
1000 while (mNext
.WriteEmptyRow() == WriteState::NEED_MORE_DATA
) {
1003 mRow
= mFrameRect
.YMost();
1004 return nullptr; // We're done.
1007 uint8_t* DoAdvanceRowFromBuffer(const uint8_t* aInputRow
) override
{
1008 CopyInputRow(aInputRow
);
1009 return DoAdvanceRow();
1012 uint8_t* DoAdvanceRow() override
{
1013 uint8_t* rowPtr
= nullptr;
1015 const int32_t currentRow
= mRow
;
1018 if (currentRow
< mFrameRect
.Y()) {
1019 // This row is outside of the frame rect, so just drop it on the floor.
1020 rowPtr
= mBuffer
? mBuffer
.get() : mNext
.CurrentRowPointer();
1021 return AdjustRowPointer(rowPtr
);
1022 } else if (currentRow
>= mFrameRect
.YMost()) {
1023 NS_WARNING("RemoveFrameRectFilter: Advancing past end of frame rect");
1027 // If we had to buffer, copy the data. Otherwise, just advance the row.
1029 // We write from the beginning of the buffer unless
1030 // |mUnclampedFrameRect.x| is negative; if that's the case, we have to
1031 // skip the portion of the unclamped frame rect that's outside the row.
1032 uint32_t* source
= reinterpret_cast<uint32_t*>(mBuffer
.get()) -
1033 std::min(mUnclampedFrameRect
.X(), 0);
1035 // We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've
1036 // already clamped these values to the size of the output, so we don't
1037 // have to worry about bounds checking here (though WriteBuffer() will do
1038 // it for us in any case).
1040 mNext
.WriteBuffer(source
, mFrameRect
.X(), mFrameRect
.Width());
1042 rowPtr
= state
== WriteState::NEED_MORE_DATA
? mBuffer
.get() : nullptr;
1044 rowPtr
= mNext
.AdvanceRow();
1047 // If there's still more data coming or we're already done, just adjust the
1048 // pointer and return.
1049 if (mRow
< mFrameRect
.YMost() || rowPtr
== nullptr) {
1050 return AdjustRowPointer(rowPtr
);
1053 // We've finished the region specified by the frame rect. Advance to the end
1054 // of the next pipeline stage's buffer, outputting blank rows.
1055 while (mNext
.WriteEmptyRow() == WriteState::NEED_MORE_DATA
) {
1058 mRow
= mFrameRect
.YMost();
1059 return nullptr; // We're done.
1063 uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer
) const {
1065 MOZ_ASSERT(aNextRowPointer
== mBuffer
.get() ||
1066 aNextRowPointer
== nullptr);
1067 return aNextRowPointer
; // No adjustment needed for an intermediate
1071 if (mFrameRect
.IsEmpty() || mRow
>= mFrameRect
.YMost() ||
1072 aNextRowPointer
== nullptr) {
1073 return nullptr; // Nothing left to write.
1076 return aNextRowPointer
+ mFrameRect
.X() * sizeof(uint32_t);
1079 Next mNext
; /// The next SurfaceFilter in the chain.
1081 gfx::IntRect mFrameRect
; /// The surface subrect which contains data,
1082 /// clamped to the image size.
1083 gfx::IntRect mUnclampedFrameRect
; /// The frame rect before clamping.
1084 UniquePtr
<uint8_t[]> mBuffer
; /// The intermediate buffer, if one is
1085 /// necessary because the frame rect width
1086 /// is larger than the image's logical width.
1087 int32_t mRow
; /// The row in unclamped frame rect space
1088 /// that we're currently writing.
1091 //////////////////////////////////////////////////////////////////////////////
1092 // ADAM7InterpolatingFilter
1093 //////////////////////////////////////////////////////////////////////////////
1095 template <typename Next
>
1096 class ADAM7InterpolatingFilter
;
1099 * A configuration struct for ADAM7InterpolatingFilter.
1101 struct ADAM7InterpolatingConfig
{
1102 template <typename Next
>
1103 using Filter
= ADAM7InterpolatingFilter
<Next
>;
1107 * ADAM7InterpolatingFilter performs bilinear interpolation over an ADAM7
1110 * ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new set
1111 * of pixels in each block receives their final values, according to the
1112 * following pattern:
1123 * When rendering the pixels that have not yet received their final values, we
1124 * can get much better intermediate results if we interpolate between
1125 * the pixels we *have* gotten so far. This filter performs bilinear
1126 * interpolation by first performing linear interpolation horizontally for each
1127 * "important" row (which we'll define as a row that has received any pixels
1128 * with final values at all) and then performing linear interpolation vertically
1129 * to produce pixel values for rows which aren't important on the current pass.
1131 * Note that this filter totally ignores the data which is written to rows which
1132 * aren't important on the current pass! It's fine to write nothing at all for
1133 * these rows, although doing so won't cause any harm.
1135 * XXX(seth): In bug 1280552 we'll add a SIMD implementation for this filter.
1137 * The 'Next' template parameter specifies the next filter in the chain.
1139 template <typename Next
>
1140 class ADAM7InterpolatingFilter final
: public SurfaceFilter
{
1142 ADAM7InterpolatingFilter()
1143 : mPass(0) // The current pass, in the range 1..7. Starts at 0 so that
1144 // DoResetToFirstRow() doesn't have to special case the first
1149 template <typename
... Rest
>
1150 nsresult
Configure(const ADAM7InterpolatingConfig
& aConfig
,
1151 const Rest
&... aRest
) {
1152 nsresult rv
= mNext
.Configure(aRest
...);
1153 if (NS_FAILED(rv
)) {
1157 // We have two intermediate buffers, one for the previous row with final
1158 // pixel values and one for the row that the previous filter in the chain is
1159 // currently writing to.
1160 size_t inputWidthInBytes
= mNext
.InputSize().width
* sizeof(uint32_t);
1161 mPreviousRow
.reset(new (fallible
) uint8_t[inputWidthInBytes
]);
1162 if (MOZ_UNLIKELY(!mPreviousRow
)) {
1163 return NS_ERROR_OUT_OF_MEMORY
;
1166 mCurrentRow
.reset(new (fallible
) uint8_t[inputWidthInBytes
]);
1167 if (MOZ_UNLIKELY(!mCurrentRow
)) {
1168 return NS_ERROR_OUT_OF_MEMORY
;
1171 memset(mPreviousRow
.get(), 0, inputWidthInBytes
);
1172 memset(mCurrentRow
.get(), 0, inputWidthInBytes
);
1174 ConfigureFilter(mNext
.InputSize(), sizeof(uint32_t));
1178 Maybe
<SurfaceInvalidRect
> TakeInvalidRect() override
{
1179 return mNext
.TakeInvalidRect();
1183 uint8_t* DoResetToFirstRow() override
{
1185 mPass
= std::min(mPass
+ 1, 7);
1187 uint8_t* rowPtr
= mNext
.ResetToFirstRow();
1189 // Short circuit this filter on the final pass, since all pixels have
1190 // their final values at that point.
1194 return mCurrentRow
.get();
1197 uint8_t* DoAdvanceRowFromBuffer(const uint8_t* aInputRow
) override
{
1198 CopyInputRow(aInputRow
);
1199 return DoAdvanceRow();
1202 uint8_t* DoAdvanceRow() override
{
1203 MOZ_ASSERT(0 < mPass
&& mPass
<= 7, "Invalid pass");
1205 int32_t currentRow
= mRow
;
1209 // On the final pass we short circuit this filter totally.
1210 return mNext
.AdvanceRow();
1213 const int32_t lastImportantRow
=
1214 LastImportantRow(InputSize().height
, mPass
);
1215 if (currentRow
> lastImportantRow
) {
1216 return nullptr; // This pass is already complete.
1219 if (!IsImportantRow(currentRow
, mPass
)) {
1220 // We just ignore whatever the caller gives us for these rows. We'll
1221 // interpolate them in later.
1222 return mCurrentRow
.get();
1225 // This is an important row. We need to perform horizontal interpolation for
1227 InterpolateHorizontally(mCurrentRow
.get(), InputSize().width
, mPass
);
1229 // Interpolate vertically between the previous important row and the current
1230 // important row. We skip this if the current row is 0 (which is always an
1231 // important row), because in that case there is no previous important row
1232 // to interpolate with.
1233 if (currentRow
!= 0) {
1234 InterpolateVertically(mPreviousRow
.get(), mCurrentRow
.get(), mPass
,
1238 // Write out the current row itself, which, being an important row, does not
1239 // need vertical interpolation.
1240 uint32_t* currentRowAsPixels
=
1241 reinterpret_cast<uint32_t*>(mCurrentRow
.get());
1242 mNext
.WriteBuffer(currentRowAsPixels
);
1244 if (currentRow
== lastImportantRow
) {
1245 // This is the last important row, which completes this pass. Note that
1246 // for very small images, this may be the first row! Since there won't be
1247 // another important row, there's nothing to interpolate with vertically,
1248 // so we just duplicate this row until the end of the image.
1249 while (mNext
.WriteBuffer(currentRowAsPixels
) ==
1250 WriteState::NEED_MORE_DATA
) {
1253 // All of the remaining rows in the image were determined above, so we're
1258 // The current row is now the previous important row; save it.
1259 Swap(mPreviousRow
, mCurrentRow
);
1261 MOZ_ASSERT(mRow
< InputSize().height
,
1262 "Reached the end of the surface without "
1263 "hitting the last important row?");
1265 return mCurrentRow
.get();
1269 static void InterpolateVertically(uint8_t* aPreviousRow
, uint8_t* aCurrentRow
,
1270 uint8_t aPass
, SurfaceFilter
& aNext
) {
1271 const float* weights
= InterpolationWeights(ImportantRowStride(aPass
));
1273 // We need to interpolate vertically to generate the rows between the
1274 // previous important row and the next one. Recall that important rows are
1275 // rows which contain at least some final pixels; see
1276 // InterpolateHorizontally() for some additional explanation as to what that
1277 // means. Note that we've already written out the previous important row, so
1278 // we start the iteration at 1.
1279 for (int32_t outRow
= 1; outRow
< ImportantRowStride(aPass
); ++outRow
) {
1280 const float weight
= weights
[outRow
];
1282 // We iterate through the previous and current important row every time we
1283 // write out an interpolated row, so we need to copy the pointers.
1284 uint8_t* prevRowBytes
= aPreviousRow
;
1285 uint8_t* currRowBytes
= aCurrentRow
;
1287 // Write out the interpolated pixels. Interpolation is componentwise.
1288 aNext
.template WritePixelsToRow
<uint32_t>([&] {
1290 auto* component
= reinterpret_cast<uint8_t*>(&pixel
);
1292 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1294 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1296 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1298 InterpolateByte(*prevRowBytes
++, *currRowBytes
++, weight
);
1299 return AsVariant(pixel
);
1304 static void InterpolateHorizontally(uint8_t* aRow
, int32_t aWidth
,
1306 // Collect the data we'll need to perform horizontal interpolation. The
1307 // terminology here bears some explanation: a "final pixel" is a pixel which
1308 // has received its final value. On each pass, a new set of pixels receives
1309 // their final value; see the diagram above of the 8x8 pattern that ADAM7
1310 // uses. Any pixel which hasn't received its final value on this pass
1311 // derives its value from either horizontal or vertical interpolation
1313 const size_t finalPixelStride
= FinalPixelStride(aPass
);
1314 const size_t finalPixelStrideBytes
= finalPixelStride
* sizeof(uint32_t);
1315 const size_t lastFinalPixel
= LastFinalPixel(aWidth
, aPass
);
1316 const size_t lastFinalPixelBytes
= lastFinalPixel
* sizeof(uint32_t);
1317 const float* weights
= InterpolationWeights(finalPixelStride
);
1319 // Interpolate blocks of pixels which lie between two final pixels.
1320 // Horizontal interpolation is done in place, as we'll need the results
1321 // later when we vertically interpolate.
1322 for (size_t blockBytes
= 0; blockBytes
< lastFinalPixelBytes
;
1323 blockBytes
+= finalPixelStrideBytes
) {
1324 uint8_t* finalPixelA
= aRow
+ blockBytes
;
1325 uint8_t* finalPixelB
= aRow
+ blockBytes
+ finalPixelStrideBytes
;
1327 MOZ_ASSERT(finalPixelA
< aRow
+ aWidth
* sizeof(uint32_t),
1328 "Running off end of buffer");
1329 MOZ_ASSERT(finalPixelB
< aRow
+ aWidth
* sizeof(uint32_t),
1330 "Running off end of buffer");
1332 // Interpolate the individual pixels componentwise. Note that we start
1333 // iteration at 1 since we don't need to apply any interpolation to the
1334 // first pixel in the block, which has its final value.
1335 for (size_t pixelIndex
= 1; pixelIndex
< finalPixelStride
; ++pixelIndex
) {
1336 const float weight
= weights
[pixelIndex
];
1337 uint8_t* pixel
= aRow
+ blockBytes
+ pixelIndex
* sizeof(uint32_t);
1339 MOZ_ASSERT(pixel
< aRow
+ aWidth
* sizeof(uint32_t),
1340 "Running off end of buffer");
1342 for (size_t component
= 0; component
< sizeof(uint32_t); ++component
) {
1343 pixel
[component
] = InterpolateByte(finalPixelA
[component
],
1344 finalPixelB
[component
], weight
);
1349 // For the pixels after the last final pixel in the row, there isn't a
1350 // second final pixel to interpolate with, so just duplicate.
1351 uint32_t* rowPixels
= reinterpret_cast<uint32_t*>(aRow
);
1352 uint32_t pixelToDuplicate
= rowPixels
[lastFinalPixel
];
1353 for (int32_t pixelIndex
= lastFinalPixel
+ 1; pixelIndex
< aWidth
;
1355 MOZ_ASSERT(pixelIndex
< aWidth
, "Running off end of buffer");
1356 rowPixels
[pixelIndex
] = pixelToDuplicate
;
1360 static uint8_t InterpolateByte(uint8_t aByteA
, uint8_t aByteB
,
1362 return uint8_t(aByteA
* aWeight
+ aByteB
* (1.0f
- aWeight
));
1365 static int32_t ImportantRowStride(uint8_t aPass
) {
1366 MOZ_ASSERT(0 < aPass
&& aPass
<= 7, "Invalid pass");
1368 // The stride between important rows for each pass, with a dummy value for
1369 // the nonexistent pass 0.
1370 static int32_t strides
[] = {1, 8, 8, 4, 4, 2, 2, 1};
1372 return strides
[aPass
];
1375 static bool IsImportantRow(int32_t aRow
, uint8_t aPass
) {
1376 MOZ_ASSERT(aRow
>= 0);
1378 // Whether the row is important comes down to divisibility by the stride for
1379 // this pass, which is always a power of 2, so we can check using a mask.
1380 int32_t mask
= ImportantRowStride(aPass
) - 1;
1381 return (aRow
& mask
) == 0;
1384 static int32_t LastImportantRow(int32_t aHeight
, uint8_t aPass
) {
1385 MOZ_ASSERT(aHeight
> 0);
1387 // We can find the last important row using the same mask trick as above.
1388 int32_t lastRow
= aHeight
- 1;
1389 int32_t mask
= ImportantRowStride(aPass
) - 1;
1390 return lastRow
- (lastRow
& mask
);
1393 static size_t FinalPixelStride(uint8_t aPass
) {
1394 MOZ_ASSERT(0 < aPass
&& aPass
<= 7, "Invalid pass");
1396 // The stride between the final pixels in important rows for each pass, with
1397 // a dummy value for the nonexistent pass 0.
1398 static size_t strides
[] = {1, 8, 4, 4, 2, 2, 1, 1};
1400 return strides
[aPass
];
1403 static size_t LastFinalPixel(int32_t aWidth
, uint8_t aPass
) {
1404 MOZ_ASSERT(aWidth
>= 0);
1406 // Again, we can use the mask trick above to find the last important pixel.
1407 int32_t lastColumn
= aWidth
- 1;
1408 size_t mask
= FinalPixelStride(aPass
) - 1;
1409 return lastColumn
- (lastColumn
& mask
);
1412 static const float* InterpolationWeights(int32_t aStride
) {
1413 // Precalculated interpolation weights. These are used to interpolate
1414 // between final pixels or between important rows. Although no interpolation
1415 // is actually applied to the previous final pixel or important row value,
1416 // the arrays still start with 1.0f, which is always skipped, primarily
1417 // because otherwise |stride1Weights| would have zero elements.
1418 static float stride8Weights
[] = {1.0f
, 7 / 8.0f
, 6 / 8.0f
, 5 / 8.0f
,
1419 4 / 8.0f
, 3 / 8.0f
, 2 / 8.0f
, 1 / 8.0f
};
1420 static float stride4Weights
[] = {1.0f
, 3 / 4.0f
, 2 / 4.0f
, 1 / 4.0f
};
1421 static float stride2Weights
[] = {1.0f
, 1 / 2.0f
};
1422 static float stride1Weights
[] = {1.0f
};
1426 return stride8Weights
;
1428 return stride4Weights
;
1430 return stride2Weights
;
1432 return stride1Weights
;
1438 Next mNext
; /// The next SurfaceFilter in the chain.
1440 UniquePtr
<uint8_t[]>
1441 mPreviousRow
; /// The last important row (i.e., row with
1442 /// final pixel values) that got written to.
1443 UniquePtr
<uint8_t[]> mCurrentRow
; /// The row that's being written to right
1445 uint8_t mPass
; /// Which ADAM7 pass we're on. Valid passes
1446 /// are 1..7 during processing and 0 prior
1447 /// to configuration.
1448 int32_t mRow
; /// The row we're currently reading.
1451 } // namespace image
1452 } // namespace mozilla
1454 #endif // mozilla_image_SurfaceFilters_h