123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- /**
- * This header contains various SurfaceFilter implementations that apply
- * transformations to image data, for usage with SurfacePipe.
- */
- #ifndef mozilla_image_SurfaceFilters_h
- #define mozilla_image_SurfaceFilters_h
- #include <algorithm>
- #include <stdint.h>
- #include <string.h>
- #include "mozilla/Likely.h"
- #include "mozilla/Maybe.h"
- #include "mozilla/UniquePtr.h"
- #include "mozilla/gfx/2D.h"
- #include "DownscalingFilter.h"
- #include "SurfaceCache.h"
- #include "SurfacePipe.h"
- namespace mozilla {
- namespace image {
- //////////////////////////////////////////////////////////////////////////////
- // DeinterlacingFilter
- //////////////////////////////////////////////////////////////////////////////
- template <typename PixelType, typename Next> class DeinterlacingFilter;
- /**
- * A configuration struct for DeinterlacingFilter.
- *
- * The 'PixelType' template parameter should be either uint32_t (for output to a
- * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
- */
- template <typename PixelType>
- struct DeinterlacingConfig
- {
- template <typename Next> using Filter = DeinterlacingFilter<PixelType, Next>;
- bool mProgressiveDisplay; /// If true, duplicate rows during deinterlacing
- /// to make progressive display look better, at
- /// the cost of some performance.
- };
- /**
- * DeinterlacingFilter performs deinterlacing by reordering the rows that are
- * written to it.
- *
- * The 'PixelType' template parameter should be either uint32_t (for output to a
- * SurfaceSink) or uint8_t (for output to a PalettedSurfaceSink).
- *
- * The 'Next' template parameter specifies the next filter in the chain.
- */
- template <typename PixelType, typename Next>
- class DeinterlacingFilter final : public SurfaceFilter
- {
- public:
- DeinterlacingFilter()
- : mInputRow(0)
- , mOutputRow(0)
- , mPass(0)
- , mProgressiveDisplay(true)
- { }
- template <typename... Rest>
- nsresult Configure(const DeinterlacingConfig<PixelType>& aConfig, const Rest&... aRest)
- {
- nsresult rv = mNext.Configure(aRest...);
- if (NS_FAILED(rv)) {
- return rv;
- }
- if (sizeof(PixelType) == 1 && !mNext.IsValidPalettedPipe()) {
- NS_WARNING("Paletted DeinterlacingFilter used with non-paletted pipe?");
- return NS_ERROR_INVALID_ARG;
- }
- if (sizeof(PixelType) == 4 && mNext.IsValidPalettedPipe()) {
- NS_WARNING("Non-paletted DeinterlacingFilter used with paletted pipe?");
- return NS_ERROR_INVALID_ARG;
- }
- gfx::IntSize outputSize = mNext.InputSize();
- mProgressiveDisplay = aConfig.mProgressiveDisplay;
- const uint32_t bufferSize = outputSize.width *
- outputSize.height *
- sizeof(PixelType);
- // Use the size of the SurfaceCache as a heuristic to avoid gigantic
- // allocations. Even if DownscalingFilter allowed us to allocate space for
- // the output image, the deinterlacing buffer may still be too big, and
- // fallible allocation won't always save us in the presence of overcommit.
- if (!SurfaceCache::CanHold(bufferSize)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- // Allocate the buffer, which contains deinterlaced scanlines of the image.
- // The buffer is necessary so that we can output rows which have already
- // been deinterlaced again on subsequent passes. Since a later stage in the
- // pipeline may be transforming the rows it receives (for example, by
- // downscaling them), the rows may no longer exist in their original form on
- // the surface itself.
- mBuffer.reset(new (fallible) uint8_t[bufferSize]);
- if (MOZ_UNLIKELY(!mBuffer)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- // Clear the buffer to avoid writing uninitialized memory to the output.
- memset(mBuffer.get(), 0, bufferSize);
- ConfigureFilter(outputSize, sizeof(PixelType));
- return NS_OK;
- }
- bool IsValidPalettedPipe() const override
- {
- return sizeof(PixelType) == 1 && mNext.IsValidPalettedPipe();
- }
- Maybe<SurfaceInvalidRect> TakeInvalidRect() override
- {
- return mNext.TakeInvalidRect();
- }
- protected:
- uint8_t* DoResetToFirstRow() override
- {
- mNext.ResetToFirstRow();
- mPass = 0;
- mInputRow = 0;
- mOutputRow = InterlaceOffset(mPass);
- return GetRowPointer(mOutputRow);
- }
- uint8_t* DoAdvanceRow() override
- {
- if (mPass >= 4) {
- return nullptr; // We already finished all passes.
- }
- if (mInputRow >= InputSize().height) {
- return nullptr; // We already got all the input rows we expect.
- }
- // Duplicate from the first Haeberli row to the remaining Haeberli rows
- // within the buffer.
- DuplicateRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow),
- HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
- InputSize(), mOutputRow));
- // Write the current set of Haeberli rows (which contains the current row)
- // to the next stage in the pipeline.
- OutputRows(HaeberliOutputStartRow(mPass, mProgressiveDisplay, mOutputRow),
- HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
- InputSize(), mOutputRow));
- // Determine which output row the next input row corresponds to.
- bool advancedPass = false;
- uint32_t stride = InterlaceStride(mPass);
- int32_t nextOutputRow = mOutputRow + stride;
- while (nextOutputRow >= InputSize().height) {
- // Copy any remaining rows from the buffer.
- if (!advancedPass) {
- OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
- InputSize(), mOutputRow),
- InputSize().height);
- }
- // We finished the current pass; advance to the next one.
- mPass++;
- if (mPass >= 4) {
- return nullptr; // Finished all passes.
- }
- // Tell the next pipeline stage that we're starting the next pass.
- mNext.ResetToFirstRow();
- // Update our state to reflect the pass change.
- advancedPass = true;
- stride = InterlaceStride(mPass);
- nextOutputRow = InterlaceOffset(mPass);
- }
- MOZ_ASSERT(nextOutputRow >= 0);
- MOZ_ASSERT(nextOutputRow < InputSize().height);
- MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
- nextOutputRow) >= 0);
- MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
- nextOutputRow) < InputSize().height);
- MOZ_ASSERT(HaeberliOutputStartRow(mPass, mProgressiveDisplay,
- nextOutputRow) <= nextOutputRow);
- MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
- InputSize(), nextOutputRow) >= 0);
- MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
- InputSize(), nextOutputRow)
- <= InputSize().height);
- MOZ_ASSERT(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
- InputSize(), nextOutputRow)
- > nextOutputRow);
- int32_t nextHaeberliOutputRow =
- HaeberliOutputStartRow(mPass, mProgressiveDisplay, nextOutputRow);
- // Copy rows from the buffer until we reach the desired output row.
- if (advancedPass) {
- OutputRows(0, nextHaeberliOutputRow);
- } else {
- OutputRows(HaeberliOutputUntilRow(mPass, mProgressiveDisplay,
- InputSize(), mOutputRow),
- nextHaeberliOutputRow);
- }
- // Update our position within the buffer.
- mInputRow++;
- mOutputRow = nextOutputRow;
- // We'll actually write to the first Haeberli output row, then copy it until
- // we reach the last Haeberli output row. The assertions above make sure
- // this always includes mOutputRow.
- return GetRowPointer(nextHaeberliOutputRow);
- }
- private:
- static uint32_t InterlaceOffset(uint32_t aPass)
- {
- MOZ_ASSERT(aPass < 4, "Invalid pass");
- static const uint8_t offset[] = { 0, 4, 2, 1 };
- return offset[aPass];
- }
- static uint32_t InterlaceStride(uint32_t aPass)
- {
- MOZ_ASSERT(aPass < 4, "Invalid pass");
- static const uint8_t stride[] = { 8, 8, 4, 2 };
- return stride[aPass];
- }
- static int32_t HaeberliOutputStartRow(uint32_t aPass,
- bool aProgressiveDisplay,
- int32_t aOutputRow)
- {
- MOZ_ASSERT(aPass < 4, "Invalid pass");
- static const uint8_t firstRowOffset[] = { 3, 1, 0, 0 };
- if (aProgressiveDisplay) {
- return std::max(aOutputRow - firstRowOffset[aPass], 0);
- } else {
- return aOutputRow;
- }
- }
- static int32_t HaeberliOutputUntilRow(uint32_t aPass,
- bool aProgressiveDisplay,
- const gfx::IntSize& aInputSize,
- int32_t aOutputRow)
- {
- MOZ_ASSERT(aPass < 4, "Invalid pass");
- static const uint8_t lastRowOffset[] = { 4, 2, 1, 0 };
- if (aProgressiveDisplay) {
- return std::min(aOutputRow + lastRowOffset[aPass],
- aInputSize.height - 1)
- + 1; // Add one because this is an open interval on the right.
- } else {
- return aOutputRow + 1;
- }
- }
- void DuplicateRows(int32_t aStart, int32_t aUntil)
- {
- MOZ_ASSERT(aStart >= 0);
- MOZ_ASSERT(aUntil >= 0);
- if (aUntil <= aStart || aStart >= InputSize().height) {
- return;
- }
- // The source row is the first row in the range.
- const uint8_t* sourceRowPointer = GetRowPointer(aStart);
- // We duplicate the source row into each subsequent row in the range.
- for (int32_t destRow = aStart + 1 ; destRow < aUntil ; ++destRow) {
- uint8_t* destRowPointer = GetRowPointer(destRow);
- memcpy(destRowPointer, sourceRowPointer, InputSize().width * sizeof(PixelType));
- }
- }
- void OutputRows(int32_t aStart, int32_t aUntil)
- {
- MOZ_ASSERT(aStart >= 0);
- MOZ_ASSERT(aUntil >= 0);
- if (aUntil <= aStart || aStart >= InputSize().height) {
- return;
- }
- for (int32_t rowToOutput = aStart; rowToOutput < aUntil; ++rowToOutput) {
- mNext.WriteBuffer(reinterpret_cast<PixelType*>(GetRowPointer(rowToOutput)));
- }
- }
- uint8_t* GetRowPointer(uint32_t aRow) const
- {
- uint32_t offset = aRow * InputSize().width * sizeof(PixelType);
- MOZ_ASSERT(offset < InputSize().width * InputSize().height * sizeof(PixelType),
- "Start of row is outside of image");
- MOZ_ASSERT(offset + InputSize().width * sizeof(PixelType)
- <= InputSize().width * InputSize().height * sizeof(PixelType),
- "End of row is outside of image");
- return mBuffer.get() + offset;
- }
- Next mNext; /// The next SurfaceFilter in the chain.
- UniquePtr<uint8_t[]> mBuffer; /// The buffer used to store reordered rows.
- int32_t mInputRow; /// The current row we're reading. (0-indexed)
- int32_t mOutputRow; /// The current row we're writing. (0-indexed)
- uint8_t mPass; /// Which pass we're on. (0-indexed)
- bool mProgressiveDisplay; /// If true, duplicate rows to optimize for
- /// progressive display.
- };
- //////////////////////////////////////////////////////////////////////////////
- // RemoveFrameRectFilter
- //////////////////////////////////////////////////////////////////////////////
- template <typename Next> class RemoveFrameRectFilter;
- /**
- * A configuration struct for RemoveFrameRectFilter.
- */
- struct RemoveFrameRectConfig
- {
- template <typename Next> using Filter = RemoveFrameRectFilter<Next>;
- gfx::IntRect mFrameRect; /// The surface subrect which contains data.
- };
- /**
- * RemoveFrameRectFilter turns an image with a frame rect that does not match
- * its logical size into an image with no frame rect. It does this by writing
- * transparent pixels into any padding regions and throwing away excess data.
- *
- * The 'Next' template parameter specifies the next filter in the chain.
- */
- template <typename Next>
- class RemoveFrameRectFilter final : public SurfaceFilter
- {
- public:
- RemoveFrameRectFilter()
- : mRow(0)
- { }
- template <typename... Rest>
- nsresult Configure(const RemoveFrameRectConfig& aConfig, const Rest&... aRest)
- {
- nsresult rv = mNext.Configure(aRest...);
- if (NS_FAILED(rv)) {
- return rv;
- }
- if (mNext.IsValidPalettedPipe()) {
- NS_WARNING("RemoveFrameRectFilter used with paletted pipe?");
- return NS_ERROR_INVALID_ARG;
- }
- mFrameRect = mUnclampedFrameRect = aConfig.mFrameRect;
- gfx::IntSize outputSize = mNext.InputSize();
- // Forbid frame rects with negative size.
- if (aConfig.mFrameRect.width < 0 || aConfig.mFrameRect.height < 0) {
- return NS_ERROR_INVALID_ARG;
- }
- // Clamp mFrameRect to the output size.
- gfx::IntRect outputRect(0, 0, outputSize.width, outputSize.height);
- mFrameRect = mFrameRect.Intersect(outputRect);
- // If there's no intersection, |mFrameRect| will be an empty rect positioned
- // at the maximum of |inputRect|'s and |aFrameRect|'s coordinates, which is
- // not what we want. Force it to (0, 0) in that case.
- if (mFrameRect.IsEmpty()) {
- mFrameRect.MoveTo(0, 0);
- }
- // We don't need an intermediate buffer unless the unclamped frame rect
- // width is larger than the clamped frame rect width. In that case, the
- // caller will end up writing data that won't end up in the final image at
- // all, and we'll need a buffer to give that data a place to go.
- if (mFrameRect.width < mUnclampedFrameRect.width) {
- mBuffer.reset(new (fallible) uint8_t[mUnclampedFrameRect.width *
- sizeof(uint32_t)]);
- if (MOZ_UNLIKELY(!mBuffer)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- memset(mBuffer.get(), 0, mUnclampedFrameRect.width * sizeof(uint32_t));
- }
- ConfigureFilter(mUnclampedFrameRect.Size(), sizeof(uint32_t));
- return NS_OK;
- }
- Maybe<SurfaceInvalidRect> TakeInvalidRect() override
- {
- return mNext.TakeInvalidRect();
- }
- protected:
- uint8_t* DoResetToFirstRow() override
- {
- uint8_t* rowPtr = mNext.ResetToFirstRow();
- if (rowPtr == nullptr) {
- mRow = mFrameRect.YMost();
- return nullptr;
- }
- mRow = mUnclampedFrameRect.y;
- // Advance the next pipeline stage to the beginning of the frame rect,
- // outputting blank rows.
- if (mFrameRect.y > 0) {
- for (int32_t rowToOutput = 0; rowToOutput < mFrameRect.y ; ++rowToOutput) {
- mNext.WriteEmptyRow();
- }
- }
- // We're at the beginning of the frame rect now, so return if we're either
- // ready for input or we're already done.
- rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
- if (!mFrameRect.IsEmpty() || rowPtr == nullptr) {
- // Note that the pointer we're returning is for the next row we're
- // actually going to write to, but we may discard writes before that point
- // if mRow < mFrameRect.y.
- return AdjustRowPointer(rowPtr);
- }
- // We've finished the region specified by the frame rect, but the frame rect
- // is empty, so we need to output the rest of the image immediately. Advance
- // to the end of the next pipeline stage's buffer, outputting blank rows.
- while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { }
- mRow = mFrameRect.YMost();
- return nullptr; // We're done.
- }
- uint8_t* DoAdvanceRow() override
- {
- uint8_t* rowPtr = nullptr;
- const int32_t currentRow = mRow;
- mRow++;
- if (currentRow < mFrameRect.y) {
- // This row is outside of the frame rect, so just drop it on the floor.
- rowPtr = mBuffer ? mBuffer.get() : mNext.CurrentRowPointer();
- return AdjustRowPointer(rowPtr);
- } else if (currentRow >= mFrameRect.YMost()) {
- NS_WARNING("RemoveFrameRectFilter: Advancing past end of frame rect");
- return nullptr;
- }
- // If we had to buffer, copy the data. Otherwise, just advance the row.
- if (mBuffer) {
- // We write from the beginning of the buffer unless |mUnclampedFrameRect.x|
- // is negative; if that's the case, we have to skip the portion of the
- // unclamped frame rect that's outside the row.
- uint32_t* source = reinterpret_cast<uint32_t*>(mBuffer.get()) -
- std::min(mUnclampedFrameRect.x, 0);
- // We write |mFrameRect.width| columns starting at |mFrameRect.x|; we've
- // already clamped these values to the size of the output, so we don't
- // have to worry about bounds checking here (though WriteBuffer() will do
- // it for us in any case).
- WriteState state = mNext.WriteBuffer(source, mFrameRect.x, mFrameRect.width);
- rowPtr = state == WriteState::NEED_MORE_DATA ? mBuffer.get()
- : nullptr;
- } else {
- rowPtr = mNext.AdvanceRow();
- }
- // If there's still more data coming or we're already done, just adjust the
- // pointer and return.
- if (mRow < mFrameRect.YMost() || rowPtr == nullptr) {
- return AdjustRowPointer(rowPtr);
- }
- // We've finished the region specified by the frame rect. Advance to the end
- // of the next pipeline stage's buffer, outputting blank rows.
- while (mNext.WriteEmptyRow() == WriteState::NEED_MORE_DATA) { }
- mRow = mFrameRect.YMost();
- return nullptr; // We're done.
- }
- private:
- uint8_t* AdjustRowPointer(uint8_t* aNextRowPointer) const
- {
- if (mBuffer) {
- MOZ_ASSERT(aNextRowPointer == mBuffer.get() || aNextRowPointer == nullptr);
- return aNextRowPointer; // No adjustment needed for an intermediate buffer.
- }
- if (mFrameRect.IsEmpty() ||
- mRow >= mFrameRect.YMost() ||
- aNextRowPointer == nullptr) {
- return nullptr; // Nothing left to write.
- }
- return aNextRowPointer + mFrameRect.x * sizeof(uint32_t);
- }
- Next mNext; /// The next SurfaceFilter in the chain.
- gfx::IntRect mFrameRect; /// The surface subrect which contains data,
- /// clamped to the image size.
- gfx::IntRect mUnclampedFrameRect; /// The frame rect before clamping.
- UniquePtr<uint8_t[]> mBuffer; /// The intermediate buffer, if one is
- /// necessary because the frame rect width
- /// is larger than the image's logical width.
- int32_t mRow; /// The row in unclamped frame rect space
- /// that we're currently writing.
- };
- //////////////////////////////////////////////////////////////////////////////
- // ADAM7InterpolatingFilter
- //////////////////////////////////////////////////////////////////////////////
- template <typename Next> class ADAM7InterpolatingFilter;
- /**
- * A configuration struct for ADAM7InterpolatingFilter.
- */
- struct ADAM7InterpolatingConfig
- {
- template <typename Next> using Filter = ADAM7InterpolatingFilter<Next>;
- };
- /**
- * ADAM7InterpolatingFilter performs bilinear interpolation over an ADAM7
- * interlaced image.
- *
- * ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new set
- * of pixels in each block receives their final values, according to the
- * following pattern:
- *
- * 1 6 4 6 2 6 4 6
- * 7 7 7 7 7 7 7 7
- * 5 6 5 6 5 6 5 6
- * 7 7 7 7 7 7 7 7
- * 3 6 4 6 3 6 4 6
- * 7 7 7 7 7 7 7 7
- * 5 6 5 6 5 6 5 6
- * 7 7 7 7 7 7 7 7
- *
- * When rendering the pixels that have not yet received their final values, we
- * can get much better intermediate results if we interpolate between
- * the pixels we *have* gotten so far. This filter performs bilinear
- * interpolation by first performing linear interpolation horizontally for each
- * "important" row (which we'll define as a row that has received any pixels
- * with final values at all) and then performing linear interpolation vertically
- * to produce pixel values for rows which aren't important on the current pass.
- *
- * Note that this filter totally ignores the data which is written to rows which
- * aren't important on the current pass! It's fine to write nothing at all for
- * these rows, although doing so won't cause any harm.
- *
- * XXX(seth): In bug 1280552 we'll add a SIMD implementation for this filter.
- *
- * The 'Next' template parameter specifies the next filter in the chain.
- */
- template <typename Next>
- class ADAM7InterpolatingFilter final : public SurfaceFilter
- {
- public:
- ADAM7InterpolatingFilter()
- : mPass(0) // The current pass, in the range 1..7. Starts at 0 so that
- // DoResetToFirstRow() doesn't have to special case the first pass.
- , mRow(0)
- { }
- template <typename... Rest>
- nsresult Configure(const ADAM7InterpolatingConfig& aConfig, const Rest&... aRest)
- {
- nsresult rv = mNext.Configure(aRest...);
- if (NS_FAILED(rv)) {
- return rv;
- }
- if (mNext.IsValidPalettedPipe()) {
- NS_WARNING("ADAM7InterpolatingFilter used with paletted pipe?");
- return NS_ERROR_INVALID_ARG;
- }
- // We have two intermediate buffers, one for the previous row with final
- // pixel values and one for the row that the previous filter in the chain is
- // currently writing to.
- size_t inputWidthInBytes = mNext.InputSize().width * sizeof(uint32_t);
- mPreviousRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
- if (MOZ_UNLIKELY(!mPreviousRow)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- mCurrentRow.reset(new (fallible) uint8_t[inputWidthInBytes]);
- if (MOZ_UNLIKELY(!mCurrentRow)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- memset(mPreviousRow.get(), 0, inputWidthInBytes);
- memset(mCurrentRow.get(), 0, inputWidthInBytes);
- ConfigureFilter(mNext.InputSize(), sizeof(uint32_t));
- return NS_OK;
- }
- Maybe<SurfaceInvalidRect> TakeInvalidRect() override
- {
- return mNext.TakeInvalidRect();
- }
- protected:
- uint8_t* DoResetToFirstRow() override
- {
- mRow = 0;
- mPass = std::min(mPass + 1, 7);
- uint8_t* rowPtr = mNext.ResetToFirstRow();
- if (mPass == 7) {
- // Short circuit this filter on the final pass, since all pixels have
- // their final values at that point.
- return rowPtr;
- }
- return mCurrentRow.get();
- }
- uint8_t* DoAdvanceRow() override
- {
- MOZ_ASSERT(0 < mPass && mPass <= 7, "Invalid pass");
- int32_t currentRow = mRow;
- ++mRow;
- if (mPass == 7) {
- // On the final pass we short circuit this filter totally.
- return mNext.AdvanceRow();
- }
- const int32_t lastImportantRow = LastImportantRow(InputSize().height, mPass);
- if (currentRow > lastImportantRow) {
- return nullptr; // This pass is already complete.
- }
- if (!IsImportantRow(currentRow, mPass)) {
- // We just ignore whatever the caller gives us for these rows. We'll
- // interpolate them in later.
- return mCurrentRow.get();
- }
- // This is an important row. We need to perform horizontal interpolation for
- // these rows.
- InterpolateHorizontally(mCurrentRow.get(), InputSize().width, mPass);
- // Interpolate vertically between the previous important row and the current
- // important row. We skip this if the current row is 0 (which is always an
- // important row), because in that case there is no previous important row
- // to interpolate with.
- if (currentRow != 0) {
- InterpolateVertically(mPreviousRow.get(), mCurrentRow.get(), mPass, mNext);
- }
- // Write out the current row itself, which, being an important row, does not
- // need vertical interpolation.
- uint32_t* currentRowAsPixels = reinterpret_cast<uint32_t*>(mCurrentRow.get());
- mNext.WriteBuffer(currentRowAsPixels);
- if (currentRow == lastImportantRow) {
- // This is the last important row, which completes this pass. Note that
- // for very small images, this may be the first row! Since there won't be
- // another important row, there's nothing to interpolate with vertically,
- // so we just duplicate this row until the end of the image.
- while (mNext.WriteBuffer(currentRowAsPixels) == WriteState::NEED_MORE_DATA) { }
- // All of the remaining rows in the image were determined above, so we're done.
- return nullptr;
- }
- // The current row is now the previous important row; save it.
- Swap(mPreviousRow, mCurrentRow);
- MOZ_ASSERT(mRow < InputSize().height, "Reached the end of the surface without "
- "hitting the last important row?");
- return mCurrentRow.get();
- }
- private:
- static void InterpolateVertically(uint8_t* aPreviousRow,
- uint8_t* aCurrentRow,
- uint8_t aPass,
- SurfaceFilter& aNext)
- {
- const float* weights = InterpolationWeights(ImportantRowStride(aPass));
- // We need to interpolate vertically to generate the rows between the
- // previous important row and the next one. Recall that important rows are
- // rows which contain at least some final pixels; see
- // InterpolateHorizontally() for some additional explanation as to what that
- // means. Note that we've already written out the previous important row, so
- // we start the iteration at 1.
- for (int32_t outRow = 1; outRow < ImportantRowStride(aPass); ++outRow) {
- const float weight = weights[outRow];
- // We iterate through the previous and current important row every time we
- // write out an interpolated row, so we need to copy the pointers.
- uint8_t* prevRowBytes = aPreviousRow;
- uint8_t* currRowBytes = aCurrentRow;
- // Write out the interpolated pixels. Interpolation is componentwise.
- aNext.template WritePixelsToRow<uint32_t>([&]{
- uint32_t pixel = 0;
- auto* component = reinterpret_cast<uint8_t*>(&pixel);
- *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
- *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
- *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
- *component++ = InterpolateByte(*prevRowBytes++, *currRowBytes++, weight);
- return AsVariant(pixel);
- });
- }
- }
- static void InterpolateHorizontally(uint8_t* aRow, int32_t aWidth, uint8_t aPass)
- {
- // Collect the data we'll need to perform horizontal interpolation. The
- // terminology here bears some explanation: a "final pixel" is a pixel which
- // has received its final value. On each pass, a new set of pixels receives
- // their final value; see the diagram above of the 8x8 pattern that ADAM7
- // uses. Any pixel which hasn't received its final value on this pass
- // derives its value from either horizontal or vertical interpolation
- // instead.
- const size_t finalPixelStride = FinalPixelStride(aPass);
- const size_t finalPixelStrideBytes = finalPixelStride * sizeof(uint32_t);
- const size_t lastFinalPixel = LastFinalPixel(aWidth, aPass);
- const size_t lastFinalPixelBytes = lastFinalPixel * sizeof(uint32_t);
- const float* weights = InterpolationWeights(finalPixelStride);
- // Interpolate blocks of pixels which lie between two final pixels.
- // Horizontal interpolation is done in place, as we'll need the results
- // later when we vertically interpolate.
- for (size_t blockBytes = 0;
- blockBytes < lastFinalPixelBytes;
- blockBytes += finalPixelStrideBytes) {
- uint8_t* finalPixelA = aRow + blockBytes;
- uint8_t* finalPixelB = aRow + blockBytes + finalPixelStrideBytes;
- MOZ_ASSERT(finalPixelA < aRow + aWidth * sizeof(uint32_t),
- "Running off end of buffer");
- MOZ_ASSERT(finalPixelB < aRow + aWidth * sizeof(uint32_t),
- "Running off end of buffer");
- // Interpolate the individual pixels componentwise. Note that we start
- // iteration at 1 since we don't need to apply any interpolation to the
- // first pixel in the block, which has its final value.
- for (size_t pixelIndex = 1; pixelIndex < finalPixelStride; ++pixelIndex) {
- const float weight = weights[pixelIndex];
- uint8_t* pixel = aRow + blockBytes + pixelIndex * sizeof(uint32_t);
- MOZ_ASSERT(pixel < aRow + aWidth * sizeof(uint32_t), "Running off end of buffer");
- for (size_t component = 0; component < sizeof(uint32_t); ++component) {
- pixel[component] =
- InterpolateByte(finalPixelA[component], finalPixelB[component], weight);
- }
- }
- }
- // For the pixels after the last final pixel in the row, there isn't a
- // second final pixel to interpolate with, so just duplicate.
- uint32_t* rowPixels = reinterpret_cast<uint32_t*>(aRow);
- uint32_t pixelToDuplicate = rowPixels[lastFinalPixel];
- for (int32_t pixelIndex = lastFinalPixel + 1;
- pixelIndex < aWidth;
- ++pixelIndex) {
- MOZ_ASSERT(pixelIndex < aWidth, "Running off end of buffer");
- rowPixels[pixelIndex] = pixelToDuplicate;
- }
- }
- static uint8_t InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight)
- {
- return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight));
- }
- static int32_t ImportantRowStride(uint8_t aPass)
- {
- MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass");
- // The stride between important rows for each pass, with a dummy value for
- // the nonexistent pass 0.
- static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1 };
- return strides[aPass];
- }
- static bool IsImportantRow(int32_t aRow, uint8_t aPass)
- {
- MOZ_ASSERT(aRow >= 0);
- // Whether the row is important comes down to divisibility by the stride for
- // this pass, which is always a power of 2, so we can check using a mask.
- int32_t mask = ImportantRowStride(aPass) - 1;
- return (aRow & mask) == 0;
- }
- static int32_t LastImportantRow(int32_t aHeight, uint8_t aPass)
- {
- MOZ_ASSERT(aHeight > 0);
- // We can find the last important row using the same mask trick as above.
- int32_t lastRow = aHeight - 1;
- int32_t mask = ImportantRowStride(aPass) - 1;
- return lastRow - (lastRow & mask);
- }
- static size_t FinalPixelStride(uint8_t aPass)
- {
- MOZ_ASSERT(0 < aPass && aPass <= 7, "Invalid pass");
- // The stride between the final pixels in important rows for each pass, with
- // a dummy value for the nonexistent pass 0.
- static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1 };
- return strides[aPass];
- }
- static size_t LastFinalPixel(int32_t aWidth, uint8_t aPass)
- {
- MOZ_ASSERT(aWidth >= 0);
- // Again, we can use the mask trick above to find the last important pixel.
- int32_t lastColumn = aWidth - 1;
- size_t mask = FinalPixelStride(aPass) - 1;
- return lastColumn - (lastColumn & mask);
- }
- static const float* InterpolationWeights(int32_t aStride)
- {
- // Precalculated interpolation weights. These are used to interpolate
- // between final pixels or between important rows. Although no interpolation
- // is actually applied to the previous final pixel or important row value,
- // the arrays still start with 1.0f, which is always skipped, primarily
- // because otherwise |stride1Weights| would have zero elements.
- static float stride8Weights[] =
- { 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f };
- static float stride4Weights[] = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f };
- static float stride2Weights[] = { 1.0f, 1 / 2.0f };
- static float stride1Weights[] = { 1.0f };
- switch (aStride) {
- case 8: return stride8Weights;
- case 4: return stride4Weights;
- case 2: return stride2Weights;
- case 1: return stride1Weights;
- default: MOZ_CRASH();
- }
- }
- Next mNext; /// The next SurfaceFilter in the chain.
- UniquePtr<uint8_t[]> mPreviousRow; /// The last important row (i.e., row with
- /// final pixel values) that got written to.
- UniquePtr<uint8_t[]> mCurrentRow; /// The row that's being written to right
- /// now.
- uint8_t mPass; /// Which ADAM7 pass we're on. Valid passes
- /// are 1..7 during processing and 0 prior
- /// to configuraiton.
- int32_t mRow; /// The row we're currently reading.
- };
- } // namespace image
- } // namespace mozilla
- #endif // mozilla_image_SurfaceFilters_h
|