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