diff --git a/image/decoders/moz.build b/image/decoders/moz.build index 126bf670d860..73eecdaf035e 100644 --- a/image/decoders/moz.build +++ b/image/decoders/moz.build @@ -32,9 +32,15 @@ UNIFIED_SOURCES += [ 'nsPNGDecoder.cpp', ] -# Decoders need RasterImage.h +include('/ipc/chromium/chromium-config.mozbuild') + LOCAL_INCLUDES += [ + # Access to Skia headers for Downscaler. + '/gfx/2d', + # Decoders need ImageLib headers. '/image', ] +LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES'] + FINAL_LIBRARY = 'xul' diff --git a/image/test/gtest/Common.cpp b/image/test/gtest/Common.cpp index 736b59c4b39b..1a5e873b01bd 100644 --- a/image/test/gtest/Common.cpp +++ b/image/test/gtest/Common.cpp @@ -6,7 +6,6 @@ #include "Common.h" #include -#include "gtest/gtest.h" #include "nsDirectoryServiceDefs.h" #include "nsIDirectoryService.h" @@ -19,13 +18,14 @@ #include "nsString.h" namespace mozilla { +namespace image { using namespace gfx; using std::abs; /////////////////////////////////////////////////////////////////////////////// -// Helpers +// General Helpers /////////////////////////////////////////////////////////////////////////////// // These macros work like gtest's ASSERT_* macros, except that they can be used @@ -85,13 +85,41 @@ LoadFile(const char* aRelativePath) } bool -IsSolidColor(SourceSurface* aSurface, BGRAColor aColor, bool aFuzzy) +IsSolidColor(SourceSurface* aSurface, + BGRAColor aColor, + uint8_t aFuzz /* = 0 */) { + IntSize size = aSurface->GetSize(); + return RectIsSolidColor(aSurface, IntRect(0, 0, size.width, size.height), + aColor, aFuzz); +} + +bool +RowsAreSolidColor(SourceSurface* aSurface, + int32_t aStartRow, + int32_t aRowCount, + BGRAColor aColor, + uint8_t aFuzz /* = 0 */) +{ + IntSize size = aSurface->GetSize(); + return RectIsSolidColor(aSurface, IntRect(0, aStartRow, size.width, aRowCount), + aColor, aFuzz); +} + +bool +RectIsSolidColor(SourceSurface* aSurface, + const IntRect& aRect, + BGRAColor aColor, + uint8_t aFuzz /* = 0 */) +{ + IntSize surfaceSize = aSurface->GetSize(); + IntRect rect = + aRect.Intersect(IntRect(0, 0, surfaceSize.width, surfaceSize.height)); + RefPtr dataSurface = aSurface->GetDataSurface(); ASSERT_TRUE_OR_RETURN(dataSurface != nullptr, false); - ASSERT_EQ_OR_RETURN(dataSurface->Stride(), aSurface->GetSize().width * 4, - false); + ASSERT_EQ_OR_RETURN(dataSurface->Stride(), surfaceSize.width * 4, false); DataSourceSurface::ScopedMap mapping(dataSurface, DataSourceSurface::MapType::READ); @@ -100,18 +128,21 @@ IsSolidColor(SourceSurface* aSurface, BGRAColor aColor, bool aFuzzy) uint8_t* data = dataSurface->GetData(); ASSERT_TRUE_OR_RETURN(data != nullptr, false); - int32_t length = dataSurface->Stride() * aSurface->GetSize().height; - for (int32_t i = 0 ; i < length ; i += 4) { - if (aFuzzy) { - ASSERT_LE_OR_RETURN(abs(aColor.mBlue - data[i + 0]), 1, false); - ASSERT_LE_OR_RETURN(abs(aColor.mGreen - data[i + 1]), 1, false); - ASSERT_LE_OR_RETURN(abs(aColor.mRed - data[i + 2]), 1, false); - ASSERT_LE_OR_RETURN(abs(aColor.mAlpha - data[i + 3]), 1, false); - } else { - ASSERT_EQ_OR_RETURN(aColor.mBlue, data[i + 0], false); - ASSERT_EQ_OR_RETURN(aColor.mGreen, data[i + 1], false); - ASSERT_EQ_OR_RETURN(aColor.mRed, data[i + 2], false); - ASSERT_EQ_OR_RETURN(aColor.mAlpha, data[i + 3], false); + int32_t rowLength = dataSurface->Stride(); + for (int32_t row = rect.y; row < rect.YMost(); ++row) { + for (int32_t col = rect.x; col < rect.XMost(); ++col) { + int32_t i = row * rowLength + col * 4; + if (aFuzz != 0) { + ASSERT_LE_OR_RETURN(abs(aColor.mBlue - data[i + 0]), aFuzz, false); + ASSERT_LE_OR_RETURN(abs(aColor.mGreen - data[i + 1]), aFuzz, false); + ASSERT_LE_OR_RETURN(abs(aColor.mRed - data[i + 2]), aFuzz, false); + ASSERT_LE_OR_RETURN(abs(aColor.mAlpha - data[i + 3]), aFuzz, false); + } else { + ASSERT_EQ_OR_RETURN(aColor.mBlue, data[i + 0], false); + ASSERT_EQ_OR_RETURN(aColor.mGreen, data[i + 1], false); + ASSERT_EQ_OR_RETURN(aColor.mRed, data[i + 2], false); + ASSERT_EQ_OR_RETURN(aColor.mAlpha, data[i + 3], false); + } } } @@ -119,6 +150,261 @@ IsSolidColor(SourceSurface* aSurface, BGRAColor aColor, bool aFuzzy) } +/////////////////////////////////////////////////////////////////////////////// +// SurfacePipe Helpers +/////////////////////////////////////////////////////////////////////////////// + +already_AddRefed +CreateTrivialDecoder() +{ + gfxPrefs::GetSingleton(); + DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif"); + RefPtr sourceBuffer = new SourceBuffer(); + RefPtr decoder = + DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, + DefaultSurfaceFlags()); + return decoder.forget(); +} + +void AssertCorrectPipelineFinalState(SurfaceFilter* aFilter, + const gfx::IntRect& aInputSpaceRect, + const gfx::IntRect& aOutputSpaceRect) +{ + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(aInputSpaceRect, invalidRect->mInputSpaceRect); + EXPECT_EQ(aOutputSpaceRect, invalidRect->mOutputSpaceRect); +} + +void +CheckGeneratedImage(Decoder* aDecoder, + const IntRect& aRect, + uint8_t aFuzz /* = 0 */) +{ + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + const IntSize surfaceSize = surface->GetSize(); + + // This diagram shows how the surface is divided into regions that the code + // below tests for the correct content. The output rect is the bounds of the + // region labeled 'C'. + // + // +---------------------------+ + // | A | + // +---------+--------+--------+ + // | B | C | D | + // +---------+--------+--------+ + // | E | + // +---------------------------+ + + // Check that the output rect itself is green. (Region 'C'.) + EXPECT_TRUE(RectIsSolidColor(surface, aRect, BGRAColor::Green(), aFuzz)); + + // Check that the area above the output rect is transparent. (Region 'A'.) + EXPECT_TRUE(RectIsSolidColor(surface, + IntRect(0, 0, surfaceSize.width, aRect.y), + BGRAColor::Transparent(), aFuzz)); + + // Check that the area to the left of the output rect is transparent. (Region 'B'.) + EXPECT_TRUE(RectIsSolidColor(surface, + IntRect(0, aRect.y, aRect.x, aRect.YMost()), + BGRAColor::Transparent(), aFuzz)); + + // Check that the area to the right of the output rect is transparent. (Region 'D'.) + const int32_t widthOnRight = surfaceSize.width - aRect.XMost(); + EXPECT_TRUE(RectIsSolidColor(surface, + IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()), + BGRAColor::Transparent(), aFuzz)); + + // Check that the area below the output rect is transparent. (Region 'E'.) + const int32_t heightBelow = surfaceSize.height - aRect.YMost(); + EXPECT_TRUE(RectIsSolidColor(surface, + IntRect(0, aRect.YMost(), surfaceSize.width, heightBelow), + BGRAColor::Transparent(), aFuzz)); +} + +template void +CheckSurfacePipeWrite(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect, + Maybe aInputRect, + Maybe aInputWriteRect, + Maybe aOutputWriteRect, + uint8_t aFuzz, + Func aFunc) +{ + IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100)); + IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100)); + IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect); + IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect); + + // Fill the image. + int32_t count = 0; + auto result = aFunc(count); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(inputWriteRect.width * inputWriteRect.height, count); + + AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect); + + // Attempt to write more data and make sure nothing changes. + const int32_t oldCount = count; + result = aFunc(count); + EXPECT_EQ(oldCount, count); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Attempt to advance to the next row and make sure nothing changes. + aFilter->AdvanceRow(); + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is correct. + CheckGeneratedImage(aDecoder, outputWriteRect, aFuzz); +} + +void +CheckWritePixels(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect /* = Nothing() */, + Maybe aInputRect /* = Nothing() */, + Maybe aInputWriteRect /* = Nothing() */, + Maybe aOutputWriteRect /* = Nothing() */, + uint8_t aFuzz /* = 0 */) +{ + CheckSurfacePipeWrite(aDecoder, aFilter, + aOutputRect, aInputRect, + aInputWriteRect, aOutputWriteRect, + aFuzz, + [&](int32_t& aCount) { + return aFilter->WritePixels([&] { + ++aCount; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + }); +} + +void +CheckWriteRows(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect /* = Nothing() */, + Maybe aInputRect /* = Nothing() */, + Maybe aInputWriteRect /* = Nothing() */, + Maybe aOutputWriteRect /* = Nothing() */, + uint8_t aFuzz /* = 0 */) +{ + CheckSurfacePipeWrite(aDecoder, aFilter, + aOutputRect, aInputRect, + aInputWriteRect, aOutputWriteRect, + aFuzz, + [&](int32_t& aCount) { + return aFilter->WriteRows([&](uint32_t* aRow, uint32_t aLength) { + for (; aLength > 0; --aLength, ++aRow, ++aCount) { + *aRow = BGRAColor::Green().AsPixel(); + } + return Nothing(); + }); + }); +} + +template void +CheckPalettedSurfacePipeWrite(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect, + Maybe aInputRect, + Maybe aInputWriteRect, + Maybe aOutputWriteRect, + uint8_t aFuzz, + Func aFunc) +{ + IntRect outputRect = aOutputRect.valueOr(IntRect(0, 0, 100, 100)); + IntRect inputRect = aInputRect.valueOr(IntRect(0, 0, 100, 100)); + IntRect inputWriteRect = aInputWriteRect.valueOr(inputRect); + IntRect outputWriteRect = aOutputWriteRect.valueOr(outputRect); + + // Fill the image. + int32_t count = 0; + auto result = aFunc(count); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(inputWriteRect.width * inputWriteRect.height, count); + + AssertCorrectPipelineFinalState(aFilter, inputRect, outputRect); + + // Attempt to write more data and make sure nothing changes. + const int32_t oldCount = count; + result = aFunc(count); + EXPECT_EQ(oldCount, count); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Attempt to advance to the next row and make sure nothing changes. + aFilter->AdvanceRow(); + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + uint8_t* imageData; + uint32_t imageLength; + currentFrame->GetImageData(&imageData, &imageLength); + ASSERT_TRUE(imageData != nullptr); + ASSERT_EQ(outputWriteRect.width * outputWriteRect.height, int32_t(imageLength)); + for (uint32_t i = 0; i < imageLength; ++i) { + ASSERT_EQ(uint8_t(255), imageData[i]); + } +} + +void +CheckPalettedWritePixels(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect /* = Nothing() */, + Maybe aInputRect /* = Nothing() */, + Maybe aInputWriteRect /* = Nothing() */, + Maybe aOutputWriteRect /* = Nothing() */, + uint8_t aFuzz /* = 0 */) +{ + CheckPalettedSurfacePipeWrite(aDecoder, aFilter, + aOutputRect, aInputRect, + aInputWriteRect, aOutputWriteRect, + aFuzz, + [&](int32_t& aCount) { + return aFilter->WritePixels([&] { + ++aCount; + return AsVariant(uint8_t(255)); + }); + }); +} + +void +CheckPalettedWriteRows(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect /* = Nothing() */, + Maybe aInputRect /* = Nothing() */, + Maybe aInputWriteRect /* = Nothing() */, + Maybe aOutputWriteRect /* = Nothing() */, + uint8_t aFuzz /* = 0*/) +{ + CheckPalettedSurfacePipeWrite(aDecoder, aFilter, + aOutputRect, aInputRect, + aInputWriteRect, aOutputWriteRect, + aFuzz, + [&](int32_t& aCount) { + return aFilter->WriteRows([&](uint8_t* aRow, uint32_t aLength) { + for (; aLength > 0; --aLength, ++aRow, ++aCount) { + *aRow = uint8_t(255); + } + return Nothing(); + }); + }); +} + + /////////////////////////////////////////////////////////////////////////////// // Test Data /////////////////////////////////////////////////////////////////////////////// @@ -224,4 +510,5 @@ ImageTestCase NoFrameDelayGIFTestCase() return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100)); } +} // namespace image } // namespace mozilla diff --git a/image/test/gtest/Common.h b/image/test/gtest/Common.h index 80daeb038e42..46637485c5bb 100644 --- a/image/test/gtest/Common.h +++ b/image/test/gtest/Common.h @@ -6,12 +6,21 @@ #ifndef mozilla_image_test_gtest_Common_h #define mozilla_image_test_gtest_Common_h +#include "gtest/gtest.h" + +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" #include "mozilla/gfx/2D.h" +#include "Decoder.h" +#include "gfxColor.h" #include "nsCOMPtr.h" +#include "SurfacePipe.h" +#include "SurfacePipeFactory.h" class nsIInputStream; namespace mozilla { +namespace image { /////////////////////////////////////////////////////////////////////////////// // Types @@ -54,6 +63,10 @@ struct BGRAColor { } static BGRAColor Green() { return BGRAColor(0x00, 0xFF, 0x00, 0xFF); } + static BGRAColor Red() { return BGRAColor(0x00, 0x00, 0xFF, 0xFF); } + static BGRAColor Transparent() { return BGRAColor(0x00, 0x00, 0x00, 0x00); } + + uint32_t AsPixel() const { return gfxPackedPixel(mAlpha, mRed, mGreen, mBlue); } uint8_t mBlue; uint8_t mGreen; @@ -63,7 +76,7 @@ struct BGRAColor /////////////////////////////////////////////////////////////////////////////// -// Helpers +// General Helpers /////////////////////////////////////////////////////////////////////////////// /// Loads a file from the current directory. @return an nsIInputStream for it. @@ -72,12 +85,206 @@ already_AddRefed LoadFile(const char* aRelativePath); /** * @returns true if every pixel of @aSurface is @aColor. * - * If @aFuzzy is true, a tolerance of 1 is allowed in each color component. This - * may be necessary for tests that involve JPEG images. + * If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color + * component. This may be necessary for tests that involve JPEG images or + * downscaling. */ bool IsSolidColor(gfx::SourceSurface* aSurface, BGRAColor aColor, - bool aFuzzy = false); + uint8_t aFuzz = 0); + +/** + * @returns true if every pixel in the range of rows specified by @aStartRow and + * @aRowCount of @aSurface is @aColor. + * + * If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color + * component. This may be necessary for tests that involve JPEG images or + * downscaling. + */ +bool RowsAreSolidColor(gfx::SourceSurface* aSurface, + int32_t aStartRow, + int32_t aRowCount, + BGRAColor aColor, + uint8_t aFuzz = 0); + +/** + * @returns true if every pixel in the rect specified by @aRect is @aColor. + * + * If @aFuzz is nonzero, a tolerance of @aFuzz is allowed in each color + * component. This may be necessary for tests that involve JPEG images or + * downscaling. + */ +bool RectIsSolidColor(gfx::SourceSurface* aSurface, + const gfx::IntRect& aRect, + BGRAColor aColor, + uint8_t aFuzz = 0); + + +/////////////////////////////////////////////////////////////////////////////// +// SurfacePipe Helpers +/////////////////////////////////////////////////////////////////////////////// + +/** + * Creates a decoder with no data associated with, suitable for testing code + * that requires a decoder to initialize or to allocate surfaces but doesn't + * actually need the decoder to do any decoding. + * + * XXX(seth): We only need this because SurfaceSink and PalettedSurfaceSink + * defer to the decoder for surface allocation. Once all decoders use + * SurfacePipe we won't need to do that anymore and we can remove this function. + */ +already_AddRefed CreateTrivialDecoder(); + +/** + * Creates a pipeline of SurfaceFilters from a list of Config structs and passes + * it to the provided lambda @aFunc. Assertions that the pipeline is constructly + * correctly and cleanup of any allocated surfaces is handled automatically. + * + * @param aDecoder The decoder to use for allocating surfaces. + * @param aFunc The lambda function to pass the filter pipeline to. + * @param aConfigs The configuration for the pipeline. + */ +template +void WithFilterPipeline(Decoder* aDecoder, Func aFunc, Configs... aConfigs) +{ + auto pipe = MakeUnique::Type>(); + nsresult rv = pipe->Configure(aConfigs...); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + aFunc(aDecoder, pipe.get()); + + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + if (currentFrame) { + currentFrame->Finish(); + } +} + +/** + * Creates a pipeline of SurfaceFilters from a list of Config structs and + * asserts that configuring it fails. Cleanup of any allocated surfaces is + * handled automatically. + * + * @param aDecoder The decoder to use for allocating surfaces. + * @param aConfigs The configuration for the pipeline. + */ +template +void AssertConfiguringPipelineFails(Decoder* aDecoder, Configs... aConfigs) +{ + auto pipe = MakeUnique::Type>(); + nsresult rv = pipe->Configure(aConfigs...); + + // Callers expect configuring the pipeline to fail. + ASSERT_TRUE(NS_FAILED(rv)); + + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + if (currentFrame) { + currentFrame->Finish(); + } +} + +/** + * Asserts that the provided filter pipeline is in the correct final state, + * which is to say, the entire surface has been written to (IsSurfaceFinished() + * returns true) and the invalid rects are as expected. + * + * @param aFilter The filter pipeline to check. + * @param aInputSpaceRect The expect invalid rect, in input space. + * @param aoutputSpaceRect The expect invalid rect, in output space. + */ +void AssertCorrectPipelineFinalState(SurfaceFilter* aFilter, + const gfx::IntRect& aInputSpaceRect, + const gfx::IntRect& aOutputSpaceRect); + +/** + * Checks a generated image for correctness. Reports any unexpected deviation + * from the expected image as GTest failures. + * + * @param aDecoder The decoder which contains the image. The decoder's current + * frame will be checked. + * @param aRect The region in the space of the output surface that the filter + * pipeline will actually write to. It's expected that pixels in + * this region are green, while pixels outside this region are + * transparent. Defaults to the entire output rect. + * @param aFuzz The amount of fuzz to use in pixel comparisons. + */ +void CheckGeneratedImage(Decoder* aDecoder, + const gfx::IntRect& aRect, + uint8_t aFuzz = 0); + +/** + * Tests the result of calling WritePixels() using the provided SurfaceFilter + * pipeline. The pipeline must be a normal (i.e., non-paletted) pipeline. + * + * The arguments are specified in the an order intended to minimize the number + * of arguments that most test cases need to pass. + * + * @param aDecoder The decoder whose current frame will be written to. + * @param aFilter The SurfaceFilter pipeline to use. + * @param aOutputRect The region in the space of the output surface that will be + * invalidated by the filter pipeline. Defaults to + * (0, 0, 100, 100). + * @param aInputRect The region in the space of the input image that will be + * invalidated by the filter pipeline. Defaults to + * (0, 0, 100, 100). + * @param aInputWriteRect The region in the space of the input image that the + * filter pipeline will allow writes to. Note the + * difference from @aInputRect: @aInputRect is the actual + * region invalidated, while @aInputWriteRect is the + * region that is written to. These can differ in cases + * where the input is not clipped to the size of the image. + * Defaults to the entire input rect. + * @param aOutputWriteRect The region in the space of the output surface that + * the filter pipeline will actually write to. It's + * expected that pixels in this region are green, while + * pixels outside this region are transparent. Defaults + * to the entire output rect. + */ +void CheckWritePixels(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect = Nothing(), + Maybe aInputRect = Nothing(), + Maybe aInputWriteRect = Nothing(), + Maybe aOutputWriteRect = Nothing(), + uint8_t aFuzz = 0); + +/** + * Tests the result of calling WriteRows() using the provided SurfaceFilter + * pipeline. The pipeline must be a normal (i.e., non-paletted) pipeline. + * @see CheckWritePixels() for documentation of the arguments. + */ +void CheckWriteRows(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect = Nothing(), + Maybe aInputRect = Nothing(), + Maybe aInputWriteRect = Nothing(), + Maybe aOutputWriteRect = Nothing(), + uint8_t aFuzz = 0); + +/** + * Tests the result of calling WritePixels() using the provided SurfaceFilter + * pipeline. The pipeline must be a paletted pipeline. + * @see CheckWritePixels() for documentation of the arguments. + */ +void CheckPalettedWritePixels(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect = Nothing(), + Maybe aInputRect = Nothing(), + Maybe aInputWriteRect = Nothing(), + Maybe aOutputWriteRect = Nothing(), + uint8_t aFuzz = 0); + +/** + * Tests the result of calling WriteRows() using the provided SurfaceFilter + * pipeline. The pipeline must be a paletted pipeline. + * @see CheckWritePixels() for documentation of the arguments. + */ +void CheckPalettedWriteRows(Decoder* aDecoder, + SurfaceFilter* aFilter, + Maybe aOutputRect = Nothing(), + Maybe aInputRect = Nothing(), + Maybe aInputWriteRect = Nothing(), + Maybe aOutputWriteRect = Nothing(), + uint8_t aFuzz = 0); /////////////////////////////////////////////////////////////////////////////// @@ -105,6 +312,7 @@ ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase(); ImageTestCase RLE4BMPTestCase(); ImageTestCase RLE8BMPTestCase(); +} // namespace image } // namespace mozilla #endif // mozilla_image_test_gtest_Common_h diff --git a/image/test/gtest/TestDecodeToSurface.cpp b/image/test/gtest/TestDecodeToSurface.cpp index 0179d981e3da..70ce27e07f0b 100644 --- a/image/test/gtest/TestDecodeToSurface.cpp +++ b/image/test/gtest/TestDecodeToSurface.cpp @@ -63,7 +63,7 @@ public: EXPECT_EQ(mTestCase.mSize, mSurface->GetSize()); EXPECT_TRUE(IsSolidColor(mSurface, BGRAColor::Green(), - mTestCase.mFlags & TEST_CASE_IS_FUZZY)); + mTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0)); } private: diff --git a/image/test/gtest/TestDecoders.cpp b/image/test/gtest/TestDecoders.cpp index e12c7607826a..039cacb81999 100644 --- a/image/test/gtest/TestDecoders.cpp +++ b/image/test/gtest/TestDecoders.cpp @@ -79,7 +79,7 @@ CheckDecoderResults(const ImageTestCase& aTestCase, Decoder* aDecoder) surface->GetFormat() == SurfaceFormat::B8G8R8A8); EXPECT_EQ(aTestCase.mSize, surface->GetSize()); EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(), - aTestCase.mFlags & TEST_CASE_IS_FUZZY)); + aTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0)); } static void diff --git a/image/test/gtest/TestDeinterlacingFilter.cpp b/image/test/gtest/TestDeinterlacingFilter.cpp new file mode 100644 index 000000000000..cc256ce781fb --- /dev/null +++ b/image/test/gtest/TestDeinterlacingFilter.cpp @@ -0,0 +1,636 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfaceFilters.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +template void +WithDeinterlacingFilter(const IntSize& aSize, + bool aProgressiveDisplay, + Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(bool(decoder)); + + WithFilterPipeline(decoder, Forward(aFunc), + DeinterlacingConfig { aProgressiveDisplay }, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +template void +WithPalettedDeinterlacingFilter(const IntSize& aSize, + Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + WithFilterPipeline(decoder, Forward(aFunc), + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + PalettedSurfaceConfig { decoder, 0, aSize, + IntRect(0, 0, 100, 100), + SurfaceFormat::B8G8R8A8, 8, + false }); +} + +void +AssertConfiguringDeinterlacingFilterFails(const IntSize& aSize) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + AssertConfiguringPipelineFails(decoder, + DeinterlacingConfig { /* mProgressiveDisplay = */ true}, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageDeinterlacingFilter, WritePixels100_100) +{ + WithDeinterlacingFilter(IntSize(100, 100), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100))); + }); +} + +TEST(ImageDeinterlacingFilter, WriteRows100_100) +{ + WithDeinterlacingFilter(IntSize(100, 100), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100))); + }); +} + +TEST(ImageDeinterlacingFilter, WritePixels99_99) +{ + WithDeinterlacingFilter(IntSize(99, 99), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 99)), + /* aInputRect = */ Some(IntRect(0, 0, 99, 99))); + }); +} + +TEST(ImageDeinterlacingFilter, WriteRows99_99) +{ + WithDeinterlacingFilter(IntSize(99, 99), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 99)), + /* aInputRect = */ Some(IntRect(0, 0, 99, 99))); + }); +} + +TEST(ImageDeinterlacingFilter, WritePixels8_8) +{ + WithDeinterlacingFilter(IntSize(8, 8), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 8, 8)), + /* aInputRect = */ Some(IntRect(0, 0, 8, 8))); + }); +} + +TEST(ImageDeinterlacingFilter, WriteRows8_8) +{ + WithDeinterlacingFilter(IntSize(8, 8), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 8, 8)), + /* aInputRect = */ Some(IntRect(0, 0, 8, 8))); + }); +} + +TEST(ImageDeinterlacingFilter, WritePixels7_7) +{ + WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 7, 7)), + /* aInputRect = */ Some(IntRect(0, 0, 7, 7))); + }); +} + +TEST(ImageDeinterlacingFilter, WriteRows7_7) +{ + WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 7, 7)), + /* aInputRect = */ Some(IntRect(0, 0, 7, 7))); + }); +} + +TEST(ImageDeinterlacingFilter, WritePixels3_3) +{ + WithDeinterlacingFilter(IntSize(3, 3), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 3, 3)), + /* aInputRect = */ Some(IntRect(0, 0, 3, 3))); + }); +} + +TEST(ImageDeinterlacingFilter, WriteRows3_3) +{ + WithDeinterlacingFilter(IntSize(3, 3), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 3, 3)), + /* aInputRect = */ Some(IntRect(0, 0, 3, 3))); + }); +} + +TEST(ImageDeinterlacingFilter, WritePixels1_1) +{ + WithDeinterlacingFilter(IntSize(1, 1), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 1, 1)), + /* aInputRect = */ Some(IntRect(0, 0, 1, 1))); + }); +} + +TEST(ImageDeinterlacingFilter, WriteRows1_1) +{ + WithDeinterlacingFilter(IntSize(1, 1), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 1, 1)), + /* aInputRect = */ Some(IntRect(0, 0, 1, 1))); + }); +} + +TEST(ImageDeinterlacingFilter, PalettedWritePixels) +{ + WithPalettedDeinterlacingFilter(IntSize(100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckPalettedWritePixels(aDecoder, aFilter); + }); +} + +TEST(ImageDeinterlacingFilter, PalettedWriteRows) +{ + WithPalettedDeinterlacingFilter(IntSize(100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckPalettedWriteRows(aDecoder, aFilter); + }); +} + +TEST(ImageDeinterlacingFilter, WritePixelsOutput20_20) +{ + WithDeinterlacingFilter(IntSize(20, 20), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be green for even rows and red for odd + // rows but we need to write the rows in the order that the deinterlacer + // expects them. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() { + uint32_t row = count / 20; // Integer division. + ++count; + + switch (row) { + // First pass. Output rows are positioned at 8n + 0. + case 0: // Output row 0. + case 1: // Output row 8. + case 2: // Output row 16. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Second pass. Rows are positioned at 8n + 4. + case 3: // Output row 4. + case 4: // Output row 12. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Third pass. Rows are positioned at 4n + 2. + case 5: // Output row 2. + case 6: // Output row 6. + case 7: // Output row 10. + case 8: // Output row 14. + case 9: // Output row 18. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Fourth pass. Rows are positioned at 2n + 1. + case 10: // Output row 1. + case 11: // Output row 3. + case 12: // Output row 5. + case 13: // Output row 7. + case 14: // Output row 9. + case 15: // Output row 11. + case 16: // Output row 13. + case 17: // Output row 15. + case 18: // Output row 17. + case 19: // Output row 19. + return AsVariant(BGRAColor::Red().AsPixel()); + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected row"); + return AsVariant(BGRAColor::Transparent().AsPixel()); + } + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(20u * 20u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 20, 20), + IntRect(0, 0, 20, 20)); + + // Check that the generated image is correct. As mentioned above, we expect + // even rows to be green and odd rows to be red. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + + for (uint32_t row = 0; row < 20; ++row) { + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, + row % 2 == 0 ? BGRAColor::Green() + : BGRAColor::Red())); + } + }); +} + +TEST(ImageDeinterlacingFilter, WriteRowsOutput7_7) +{ + WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be a repeating pattern of two green + // rows followed by two red rows but we need to write the rows in the order + // that the deinterlacer expects them. + uint32_t count = 0; + uint32_t row = 0; + auto result = aFilter->WriteRows([&](uint32_t* aRow, uint32_t aLength) { + uint32_t color = 0; + switch (row) { + // First pass. Output rows are positioned at 8n + 0. + case 0: // Output row 0. + color = BGRAColor::Green().AsPixel(); + break; + + // Second pass. Rows are positioned at 8n + 4. + case 1: // Output row 4. + color = BGRAColor::Green().AsPixel(); + break; + + // Third pass. Rows are positioned at 4n + 2. + case 2: // Output row 2. + case 3: // Output row 6. + color = BGRAColor::Red().AsPixel(); + break; + + // Fourth pass. Rows are positioned at 2n + 1. + case 4: // Output row 1. + color = BGRAColor::Green().AsPixel(); + break; + + case 5: // Output row 3. + color = BGRAColor::Red().AsPixel(); + break; + + case 6: // Output row 5. + color = BGRAColor::Green().AsPixel(); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected row"); + } + + ++row; + + for (; aLength > 0; --aLength, ++aRow, ++count) { + *aRow = color; + } + + return Nothing(); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(7u * 7u, count); + EXPECT_EQ(7u, row); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 7, 7), + IntRect(0, 0, 7, 7)); + + // Check that the generated image is correct. As mentioned above, we expect + // two green rows, followed by two red rows, then two green rows, etc. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + + for (uint32_t row = 0; row < 7; ++row) { + BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5 + ? BGRAColor::Green() + : BGRAColor::Red(); + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color)); + } + }); +} + +TEST(ImageDeinterlacingFilter, WritePixelsOutput3_3) +{ + WithDeinterlacingFilter(IntSize(3, 3), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be green, red, green in that order, but + // we need to write the rows in the order that the deinterlacer expects + // them. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() { + uint32_t row = count / 3; // Integer division. + ++count; + + switch (row) { + // First pass. Output rows are positioned at 8n + 0. + case 0: // Output row 0. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Second pass. Rows are positioned at 8n + 4. + // No rows for this pass. + + // Third pass. Rows are positioned at 4n + 2. + case 1: // Output row 2. + return AsVariant(BGRAColor::Green().AsPixel()); + + // Fourth pass. Rows are positioned at 2n + 1. + case 2: // Output row 1. + return AsVariant(BGRAColor::Red().AsPixel()); + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected row"); + return AsVariant(BGRAColor::Transparent().AsPixel()); + } + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(3u * 3u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 3, 3), + IntRect(0, 0, 3, 3)); + + // Check that the generated image is correct. As mentioned above, we expect + // green, red, green in that order. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + + for (uint32_t row = 0; row < 3; ++row) { + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, + row == 0 || row == 2 ? BGRAColor::Green() + : BGRAColor::Red())); + } + }); +} + +TEST(ImageDeinterlacingFilter, WritePixelsOutput1_1) +{ + WithDeinterlacingFilter(IntSize(1, 1), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be a single red row. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(1u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 1, 1), + IntRect(0, 0, 1, 1)); + + // Check that the generated image is correct. As mentioned above, we expect + // a single red row. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 1, BGRAColor::Red())); + }); +} + +void +WriteRowAndCheckInterlacerOutput(Decoder* aDecoder, + SurfaceFilter* aFilter, + BGRAColor aColor, + WriteState aNextState, + IntRect aInvalidRect, + uint32_t aFirstHaeberliRow, + uint32_t aLastHaeberliRow) +{ + uint32_t count = 0; + + auto result = aFilter->WriteRows([&](uint32_t* aRow, uint32_t aLength) { + for (; aLength > 0; --aLength, ++aRow, ++count) { + *aRow = aColor.AsPixel(); + } + return Some(WriteState::NEED_MORE_DATA); + }); + + EXPECT_EQ(aNextState, result); + EXPECT_EQ(7u, count); + + // Assert that we got the expected invalidation region. + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(aInvalidRect, invalidRect->mInputSpaceRect); + EXPECT_EQ(aInvalidRect, invalidRect->mOutputSpaceRect); + + // Check that the portion of the image generated so far is correct. The rows + // from aFirstHaeberliRow to aLastHaeberliRow should be filled with aColor. + // Note that this is not the same as the set of rows in aInvalidRect, because + // after writing a row the deinterlacer seeks to the next row to write, which + // may involve copying previously-written rows in the buffer to the output + // even though they don't change in this pass. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + + for (uint32_t row = aFirstHaeberliRow; row <= aLastHaeberliRow; ++row) { + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, aColor)); + } +} + +TEST(ImageDeinterlacingFilter, WriteRowsIntermediateOutput7_7) +{ + WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ true, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be a repeating pattern of two green + // rows followed by two red rows but we need to write the rows in the order + // that the deinterlacer expects them. + + // First pass. Output rows are positioned at 8n + 0. + + // Output row 0. The invalid rect is the entire image because this is the + // end of the first pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 0, 4); + + // Second pass. Rows are positioned at 8n + 4. + + // Output row 4. The invalid rect is the entire image because this is the + // end of the second pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 1, 4); + + // Third pass. Rows are positioned at 4n + 2. + + // Output row 2. The invalid rect contains the Haeberli rows for this output + // row (rows 2 and 3) as well as the rows that we copy from previous passes + // when seeking to the next output row (rows 4 and 5). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 2, 7, 4), 2, 3); + + // Output row 6. The invalid rect is the entire image because this is the + // end of the third pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 6, 6); + + // Fourth pass. Rows are positioned at 2n + 1. + + // Output row 1. The invalid rect contains the Haeberli rows for this output + // row (just row 1) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 2). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 1, 7, 2), 1, 1); + + // Output row 3. The invalid rect contains the Haeberli rows for this output + // row (just row 3) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 4). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 3, 7, 2), 3, 3); + + // Output row 5. The invalid rect contains the Haeberli rows for this output + // row (just row 5) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 6). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::FINISHED, + IntRect(0, 5, 7, 2), 5, 5); + + // Assert that we're in the expected final state. + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is correct. As mentioned above, we expect + // two green rows, followed by two red rows, then two green rows, etc. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + + for (uint32_t row = 0; row < 7; ++row) { + BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5 + ? BGRAColor::Green() + : BGRAColor::Red(); + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color)); + } + }); +} + +TEST(ImageDeinterlacingFilter, WriteRowsNonProgressiveIntermediateOutput7_7) +{ + WithDeinterlacingFilter(IntSize(7, 7), /* aProgressiveDisplay = */ false, + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. The output should be a repeating pattern of two green + // rows followed by two red rows but we need to write the rows in the order + // that the deinterlacer expects them. + + // First pass. Output rows are positioned at 8n + 0. + + // Output row 0. The invalid rect is the entire image because this is the + // end of the first pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 0, 0); + + // Second pass. Rows are positioned at 8n + 4. + + // Output row 4. The invalid rect is the entire image because this is the + // end of the second pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 4, 4); + + // Third pass. Rows are positioned at 4n + 2. + + // Output row 2. The invalid rect contains the Haeberli rows for this output + // row (rows 2 and 3) as well as the rows that we copy from previous passes + // when seeking to the next output row (rows 4 and 5). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 2, 7, 4), 2, 2); + + // Output row 6. The invalid rect is the entire image because this is the + // end of the third pass. + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 0, 7, 7), 6, 6); + + // Fourth pass. Rows are positioned at 2n + 1. + + // Output row 1. The invalid rect contains the Haeberli rows for this output + // row (just row 1) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 2). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::NEED_MORE_DATA, + IntRect(0, 1, 7, 2), 1, 1); + + // Output row 3. The invalid rect contains the Haeberli rows for this output + // row (just row 3) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 4). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Red(), + WriteState::NEED_MORE_DATA, + IntRect(0, 3, 7, 2), 3, 3); + + // Output row 5. The invalid rect contains the Haeberli rows for this output + // row (just row 5) as well as the rows that we copy from previous passes + // when seeking to the next output row (row 6). + WriteRowAndCheckInterlacerOutput(aDecoder, aFilter, BGRAColor::Green(), + WriteState::FINISHED, + IntRect(0, 5, 7, 2), 5, 5); + + // Assert that we're in the expected final state. + EXPECT_TRUE(aFilter->IsSurfaceFinished()); + Maybe invalidRect = aFilter->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is correct. As mentioned above, we expect + // two green rows, followed by two red rows, then two green rows, etc. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + + for (uint32_t row = 0; row < 7; ++row) { + BGRAColor color = row == 0 || row == 1 || row == 4 || row == 5 + ? BGRAColor::Green() + : BGRAColor::Red(); + EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, color)); + } + }); +} + + +TEST(ImageDeinterlacingFilter, DeinterlacingFailsFor0_0) +{ + // A 0x0 input size is invalid, so configuration should fail. + AssertConfiguringDeinterlacingFilterFails(IntSize(0, 0)); +} + +TEST(ImageDeinterlacingFilter, DeinterlacingFailsForMinus1_Minus1) +{ + // A negative input size is invalid, so configuration should fail. + AssertConfiguringDeinterlacingFilterFails(IntSize(-1, -1)); +} diff --git a/image/test/gtest/TestDownscalingFilter.cpp b/image/test/gtest/TestDownscalingFilter.cpp new file mode 100644 index 000000000000..9ede21c3f641 --- /dev/null +++ b/image/test/gtest/TestDownscalingFilter.cpp @@ -0,0 +1,364 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfaceFilters.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +template void +WithDownscalingFilter(const IntSize& aInputSize, + const IntSize& aOutputSize, + Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + WithFilterPipeline(decoder, Forward(aFunc), + DownscalingConfig { aInputSize, + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, aOutputSize, + SurfaceFormat::B8G8R8A8, false }); +} + +void +AssertConfiguringDownscalingFilterFails(const IntSize& aInputSize, + const IntSize& aOutputSize) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + AssertConfiguringPipelineFails(decoder, + DownscalingConfig { aInputSize, + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, aOutputSize, + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to99_99) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(99, 99), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 99))); + }); +} + +TEST(ImageDownscalingFilter, WriteRows100_100to99_99) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(99, 99), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 99))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to33_33) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(33, 33), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 33, 33))); + }); +} + +TEST(ImageDownscalingFilter, WriteRows100_100to33_33) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(33, 33), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 33, 33))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to1_1) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(1, 1), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 1, 1))); + }); +} + +TEST(ImageDownscalingFilter, WriteRows100_100to1_1) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(1, 1), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 1, 1))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to33_99) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(33, 99), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 33, 99))); + }); +} + +TEST(ImageDownscalingFilter, WriteRows100_100to33_99) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(33, 99), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 33, 99))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to99_33) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(99, 33), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 33))); + }); +} + +TEST(ImageDownscalingFilter, WriteRows100_100to99_33) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(99, 33), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 33))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to99_1) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(99, 1), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 1))); + }); +} + +TEST(ImageDownscalingFilter, WriteRows100_100to99_1) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(99, 1), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 99, 1))); + }); +} + +TEST(ImageDownscalingFilter, WritePixels100_100to1_99) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(1, 99), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 1, 99))); + }); +} + +TEST(ImageDownscalingFilter, WriteRows100_100to1_99) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(1, 99), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 1, 99))); + }); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to101_101) +{ + // Upscaling is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(101, 101)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to100_100) +{ + // "Scaling" to the same size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(100, 100)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor0_0toMinus1_Minus1) +{ + // A 0x0 input size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(0, 0), IntSize(-1, -1)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsForMinus1_Minus1toMinus2_Minus2) +{ + // A negative input size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(-1, -1), IntSize(-2, -2)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor100_100to0_0) +{ + // A 0x0 output size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(0, 0)); +} + +TEST(ImageDownscalingFilter, DownscalingFailsFor100_100toMinus1_Minus1) +{ + // A negative output size is disallowed. + AssertConfiguringDownscalingFilterFails(IntSize(100, 100), IntSize(-1, -1)); +} + +TEST(ImageDownscalingFilter, WritePixelsOutput100_100to20_20) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(20, 20), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. It consists of 25 lines of green, followed by 25 lines of + // red, followed by 25 lines of green, followed by 25 more lines of red. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() -> NextPixel { + uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100) + ? BGRAColor::Green().AsPixel() + : BGRAColor::Red().AsPixel(); + ++count; + return AsVariant(color); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 20, 20)); + + // Check that the generated image is correct. Note that we skip rows near + // the transitions between colors, since the downscaler does not produce a + // sharp boundary at these points. Even some of the rows we test need a + // small amount of fuzz; this is just the nature of Lanczos downscaling. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2)); + EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3)); + }); +} + +TEST(ImageDownscalingFilter, WriteRowsOutput100_100to20_20) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(20, 20), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. It consists of 25 lines of green, followed by 25 lines of + // red, followed by 25 lines of green, followed by 25 more lines of red. + uint32_t count = 0; + auto result = aFilter->WriteRows([&](uint32_t* aRow, uint32_t aLength) { + uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100) + ? BGRAColor::Green().AsPixel() + : BGRAColor::Red().AsPixel(); + for (; aLength > 0; --aLength, ++aRow, ++count) { + *aRow = color; + } + return Nothing(); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 20, 20)); + + // Check that the generated image is correct. (Note that we skip rows near + // the transitions between colors, since the downscaler does not produce a + // sharp boundary at these points.) + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2)); + EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3)); + }); +} + +TEST(ImageDownscalingFilter, WritePixelsOutput100_100to10_20) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(10, 20), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. It consists of 25 lines of green, followed by 25 lines of + // red, followed by 25 lines of green, followed by 25 more lines of red. + uint32_t count = 0; + auto result = aFilter->WritePixels([&]() -> NextPixel { + uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100) + ? BGRAColor::Green().AsPixel() + : BGRAColor::Red().AsPixel(); + ++count; + return AsVariant(color); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 10, 20)); + + // Check that the generated image is correct. Note that we skip rows near + // the transitions between colors, since the downscaler does not produce a + // sharp boundary at these points. Even some of the rows we test need a + // small amount of fuzz; this is just the nature of Lanczos downscaling. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2)); + EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3)); + }); +} + +TEST(ImageDownscalingFilter, WriteRowsOutput100_100to10_20) +{ + WithDownscalingFilter(IntSize(100, 100), IntSize(10, 20), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Fill the image. It consists of 25 lines of green, followed by 25 lines of + // red, followed by 25 lines of green, followed by 25 more lines of red. + uint32_t count = 0; + auto result = aFilter->WriteRows([&](uint32_t* aRow, uint32_t aLength) { + uint32_t color = (count <= 25 * 100) || (count > 50 * 100 && count <= 75 * 100) + ? BGRAColor::Green().AsPixel() + : BGRAColor::Red().AsPixel(); + for (; aLength > 0; --aLength, ++aRow, ++count) { + *aRow = color; + } + return Nothing(); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aFilter, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 10, 20)); + + // Check that the generated image is correct. (Note that we skip rows near + // the transitions between colors, since the downscaler does not produce a + // sharp boundary at these points.) + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 2)); + EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 3)); + EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 3)); + }); +} + +TEST(ImageDownscalingFilter, ConfiguringPalettedDownscaleFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // DownscalingFilter does not support paletted images, so configuration should + // fail. + AssertConfiguringPipelineFails(decoder, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + PalettedSurfaceConfig { decoder, 0, IntSize(20, 20), + IntRect(0, 0, 20, 20), + SurfaceFormat::B8G8R8A8, 8, + false }); +} diff --git a/image/test/gtest/TestDownscalingFilterNoSkia.cpp b/image/test/gtest/TestDownscalingFilterNoSkia.cpp new file mode 100644 index 000000000000..c62ca018da1f --- /dev/null +++ b/image/test/gtest/TestDownscalingFilterNoSkia.cpp @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfacePipe.h" + +// We want to ensure that we're testing the non-Skia fallback version of +// DownscalingFilter, but there are two issues: +// (1) We don't know whether Skia is currently enabled. +// (2) If we force disable it, the disabled version will get linked into the +// binary and will cause the tests in TestDownscalingFilter to fail. +// To avoid these problems, we ensure that MOZ_ENABLE_SKIA is defined when +// including DownscalingFilter.h, and we use the preprocessor to redefine the +// DownscalingFilter class to DownscalingFilterNoSkia. + +#define DownscalingFilter DownscalingFilterNoSkia + +#ifdef MOZ_ENABLE_SKIA + +#undef MOZ_ENABLE_SKIA +#include "Common.h" +#include "DownscalingFilter.h" +#define MOZ_ENABLE_SKIA + +#else + +#include "Common.h" +#include "DownscalingFilter.h" + +#endif + +#undef DownscalingFilter + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +TEST(ImageDownscalingFilter, NoSkia) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(bool(decoder)); + + // Configuring a DownscalingFilter should fail without Skia. + AssertConfiguringPipelineFails(decoder, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(50, 50), + SurfaceFormat::B8G8R8A8, false }); +} diff --git a/image/test/gtest/TestRemoveFrameRectFilter.cpp b/image/test/gtest/TestRemoveFrameRectFilter.cpp new file mode 100644 index 000000000000..d1b7afa84c2e --- /dev/null +++ b/image/test/gtest/TestRemoveFrameRectFilter.cpp @@ -0,0 +1,565 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfaceFilters.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +template void +WithRemoveFrameRectFilter(const IntSize& aSize, + const IntRect& aFrameRect, + Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + WithFilterPipeline(decoder, Forward(aFunc), + RemoveFrameRectConfig { aFrameRect }, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +void +AssertConfiguringRemoveFrameRectFilterFails(const IntSize& aSize, + const IntRect& aFrameRect) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + AssertConfiguringPipelineFails(decoder, + RemoveFrameRectConfig { aFrameRect }, + SurfaceConfig { decoder, 0, aSize, + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_0_0_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_0_0_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_0_0_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(0, 0, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_0_0_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(0, 0, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_50_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-50, 50, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_Minus50_50_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-50, 50, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_Minus50_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(50, -50, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_50_Minus50_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(50, -50, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_150_50_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(150, 50, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_150_50_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(150, 50, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_150_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(50, 150, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_50_150_0_0) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(50, 150, 0, 0), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_200_200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(200, 200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_200_200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(200, 200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus200_25_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-200, 25, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_Minus200_25_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-200, 25, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_Minus200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, -200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_25_Minus200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, -200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_200_25_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(200, 25, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_200_25_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(200, 25, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, 200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_25_200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, 200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is zero-size because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus200_Minus200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-200, -200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_Minus200_Minus200_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-200, -200, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 0, 0)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 0, 0))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_Minus50_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-50, -50, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_Minus50_Minus50_100_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-50, -50, 100, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(0, 0, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_Minus50_25_100_50) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-50, 25, 100, 50), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)), + /* aOutputWriteRect = */ Some(IntRect(0, 25, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_Minus50_25_100_50) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(-50, 25, 100, 50), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)), + /* aOutputWriteRect = */ Some(IntRect(0, 25, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_Minus50_50_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, -50, 50, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 50, 100)), + /* aOutputWriteRect = */ Some(IntRect(25, 0, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_25_Minus50_50_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, -50, 50, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 50, 100)), + /* aOutputWriteRect = */ Some(IntRect(25, 0, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_50_25_100_50) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(50, 25, 100, 50), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)), + /* aOutputWriteRect = */ Some(IntRect(50, 25, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_50_25_100_50) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(50, 25, 100, 50), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 50)), + /* aOutputWriteRect = */ Some(IntRect(50, 25, 50, 50))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WritePixels100_100_to_25_50_50_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, 50, 50, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is 50x50 because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(25, 50, 50, 100))); + }); +} + +TEST(ImageRemoveFrameRectFilter, WriteRows100_100_to_25_50_50_100) +{ + WithRemoveFrameRectFilter(IntSize(100, 100), + IntRect(25, 50, 50, 100), + [](Decoder* aDecoder, SurfaceFilter* aFilter) { + // Note that aInputRect is 50x50 because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows + // unfortunately can't be ignored.) + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(0, 0, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(25, 50, 50, 100))); + }); +} + +TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor0_0_to_0_0_100_100) +{ + // A zero-size image is disallowed. + AssertConfiguringRemoveFrameRectFilterFails(IntSize(0, 0), + IntRect(0, 0, 100, 100)); +} + +TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsForMinus1_Minus1_to_0_0_100_100) +{ + // A negative-size image is disallowed. + AssertConfiguringRemoveFrameRectFilterFails(IntSize(-1, -1), + IntRect(0, 0, 100, 100)); +} + +TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor100_100_to_0_0_0_0) +{ + // A zero size frame rect is disallowed. + AssertConfiguringRemoveFrameRectFilterFails(IntSize(100, 100), + IntRect(0, 0, -1, -1)); +} + +TEST(ImageRemoveFrameRectFilter, RemoveFrameRectFailsFor100_100_to_0_0_Minus1_Minus1) +{ + // A negative size frame rect is disallowed. + AssertConfiguringRemoveFrameRectFilterFails(IntSize(100, 100), + IntRect(0, 0, -1, -1)); +} + +TEST(ImageRemoveFrameRectFilter, ConfiguringPalettedRemoveFrameRectFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // RemoveFrameRectFilter does not support paletted images, so configuration + // should fail. + AssertConfiguringPipelineFails(decoder, + RemoveFrameRectConfig { IntRect(0, 0, 50, 50) }, + PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + IntRect(0, 0, 50, 50), + SurfaceFormat::B8G8R8A8, 8, + false }); +} diff --git a/image/test/gtest/TestSurfacePipeIntegration.cpp b/image/test/gtest/TestSurfacePipeIntegration.cpp new file mode 100644 index 000000000000..d992005e95cc --- /dev/null +++ b/image/test/gtest/TestSurfacePipeIntegration.cpp @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +namespace mozilla { +namespace image { + +class TestSurfacePipeFactory +{ +public: + static SurfacePipe SimpleSurfacePipe() + { + SurfacePipe pipe; + return Move(pipe); + } + + template + static SurfacePipe SurfacePipeFromPipeline(T&& aPipeline) + { + return SurfacePipe { Move(aPipeline) }; + } + +private: + TestSurfacePipeFactory() { } +}; + +} // namespace image +} // namespace mozilla + +TEST(ImageSurfacePipeIntegration, SurfacePipe) +{ + // Test that SurfacePipe objects can be initialized and move constructed. + SurfacePipe pipe = TestSurfacePipeFactory::SimpleSurfacePipe(); + + // Test that SurfacePipe objects can be move assigned. + pipe = TestSurfacePipeFactory::SimpleSurfacePipe(); + + // Test that SurfacePipe objects can be initialized with a pipeline. + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto sink = MakeUnique(); + nsresult rv = + sink->Configure(SurfaceConfig { decoder.get(), 0, IntSize(100, 100), + SurfaceFormat::B8G8R8A8, false }); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + + pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink); + + // Test that SurfacePipe passes through method calls to the underlying pipeline. + int32_t count = 0; + auto result = pipe.WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100 * 100, count); + + // Note that we're explicitly testing the SurfacePipe versions of these + // methods, so we don't want to use AssertCorrectPipelineFinalState() here. + EXPECT_TRUE(pipe.IsSurfaceFinished()); + Maybe invalidRect = pipe.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect); + + CheckGeneratedImage(decoder, IntRect(0, 0, 100, 100)); + + pipe.ResetToFirstRow(); + EXPECT_FALSE(pipe.IsSurfaceFinished()); + + RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef(); + currentFrame->Finish(); +} + +TEST(ImageSurfacePipeIntegration, DeinterlaceDownscaleWritePixels) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 25, 25))); + }; + + WithFilterPipeline(decoder, test, + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(25, 25), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageSurfacePipeIntegration, DeinterlaceDownscaleWriteRows) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 25, 25))); + }; + + WithFilterPipeline(decoder, test, + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(25, 25), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageSurfacePipeIntegration, RemoveFrameRectDownscaleWritePixels) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // Note that aInputWriteRect is 100x50 because RemoveFrameRectFilter ignores + // trailing rows that don't show up in the output. (Leading rows unfortunately + // can't be ignored.) So the action of the pipeline is as follows: + // + // (1) RemoveFrameRectFilter reads a 100x50 region of the input. + // (aInputWriteRect captures this fact.) The remaining 50 rows are ignored + // because they extend off the bottom of the image due to the frame rect's + // (50, 50) offset. The 50 columns on the right also don't end up in the + // output, so ultimately only a 50x50 region in the output contains data + // from the input. The filter's output is not 50x50, though, but 100x100, + // because what RemoveFrameRectFilter does is introduce blank rows or + // columns as necessary to transform an image that needs a frame rect into + // an image that doesn't. + // + // (2) DownscalingFilter reads the output of RemoveFrameRectFilter (100x100) + // and downscales it to 20x20. + // + // (3) The surface owned by SurfaceSink logically has only a 10x10 region + // region in it that's non-blank; this is the downscaled version of the + // 50x50 region discussed in (1). (aOutputWriteRect captures this fact.) + // Some fuzz, as usual, is necessary when dealing with Lanczos downscaling. + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 50)), + /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)), + /* aFuzz = */ 0x33); + }; + + WithFilterPipeline(decoder, test, + RemoveFrameRectConfig { IntRect(50, 50, 100, 100) }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(20, 20), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageSurfacePipeIntegration, RemoveFrameRectDownscaleWriteRows) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // See the WritePixels version of this test for a discussion of where the + // numbers below come from. + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 50)), + /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)), + /* aFuzz = */ 0x33); + }; + + WithFilterPipeline(decoder, test, + RemoveFrameRectConfig { IntRect(50, 50, 100, 100) }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(20, 20), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectWritePixels) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // Note that aInputRect is the full 100x100 size even though + // RemoveFrameRectFilter is part of this pipeline, because deinterlacing + // requires reading every row. + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(50, 50, 50, 50))); + }; + + WithFilterPipeline(decoder, test, + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + RemoveFrameRectConfig { IntRect(50, 50, 100, 100) }, + SurfaceConfig { decoder, 0, IntSize(100, 100), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectWriteRows) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // Note that aInputRect is the full 100x100 size even though + // RemoveFrameRectFilter is part of this pipeline, because deinterlacing + // requires reading every row. + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(50, 50, 50, 50))); + }; + + WithFilterPipeline(decoder, test, + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + RemoveFrameRectConfig { IntRect(50, 50, 100, 100) }, + SurfaceConfig { decoder, 0, IntSize(100, 100), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectDownscaleWritePixels) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWritePixels(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)), + /* aFuzz = */ 33); + }; + + WithFilterPipeline(decoder, test, + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + RemoveFrameRectConfig { IntRect(50, 50, 100, 100) }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(20, 20), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectDownscaleWriteRows) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + auto test = [](Decoder* aDecoder, SurfaceFilter* aFilter) { + CheckWriteRows(aDecoder, aFilter, + /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), + /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), + /* aInputWriteRect = */ Some(IntRect(50, 50, 100, 100)), + /* aOutputWriteRect = */ Some(IntRect(10, 10, 10, 10)), + /* aFuzz = */ 33); + }; + + WithFilterPipeline(decoder, test, + DeinterlacingConfig { /* mProgressiveDisplay = */ true }, + RemoveFrameRectConfig { IntRect(50, 50, 100, 100) }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + SurfaceConfig { decoder, 0, IntSize(20, 20), + SurfaceFormat::B8G8R8A8, false }); +} + +TEST(ImageSurfacePipeIntegration, ConfiguringPalettedRemoveFrameRectDownscaleFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // This is an invalid pipeline for paletted images, so configuration should + // fail. + AssertConfiguringPipelineFails(decoder, + RemoveFrameRectConfig { IntRect(0, 0, 50, 50) }, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + IntRect(0, 0, 50, 50), + SurfaceFormat::B8G8R8A8, 8, + false }); +} + +TEST(ImageSurfacePipeIntegration, ConfiguringPalettedDeinterlaceDownscaleFails) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + // This is an invalid pipeline for paletted images, so configuration should + // fail. + AssertConfiguringPipelineFails(decoder, + DeinterlacingConfig { /* mProgressiveDisplay = */ true}, + DownscalingConfig { IntSize(100, 100), + SurfaceFormat::B8G8R8A8 }, + PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + IntRect(0, 0, 20, 20), + SurfaceFormat::B8G8R8A8, 8, + false }); +} diff --git a/image/test/gtest/TestSurfaceSink.cpp b/image/test/gtest/TestSurfaceSink.cpp new file mode 100644 index 000000000000..6d3e5750b8f6 --- /dev/null +++ b/image/test/gtest/TestSurfaceSink.cpp @@ -0,0 +1,578 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/gfx/2D.h" +#include "Common.h" +#include "Decoder.h" +#include "DecoderFactory.h" +#include "SourceBuffer.h" +#include "SurfacePipe.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +enum class Orient +{ + NORMAL, + FLIP_VERTICALLY +}; + +template void +WithSurfaceSink(Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + const bool flipVertically = Orientation == Orient::FLIP_VERTICALLY; + + WithFilterPipeline(decoder, Forward(aFunc), + SurfaceConfig { decoder, 0, IntSize(100, 100), + SurfaceFormat::B8G8R8A8, flipVertically }); +} + +template void +WithPalettedSurfaceSink(const IntRect& aFrameRect, Func aFunc) +{ + RefPtr decoder = CreateTrivialDecoder(); + ASSERT_TRUE(decoder != nullptr); + + WithFilterPipeline(decoder, Forward(aFunc), + PalettedSurfaceConfig { decoder, 0, IntSize(100, 100), + aFrameRect, SurfaceFormat::B8G8R8A8, + 8, false }); +} + +TEST(ImageSurfaceSink, NullSurfaceSink) +{ + // Create the NullSurfaceSink. + NullSurfaceSink sink; + nsresult rv = sink.Configure(NullSurfaceConfig { }); + ASSERT_TRUE(NS_SUCCEEDED(rv)); + EXPECT_TRUE(!sink.IsValidPalettedPipe()); + + // Ensure that we can't write anything. + bool gotCalled = false; + auto result = sink.WritePixels([&]() { + gotCalled = true; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_FALSE(gotCalled); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(sink.IsSurfaceFinished()); + Maybe invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + result = sink.WriteRows([&](uint32_t* aRow, uint32_t aLength) { + gotCalled = true; + for (; aLength > 0; --aLength, ++aRow) { + *aRow = BGRAColor::Green().AsPixel(); + } + return Nothing(); + }); + EXPECT_FALSE(gotCalled); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_TRUE(sink.IsSurfaceFinished()); + invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Attempt to advance to the next row and make sure nothing changes. + sink.AdvanceRow(); + EXPECT_TRUE(sink.IsSurfaceFinished()); + invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Attempt to advance to the next pass and make sure nothing changes. + sink.ResetToFirstRow(); + EXPECT_TRUE(sink.IsSurfaceFinished()); + invalidRect = sink.TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); +} + +TEST(ImageSurfaceSink, SurfaceSinkWritePixels) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + CheckWritePixels(aDecoder, aSink); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWriteRows) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + CheckWriteRows(aDecoder, aSink); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWritePixelsFinish) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Write nothing into the surface; just finish immediately. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + count++; + return AsVariant(WriteState::FINISHED); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(1u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WritePixels([&]() { + count++; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Transparent())); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkWriteRowsFinish) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + // Write nothing into the surface; just finish immediately. + uint32_t count = 0; + auto result = aSink->WriteRows([&](uint32_t* aRow, uint32_t aLength) { + count++; + return Some(WriteState::FINISHED); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(1u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Attempt to write more and make sure that nothing gets written. + count = 0; + result = aSink->WriteRows([&](uint32_t* aRow, uint32_t aLength) { + count++; + for (; aLength > 0; --aLength, ++aRow) { + *aRow = BGRAColor::Green().AsPixel(); + } + return Nothing(); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(0u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Transparent())); + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkProgressivePasses) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + { + // Fill the image with a first pass of red. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); + } + + { + // Reset for the second pass. + aSink->ResetToFirstRow(); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is still the first pass image. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); + } + + { + // Fill the image with a second pass of green. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green())); + } + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkInvalidRect) +{ + WithSurfaceSink([](Decoder* aDecoder, SurfaceSink* aSink) { + { + // Write one row. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 100) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(100u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 0, 100, 1), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 0, 100, 1), invalidRect->mOutputSpaceRect); + } + + { + // Write eight rows. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 100 * 8) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(100u * 8u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 1, 100, 8), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 1, 100, 8), invalidRect->mOutputSpaceRect); + } + + { + // Write the left half of one row. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we don't have an invalid rect, since the invalid rect only + // gets updated when a row gets completed. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + } + + { + // Write the right half of the same row. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 50) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(50u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect, which will include both the + // left and right halves of this row now that we've completed it. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 9, 100, 1), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 9, 100, 1), invalidRect->mOutputSpaceRect); + } + + { + // Write no rows. + auto result = aSink->WritePixels([&]() { + return AsVariant(WriteState::NEED_MORE_DATA); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we don't have an invalid rect. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + } + + { + // Fill the rest of the image. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 90u, count); + EXPECT_TRUE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 10, 100, 90), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 10, 100, 90), invalidRect->mOutputSpaceRect); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green())); + } + }); +} + +TEST(ImageSurfaceSink, SurfaceSinkFlipVertically) +{ + WithSurfaceSink([](Decoder* aDecoder, + SurfaceSink* aSink) { + { + // Fill the image with a first pass of red. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Red().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(100u * 100u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 100), + IntRect(0, 0, 100, 100)); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); + } + + { + // Reset for the second pass. + aSink->ResetToFirstRow(); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isNothing()); + + // Check that the generated image is still the first pass image. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); + } + + { + // Fill 25 rows of the image with green and make sure everything is OK. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() -> NextPixel { + if (count == 25 * 100) { + return AsVariant(WriteState::NEED_MORE_DATA); + } + count++; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::NEED_MORE_DATA, result); + EXPECT_EQ(25u * 100u, count); + EXPECT_FALSE(aSink->IsSurfaceFinished()); + + // Assert that we have the right invalid rect, which should include the + // *bottom* (since we're flipping vertically) 25 rows of the image. + Maybe invalidRect = aSink->TakeInvalidRect(); + EXPECT_TRUE(invalidRect.isSome()); + EXPECT_EQ(IntRect(0, 75, 100, 25), invalidRect->mInputSpaceRect); + EXPECT_EQ(IntRect(0, 75, 100, 25), invalidRect->mOutputSpaceRect); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(RowsAreSolidColor(surface, 0, 75, BGRAColor::Red())); + EXPECT_TRUE(RowsAreSolidColor(surface, 75, 25, BGRAColor::Green())); + } + + { + // Fill the rest of the image with a second pass of green. + uint32_t count = 0; + auto result = aSink->WritePixels([&]() { + ++count; + return AsVariant(BGRAColor::Green().AsPixel()); + }); + EXPECT_EQ(WriteState::FINISHED, result); + EXPECT_EQ(75u * 100u, count); + + AssertCorrectPipelineFinalState(aSink, + IntRect(0, 0, 100, 75), + IntRect(0, 0, 100, 75)); + + // Check that the generated image is correct. + RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); + RefPtr surface = currentFrame->GetSurface(); + EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green())); + } + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor0_0_100_100) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteRowsFor0_0_100_100) +{ + WithPalettedSurfaceSink(IntRect(0, 0, 100, 100), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWriteRows(aDecoder, aSink); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor25_25_50_50) +{ + WithPalettedSurfaceSink(IntRect(25, 25, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(25, 25, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(25, 25, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteRowsFor25_25_50_50) +{ + WithPalettedSurfaceSink(IntRect(25, 25, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWriteRows(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(25, 25, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(25, 25, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsForMinus25_Minus25_50_50) +{ + WithPalettedSurfaceSink(IntRect(-25, -25, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(-25, -25, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(-25, -25, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteRowsForMinus25_Minus25_50_50) +{ + WithPalettedSurfaceSink(IntRect(-25, -25, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWriteRows(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(-25, -25, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(-25, -25, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor75_Minus25_50_50) +{ + WithPalettedSurfaceSink(IntRect(75, -25, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(75, -25, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(75, -25, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteRowsFor75_Minus25_50_50) +{ + WithPalettedSurfaceSink(IntRect(75, -25, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWriteRows(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(75, -25, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(75, -25, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsForMinus25_75_50_50) +{ + WithPalettedSurfaceSink(IntRect(-25, 75, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(-25, 75, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(-25, 75, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteRowsForMinus25_75_50_50) +{ + WithPalettedSurfaceSink(IntRect(-25, 75, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWriteRows(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(-25, 75, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(-25, 75, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor75_75_50_50) +{ + WithPalettedSurfaceSink(IntRect(75, 75, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWritePixels(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(75, 75, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(75, 75, 50, 50))); + }); +} + +TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteRowsFor75_75_50_50) +{ + WithPalettedSurfaceSink(IntRect(75, 75, 50, 50), + [](Decoder* aDecoder, PalettedSurfaceSink* aSink) { + CheckPalettedWriteRows(aDecoder, aSink, + /* aOutputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputRect = */ Some(IntRect(0, 0, 50, 50)), + /* aInputWriteRect = */ Some(IntRect(75, 75, 50, 50)), + /* aOutputWriteRect = */ Some(IntRect(75, 75, 50, 50))); + }); +} diff --git a/image/test/gtest/moz.build b/image/test/gtest/moz.build index dd23f42aaa2a..c2a7038021ef 100644 --- a/image/test/gtest/moz.build +++ b/image/test/gtest/moz.build @@ -11,8 +11,22 @@ UNIFIED_SOURCES = [ 'TestCopyOnWrite.cpp', 'TestDecoders.cpp', 'TestDecodeToSurface.cpp', + 'TestDeinterlacingFilter.cpp', 'TestMetadata.cpp', + 'TestRemoveFrameRectFilter.cpp', 'TestStreamingLexer.cpp', + 'TestSurfaceSink.cpp', +] + +if CONFIG['MOZ_ENABLE_SKIA']: + UNIFIED_SOURCES += [ + 'TestDownscalingFilter.cpp', + 'TestSurfacePipeIntegration.cpp', + ] + +SOURCES += [ + # Can't be unified because it manipulates the preprocessor environment. + 'TestDownscalingFilterNoSkia.cpp', ] TEST_HARNESS_FILES.gtest += [ @@ -34,8 +48,14 @@ TEST_HARNESS_FILES.gtest += [ 'transparent.png', ] +include('/ipc/chromium/chromium-config.mozbuild') + LOCAL_INCLUDES += [ + '/dom/base', + '/gfx/2d', '/image', ] +LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES'] + FINAL_LIBRARY = 'xul-gtest'