/* -*- 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 pipe; } template static SurfacePipe SurfacePipeFromPipeline(T&& aPipeline) { return SurfacePipe{std::move(aPipeline)}; } private: TestSurfacePipeFactory() {} }; } // namespace image } // namespace mozilla void CheckSurfacePipeMethodResults(SurfacePipe* aPipe, image::Decoder* aDecoder, const IntRect& aRect = IntRect(0, 0, 100, 100)) { // Check that the pipeline ended up in the state we expect. Note that we're // explicitly testing the SurfacePipe versions of these methods, so we don't // want to use AssertCorrectPipelineFinalState() here. EXPECT_TRUE(aPipe->IsSurfaceFinished()); Maybe invalidRect = aPipe->TakeInvalidRect(); EXPECT_TRUE(invalidRect.isSome()); EXPECT_EQ(OrientedIntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect); EXPECT_EQ(OrientedIntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect); // Check the generated image. CheckGeneratedImage(aDecoder, aRect); // Reset and clear the image before the next test. aPipe->ResetToFirstRow(); EXPECT_FALSE(aPipe->IsSurfaceFinished()); invalidRect = aPipe->TakeInvalidRect(); EXPECT_TRUE(invalidRect.isNothing()); uint32_t count = 0; auto result = aPipe->WritePixels([&]() { ++count; return AsVariant(BGRAColor::Transparent().AsPixel()); }); EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(100u * 100u, count); EXPECT_TRUE(aPipe->IsSurfaceFinished()); invalidRect = aPipe->TakeInvalidRect(); EXPECT_TRUE(invalidRect.isSome()); EXPECT_EQ(OrientedIntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect); EXPECT_EQ(OrientedIntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect); aPipe->ResetToFirstRow(); EXPECT_FALSE(aPipe->IsSurfaceFinished()); invalidRect = aPipe->TakeInvalidRect(); EXPECT_TRUE(invalidRect.isNothing()); } class ImageSurfacePipeIntegration : public ::testing::Test { protected: AutoInitializeImageLib mInit; }; TEST_F(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, IntSize(100, 100), SurfaceFormat::OS_RGBA, false}); ASSERT_NS_SUCCEEDED(rv); pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink); // Test that WritePixels() gets passed through to the underlying pipeline. { uint32_t count = 0; auto result = pipe.WritePixels([&]() { ++count; return AsVariant(BGRAColor::Green().AsPixel()); }); EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(100u * 100u, count); CheckSurfacePipeMethodResults(&pipe, decoder); } // Create a buffer the same size as one row of the surface, containing all // green pixels. We'll use this for the WriteBuffer() tests. uint32_t buffer[100]; for (int i = 0; i < 100; ++i) { buffer[i] = BGRAColor::Green().AsPixel(); } // Test that WriteBuffer() gets passed through to the underlying pipeline. { uint32_t count = 0; WriteState result = WriteState::NEED_MORE_DATA; while (result == WriteState::NEED_MORE_DATA) { result = pipe.WriteBuffer(buffer); ++count; } EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(100u, count); CheckSurfacePipeMethodResults(&pipe, decoder); } // Test that the 3 argument version of WriteBuffer() gets passed through to // the underlying pipeline. { uint32_t count = 0; WriteState result = WriteState::NEED_MORE_DATA; while (result == WriteState::NEED_MORE_DATA) { result = pipe.WriteBuffer(buffer, 0, 100); ++count; } EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(100u, count); CheckSurfacePipeMethodResults(&pipe, decoder); } // Test that WritePixelBlocks() gets passed through to the underlying // pipeline. { uint32_t count = 0; WriteState result = pipe.WritePixelBlocks( [&](uint32_t* aBlockStart, int32_t aLength) { ++count; EXPECT_EQ(int32_t(100), aLength); memcpy(aBlockStart, buffer, 100 * sizeof(uint32_t)); return std::make_tuple(int32_t(100), Maybe()); }); EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(100u, count); CheckSurfacePipeMethodResults(&pipe, decoder); } // Test that WriteEmptyRow() gets passed through to the underlying pipeline. { uint32_t count = 0; WriteState result = WriteState::NEED_MORE_DATA; while (result == WriteState::NEED_MORE_DATA) { result = pipe.WriteEmptyRow(); ++count; } EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(100u, count); CheckSurfacePipeMethodResults(&pipe, decoder, IntRect(0, 0, 0, 0)); } // Mark the frame as finished so we don't get an assertion. RawAccessFrameRef currentFrame = decoder->GetCurrentFrameRef(); currentFrame->Finish(); } TEST_F(ImageSurfacePipeIntegration, DeinterlaceDownscaleWritePixels) { RefPtr decoder = CreateTrivialDecoder(); ASSERT_TRUE(decoder != nullptr); auto test = [](image::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::OS_RGBA}, SurfaceConfig{decoder, IntSize(25, 25), SurfaceFormat::OS_RGBA, false}); } TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectBottomRightDownscaleWritePixels) { // This test case uses a frame rect that extends beyond the borders of the // image to the bottom and to the right. It looks roughly like this (with the // box made of '#'s representing the frame rect): // // +------------+ // + + // + +------------+ // + +############+ // +------+############+ // +############+ // +------------+ 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 = [](image::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::OS_RGBA}, SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::OS_RGBA, false}); } TEST_F(ImageSurfacePipeIntegration, RemoveFrameRectTopLeftDownscaleWritePixels) { // This test case uses a frame rect that extends beyond the borders of the // image to the top and to the left. It looks roughly like this (with the // box made of '#'s representing the frame rect): // // +------------+ // +############+ // +############+------+ // +############+ + // +------------+ + // + + // +------------+ RefPtr decoder = CreateTrivialDecoder(); ASSERT_TRUE(decoder != nullptr); auto test = [](image::Decoder* aDecoder, SurfaceFilter* aFilter) { CheckWritePixels(aDecoder, aFilter, /* aOutputRect = */ Some(IntRect(0, 0, 20, 20)), /* aInputRect = */ Some(IntRect(0, 0, 100, 100)), /* aInputWriteRect = */ Some(IntRect(0, 0, 100, 100)), /* aOutputWriteRect = */ Some(IntRect(0, 0, 10, 10)), /* aFuzz = */ 0x21); }; WithFilterPipeline( decoder, test, RemoveFrameRectConfig{IntRect(-50, -50, 100, 100)}, DownscalingConfig{IntSize(100, 100), SurfaceFormat::OS_RGBA}, SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::OS_RGBA, false}); } TEST_F(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 = [](image::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, IntSize(100, 100), SurfaceFormat::OS_RGBA, false}); } TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectDownscaleWritePixels) { RefPtr decoder = CreateTrivialDecoder(); ASSERT_TRUE(decoder != nullptr); auto test = [](image::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::OS_RGBA}, SurfaceConfig{decoder, IntSize(20, 20), SurfaceFormat::OS_RGBA, false}); } TEST_F(ImageSurfacePipeIntegration, ConfiguringHugeDeinterlacingBufferFails) { RefPtr decoder = CreateTrivialDecoder(); ASSERT_TRUE(decoder != nullptr); // When DownscalingFilter is used, we may succeed in allocating an output // surface for huge images, because we only need to store the scaled-down // version of the image. However, regardless of downscaling, // DeinterlacingFilter needs to allocate a buffer as large as the size of the // input. This can cause OOMs on operating systems that allow overcommit. This // test makes sure that we reject such allocations. AssertConfiguringPipelineFails( decoder, DeinterlacingConfig{/* mProgressiveDisplay = */ true}, DownscalingConfig{IntSize(60000, 60000), SurfaceFormat::OS_RGBA}, SurfaceConfig{decoder, IntSize(600, 600), SurfaceFormat::OS_RGBA, false}); }