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