Bug 1646700 [wpt PR 24235] - Update picture-in-picture idlharness test, a=testonly
[gecko.git] / image / SurfaceFilters.h
blob92c406386aedd9f83b0ceb152761cb1b53870cca
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/. */
7 /**
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
15 #include <algorithm>
16 #include <stdint.h>
17 #include <string.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"
30 namespace mozilla {
31 namespace image {
33 //////////////////////////////////////////////////////////////////////////////
34 // SwizzleFilter
35 //////////////////////////////////////////////////////////////////////////////
37 template <typename Next>
38 class SwizzleFilter;
40 /**
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;
51 /**
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 {
60 public:
61 SwizzleFilter() : mSwizzleFn(nullptr) {}
63 template <typename... Rest>
64 nsresult Configure(const SwizzleConfig& aConfig, const Rest&... aRest) {
65 nsresult rv = mNext.Configure(aRest...);
66 if (NS_FAILED(rv)) {
67 return rv;
70 if (aConfig.mPremultiplyAlpha) {
71 mSwizzleFn = gfx::PremultiplyRow(aConfig.mInFormat, aConfig.mOutFormat);
72 } else {
73 mSwizzleFn = gfx::SwizzleRow(aConfig.mInFormat, aConfig.mOutFormat);
76 if (!mSwizzleFn) {
77 return NS_ERROR_INVALID_ARG;
80 ConfigureFilter(mNext.InputSize(), sizeof(uint32_t));
81 return NS_OK;
84 Maybe<SurfaceInvalidRect> TakeInvalidRect() override {
85 return mNext.TakeInvalidRect();
88 protected:
89 uint8_t* DoResetToFirstRow() override { return mNext.ResetToFirstRow(); }
91 uint8_t* DoAdvanceRowFromBuffer(const uint8_t* aInputRow) override {
92 uint8_t* rowPtr = mNext.CurrentRowPointer();
93 if (!rowPtr) {
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
128 * to it.
130 * The 'Next' template parameter specifies the next filter in the chain.
132 template <typename Next>
133 class ColorManagementFilter final : public SurfaceFilter {
134 public:
135 ColorManagementFilter() : mTransform(nullptr) {}
137 template <typename... Rest>
138 nsresult Configure(const ColorManagementConfig& aConfig,
139 const Rest&... aRest) {
140 nsresult rv = mNext.Configure(aRest...);
141 if (NS_FAILED(rv)) {
142 return rv;
145 if (!aConfig.mTransform) {
146 return NS_ERROR_INVALID_ARG;
149 mTransform = aConfig.mTransform;
150 ConfigureFilter(mNext.InputSize(), sizeof(uint32_t));
151 return NS_OK;
154 Maybe<SurfaceInvalidRect> TakeInvalidRect() override {
155 return mNext.TakeInvalidRect();
158 protected:
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
200 * written to it.
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 {
209 public:
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...);
217 if (NS_FAILED(rv)) {
218 return rv;
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));
251 return NS_OK;
254 Maybe<SurfaceInvalidRect> TakeInvalidRect() override {
255 return mNext.TakeInvalidRect();
258 protected:
259 uint8_t* DoResetToFirstRow() override {
260 mNext.ResetToFirstRow();
261 mPass = 0;
262 mInputRow = 0;
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 {
273 if (mPass >= 4) {
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.
282 DuplicateRows(
283 HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow),
284 HaeberliOutputUntilRow(mPass, mProgressiveDisplay, InputSize(),
285 mOutputRow));
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(),
291 mOutputRow));
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.
299 if (!advancedPass) {
300 OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
301 InputSize(), mOutputRow),
302 InputSize().height);
305 // We finished the current pass; advance to the next one.
306 mPass++;
307 if (mPass >= 4) {
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.
315 advancedPass = true;
316 stride = InterlaceStride(mPass);
317 nextOutputRow = InterlaceOffset(mPass);
320 MOZ_ASSERT(nextOutputRow >= 0);
321 MOZ_ASSERT(nextOutputRow < InputSize().height);
323 MOZ_ASSERT(
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.
341 if (advancedPass) {
342 OutputRows(0, nextHaeberliOutputRow);
343 } else {
344 OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay, InputSize(),
345 mOutputRow),
346 nextHaeberliOutputRow);
349 // Update our position within the buffer.
350 mInputRow++;
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);
359 private:
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);
380 } else {
381 return aOutputRow;
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.
396 } else {
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) {
406 return;
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) {
425 return;
428 for (int32_t rowToOutput = aStart; rowToOutput < aUntil; ++rowToOutput) {
429 mNext.WriteBuffer(
430 reinterpret_cast<PixelType*>(GetRowPointer(rowToOutput)));
434 uint8_t* GetRowPointer(uint32_t aRow) const {
435 #ifdef DEBUG
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");
442 MOZ_ASSERT(
443 offset64 + uint64_t(InputSize().width) * uint64_t(sizeof(PixelType)) <=
444 bufferLength,
445 "End of row is outside of image");
446 #endif
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
486 * generating.
488 * The 'Next' template parameter specifies the next filter in the chain.
490 template <typename Next>
491 class BlendAnimationFilter final : public SurfaceFilter {
492 public:
493 BlendAnimationFilter()
494 : mRow(0),
495 mRowLength(0),
496 mRecycleRow(0),
497 mRecycleRowMost(0),
498 mRecycleRowOffset(0),
499 mRecycleRowLength(0),
500 mClearRow(0),
501 mClearRowMost(0),
502 mClearPrefixLength(0),
503 mClearInfixOffset(0),
504 mClearInfixLength(0),
505 mClearPostfixOffset(0),
506 mClearPostfixLength(0),
507 mOverProc(nullptr),
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...);
515 if (NS_FAILED(rv)) {
516 return rv;
519 imgFrame* currentFrame = aConfig.mDecoder->GetCurrentFrame();
520 if (!currentFrame) {
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) {
548 default:
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.
553 break;
554 case BlendMethod::OVER:
555 // OVER only has an impact on the output if we have new data to blend
556 // with.
557 if (mFrameRect.IsEmpty()) {
558 blendMethod = BlendMethod::SOURCE;
560 break;
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();
571 if (restoreFrame) {
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()) {
583 default:
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);
589 break;
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;
603 } else {
604 dirtyRect = mFrameRect.Union(restoreDirtyRect).Union(clearRect);
606 break;
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
656 // data.
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));
683 return NS_OK;
686 Maybe<SurfaceInvalidRect> TakeInvalidRect() override {
687 return mNext.TakeInvalidRect();
690 protected:
691 uint8_t* DoResetToFirstRow() override {
692 uint8_t* rowPtr = mNext.ResetToFirstRow();
693 if (rowPtr == nullptr) {
694 mRow = mFrameRect.YMost();
695 return nullptr;
698 mRow = 0;
699 mBaseFrameRowPtr = mBaseFrameStartPtr;
701 while (mRow < mFrameRect.y) {
702 WriteBaseFrameRow();
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;
714 WriteBaseFrameRow();
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;
737 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())) {
750 return nullptr;
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.
755 if (mBuffer) {
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);
760 dst += mFrameRect.x;
761 if (mOverProc) {
762 mOverProc(dst, src, width, 0xFF);
763 } else {
764 memcpy(dst, src, width * sizeof(uint32_t));
766 rowPtr = mNext.AdvanceRow() ? mBuffer.get() : nullptr;
767 } else {
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) {
775 WriteBaseFrameRow();
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.
787 private:
788 void WriteBaseFrameRowsUntilComplete() {
789 do {
790 WriteBaseFrameRow();
791 } while (AdvanceRowOutsideFrameRect());
794 void WriteBaseFrameRow() {
795 uint8_t* dest = mNext.CurrentRowPointer();
796 if (!dest) {
797 return;
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.
806 if (needBaseFrame) {
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.
812 if (needBaseFrame) {
813 memcpy(dest + mRecycleRowOffset, mBaseFrameRowPtr + mRecycleRowOffset,
814 mClearPrefixLength);
815 memcpy(dest + mClearPostfixOffset,
816 mBaseFrameRowPtr + mClearPostfixOffset, mClearPostfixLength);
818 memset(dest + mClearInfixOffset, 0, mClearInfixLength);
819 } else if (needBaseFrame) {
820 memcpy(dest + mRecycleRowOffset, mBaseFrameRowPtr + mRecycleRowOffset,
821 mRecycleRowLength);
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
828 // will be wrong.
829 MOZ_ASSERT(mRow >= 0);
830 MOZ_ASSERT(mRow < mFrameRect.y || mRow >= mFrameRect.YMost());
832 mRow++;
833 if (mBaseFrameRowPtr) {
834 mBaseFrameRowPtr += mRowLength;
837 return mNext.AdvanceRow() != nullptr;
840 uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer) const {
841 if (mBuffer) {
842 MOZ_ASSERT(aNextRowPointer == mBuffer.get() ||
843 aNextRowPointer == nullptr);
844 return aNextRowPointer; // No adjustment needed for an intermediate
845 // buffer.
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.
884 const uint8_t*
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
888 /// data.
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 {
916 public:
917 RemoveFrameRectFilter() : mRow(0) {}
919 template <typename... Rest>
920 nsresult Configure(const RemoveFrameRectConfig& aConfig,
921 const Rest&... aRest) {
922 nsresult rv = mNext.Configure(aRest...);
923 if (NS_FAILED(rv)) {
924 return rv;
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()) {
951 mBuffer.reset(new (
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));
961 return NS_OK;
964 Maybe<SurfaceInvalidRect> TakeInvalidRect() override {
965 return mNext.TakeInvalidRect();
968 protected:
969 uint8_t* DoResetToFirstRow() override {
970 uint8_t* rowPtr = mNext.ResetToFirstRow();
971 if (rowPtr == nullptr) {
972 mRow = mFrameRect.YMost();
973 return nullptr;
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();
982 ++rowToOutput) {
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;
1016 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");
1024 return nullptr;
1027 // If we had to buffer, copy the data. Otherwise, just advance the row.
1028 if (mBuffer) {
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).
1039 WriteState state =
1040 mNext.WriteBuffer(source, mFrameRect.X(), mFrameRect.Width());
1042 rowPtr = state == WriteState::NEED_MORE_DATA ? mBuffer.get() : nullptr;
1043 } else {
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.
1062 private:
1063 uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer) const {
1064 if (mBuffer) {
1065 MOZ_ASSERT(aNextRowPointer == mBuffer.get() ||
1066 aNextRowPointer == nullptr);
1067 return aNextRowPointer; // No adjustment needed for an intermediate
1068 // buffer.
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
1108 * interlaced image.
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:
1114 * 1 6 4 6 2 6 4 6
1115 * 7 7 7 7 7 7 7 7
1116 * 5 6 5 6 5 6 5 6
1117 * 7 7 7 7 7 7 7 7
1118 * 3 6 4 6 3 6 4 6
1119 * 7 7 7 7 7 7 7 7
1120 * 5 6 5 6 5 6 5 6
1121 * 7 7 7 7 7 7 7 7
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 {
1141 public:
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
1145 // pass.
1147 mRow(0) {}
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)) {
1154 return 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));
1175 return NS_OK;
1178 Maybe<SurfaceInvalidRect> TakeInvalidRect() override {
1179 return mNext.TakeInvalidRect();
1182 protected:
1183 uint8_t* DoResetToFirstRow() override {
1184 mRow = 0;
1185 mPass = std::min(mPass + 1, 7);
1187 uint8_t* rowPtr = mNext.ResetToFirstRow();
1188 if (mPass == 7) {
1189 // Short circuit this filter on the final pass, since all pixels have
1190 // their final values at that point.
1191 return rowPtr;
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;
1206 ++mRow;
1208 if (mPass == 7) {
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
1226 // these rows.
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,
1235 mNext);
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
1254 // done.
1255 return nullptr;
1258 // The current row is now the previous important row; save it.
1259 std::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();
1268 private:
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>([&] {
1289 uint32_t pixel = 0;
1290 auto* component = reinterpret_cast<uint8_t*>(&pixel);
1291 *component++ =
1292 InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
1293 *component++ =
1294 InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
1295 *component++ =
1296 InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
1297 *component++ =
1298 InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
1299 return AsVariant(pixel);
1304 static void InterpolateHorizontally(uint8_t* aRow, int32_t aWidth,
1305 uint8_t aPass) {
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
1312 // instead.
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;
1354 ++pixelIndex) {
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,
1361 float aWeight) {
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};
1424 switch (aStride) {
1425 case 8:
1426 return stride8Weights;
1427 case 4:
1428 return stride4Weights;
1429 case 2:
1430 return stride2Weights;
1431 case 1:
1432 return stride1Weights;
1433 default:
1434 MOZ_CRASH();
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
1444 /// now.
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