/* -*- 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 }; static void InitializeRowBuffer(uint32_t* aBuffer, size_t aSize, size_t aStartPixel, size_t aEndPixel, uint32_t aSetPixel) { uint32_t transparentPixel = BGRAColor::Transparent().AsPixel(); for (size_t i = 0; i < aStartPixel && i < aSize; ++i) { aBuffer[i] = transparentPixel; } for (size_t i = aStartPixel; i < aEndPixel && i < aSize; ++i) { aBuffer[i] = aSetPixel; } for (size_t i = aEndPixel; i < aSize; ++i) { aBuffer[i] = transparentPixel; } } template void WithSurfaceSink(Func aFunc) { RefPtr decoder = CreateTrivialDecoder(); ASSERT_TRUE(decoder != nullptr); const bool flipVertically = Orientation == Orient::FLIP_VERTICALLY; WithFilterPipeline(decoder, std::forward(aFunc), SurfaceConfig{decoder, IntSize(100, 100), SurfaceFormat::OS_RGBA, flipVertically}); } void ResetForNextPass(SurfaceFilter* aSink) { aSink->ResetToFirstRow(); EXPECT_FALSE(aSink->IsSurfaceFinished()); Maybe invalidRect = aSink->TakeInvalidRect(); EXPECT_TRUE(invalidRect.isNothing()); } template void DoCheckIterativeWrite(SurfaceFilter* aSink, WriteFunc aWriteFunc, CheckFunc aCheckFunc) { // Write the buffer to successive rows until every row of the surface // has been written. uint32_t row = 0; WriteState result = WriteState::NEED_MORE_DATA; while (result == WriteState::NEED_MORE_DATA) { result = aWriteFunc(row); ++row; } EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(100u, row); AssertCorrectPipelineFinalState(aSink, IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100)); // Check that the generated image is correct. aCheckFunc(); } template void CheckIterativeWrite(image::Decoder* aDecoder, SurfaceSink* aSink, const IntRect& aOutputRect, WriteFunc aWriteFunc) { // Ignore the row passed to WriteFunc, since no callers use it. auto writeFunc = [&](uint32_t) { return aWriteFunc(); }; DoCheckIterativeWrite(aSink, writeFunc, [&] { CheckGeneratedImage(aDecoder, aOutputRect); }); } TEST(ImageSurfaceSink, SurfaceSinkInitialization) { WithSurfaceSink( [](image::Decoder* aDecoder, SurfaceSink* aSink) { // Check initial state. EXPECT_FALSE(aSink->IsSurfaceFinished()); Maybe invalidRect = aSink->TakeInvalidRect(); EXPECT_TRUE(invalidRect.isNothing()); // Check that the surface is zero-initialized. We verify this by calling // CheckGeneratedImage() and telling it that we didn't write to the // surface anyway (i.e., we wrote to the empty rect); it will then // expect the entire surface to be transparent, which is what it should // be if it was zero-initialied. CheckGeneratedImage(aDecoder, IntRect(0, 0, 0, 0)); }); } TEST(ImageSurfaceSink, SurfaceSinkWritePixels) { WithSurfaceSink( [](image::Decoder* aDecoder, SurfaceSink* aSink) { CheckWritePixels(aDecoder, aSink); }); } TEST(ImageSurfaceSink, SurfaceSinkWritePixelsFinish) { WithSurfaceSink( [](image::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); AssertCorrectPipelineFinalState(aSink, IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100)); // 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->GetSourceSurface(); EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Transparent())); }); } TEST(ImageSurfaceSink, SurfaceSinkWritePixelsEarlyExit) { auto checkEarlyExit = [](image::Decoder* aDecoder, SurfaceSink* aSink, WriteState aState) { // Write half a row of green pixels and then exit early with |aState|. If // the lambda keeps getting called, we'll write red pixels, which will cause // the test to fail. uint32_t count = 0; auto result = aSink->WritePixels([&]() -> NextPixel { if (count == 50) { return AsVariant(aState); } return count++ < 50 ? AsVariant(BGRAColor::Green().AsPixel()) : AsVariant(BGRAColor::Red().AsPixel()); }); EXPECT_EQ(aState, result); EXPECT_EQ(50u, count); CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1)); if (aState != WriteState::FINISHED) { // We should still be able to write more at this point. EXPECT_FALSE(aSink->IsSurfaceFinished()); // Verify that we can resume writing. We'll finish up the same row. count = 0; 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()); CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 1)); return; } // We should've finished the surface at this point. AssertCorrectPipelineFinalState(aSink, IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100)); // 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 still correct. CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1)); }; WithSurfaceSink( [&](image::Decoder* aDecoder, SurfaceSink* aSink) { checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA); }); WithSurfaceSink( [&](image::Decoder* aDecoder, SurfaceSink* aSink) { checkEarlyExit(aDecoder, aSink, WriteState::FAILURE); }); WithSurfaceSink( [&](image::Decoder* aDecoder, SurfaceSink* aSink) { checkEarlyExit(aDecoder, aSink, WriteState::FINISHED); }); } TEST(ImageSurfaceSink, SurfaceSinkWritePixelsToRow) { WithSurfaceSink([](image::Decoder* aDecoder, SurfaceSink* aSink) { // Write the first 99 rows of our 100x100 surface and verify that even // though our lambda will yield pixels forever, only one row is written // per call to WritePixelsToRow(). for (int row = 0; row < 99; ++row) { uint32_t count = 0; WriteState result = aSink->WritePixelsToRow([&] { ++count; return AsVariant(BGRAColor::Green().AsPixel()); }); EXPECT_EQ(WriteState::NEED_MORE_DATA, result); EXPECT_EQ(100u, count); EXPECT_FALSE(aSink->IsSurfaceFinished()); Maybe invalidRect = aSink->TakeInvalidRect(); EXPECT_TRUE(invalidRect.isSome()); EXPECT_EQ(OrientedIntRect(0, row, 100, 1), invalidRect->mInputSpaceRect); EXPECT_EQ(OrientedIntRect(0, row, 100, 1), invalidRect->mOutputSpaceRect); CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, row + 1)); } // Write the final line, which should finish the surface. uint32_t count = 0; WriteState result = aSink->WritePixelsToRow([&] { ++count; return AsVariant(BGRAColor::Green().AsPixel()); }); EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(100u, count); // Note that the final invalid rect we expect here is only the last row; // that's because we called TakeInvalidRect() repeatedly in the loop // above. AssertCorrectPipelineFinalState(aSink, IntRect(0, 99, 100, 1), IntRect(0, 99, 100, 1)); // Check that the generated image is correct. CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 100)); // Attempt to write more and make sure that nothing gets written. count = 0; result = aSink->WritePixelsToRow([&] { 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 still correct. CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 100)); }); } TEST(ImageSurfaceSink, SurfaceSinkWritePixelsToRowEarlyExit) { auto checkEarlyExit = [](image::Decoder* aDecoder, SurfaceSink* aSink, WriteState aState) { // Write half a row of green pixels and then exit early with |aState|. If // the lambda keeps getting called, we'll write red pixels, which will cause // the test to fail. uint32_t count = 0; auto result = aSink->WritePixelsToRow([&]() -> NextPixel { if (count == 50) { return AsVariant(aState); } return count++ < 50 ? AsVariant(BGRAColor::Green().AsPixel()) : AsVariant(BGRAColor::Red().AsPixel()); }); EXPECT_EQ(aState, result); EXPECT_EQ(50u, count); CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1)); if (aState != WriteState::FINISHED) { // We should still be able to write more at this point. EXPECT_FALSE(aSink->IsSurfaceFinished()); // Verify that we can resume the same row and still stop at the end. count = 0; WriteState result = aSink->WritePixelsToRow([&] { ++count; return AsVariant(BGRAColor::Green().AsPixel()); }); EXPECT_EQ(WriteState::NEED_MORE_DATA, result); EXPECT_EQ(50u, count); EXPECT_FALSE(aSink->IsSurfaceFinished()); CheckGeneratedImage(aDecoder, IntRect(0, 0, 100, 1)); return; } // We should've finished the surface at this point. AssertCorrectPipelineFinalState(aSink, IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100)); // Attempt to write more and make sure that nothing gets written. count = 0; result = aSink->WritePixelsToRow([&] { 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 still correct. CheckGeneratedImage(aDecoder, IntRect(0, 0, 50, 1)); }; WithSurfaceSink( [&](image::Decoder* aDecoder, SurfaceSink* aSink) { checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA); }); WithSurfaceSink( [&](image::Decoder* aDecoder, SurfaceSink* aSink) { checkEarlyExit(aDecoder, aSink, WriteState::FAILURE); }); WithSurfaceSink( [&](image::Decoder* aDecoder, SurfaceSink* aSink) { checkEarlyExit(aDecoder, aSink, WriteState::FINISHED); }); } TEST(ImageSurfaceSink, SurfaceSinkWriteBuffer) { WithSurfaceSink( [](image::Decoder* aDecoder, SurfaceSink* aSink) { // Create a green buffer the same size as one row of the surface (which // is 100x100), containing 60 pixels of green in the middle and 20 // transparent pixels on either side. uint32_t buffer[100]; InitializeRowBuffer(buffer, 100, 20, 80, BGRAColor::Green().AsPixel()); // Write the buffer to every row of the surface and check that the // generated image is correct. CheckIterativeWrite(aDecoder, aSink, IntRect(20, 0, 60, 100), [&] { return aSink->WriteBuffer(buffer); }); }); } TEST(ImageSurfaceSink, SurfaceSinkWriteBufferPartialRow) { WithSurfaceSink( [](image::Decoder* aDecoder, SurfaceSink* aSink) { // Create a buffer the same size as one row of the surface, containing // all green pixels. uint32_t buffer[100]; for (int i = 0; i < 100; ++i) { buffer[i] = BGRAColor::Green().AsPixel(); } // Write the buffer to the middle 60 pixels of every row of the surface // and check that the generated image is correct. CheckIterativeWrite(aDecoder, aSink, IntRect(20, 0, 60, 100), [&] { return aSink->WriteBuffer(buffer, 20, 60); }); }); } TEST(ImageSurfaceSink, SurfaceSinkWriteBufferPartialRowStartColOverflow) { WithSurfaceSink([](image::Decoder* aDecoder, SurfaceSink* aSink) { // Create a buffer the same size as one row of the surface, containing all // green pixels. uint32_t buffer[100]; for (int i = 0; i < 100; ++i) { buffer[i] = BGRAColor::Green().AsPixel(); } { // Write the buffer to successive rows until every row of the surface // has been written. We place the start column beyond the end of the row, // which will prevent us from writing anything, so we check that the // generated image is entirely transparent. CheckIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0), [&] { return aSink->WriteBuffer(buffer, 100, 100); }); } ResetForNextPass(aSink); { // Write the buffer to successive rows until every row of the surface // has been written. We use column 50 as the start column, but we still // write the buffer, which means we overflow the right edge of the surface // by 50 pixels. We check that the left half of the generated image is // transparent and the right half is green. CheckIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&] { return aSink->WriteBuffer(buffer, 50, 100); }); } }); } TEST(ImageSurfaceSink, SurfaceSinkWriteBufferPartialRowBufferOverflow) { WithSurfaceSink([](image::Decoder* aDecoder, SurfaceSink* aSink) { // Create a buffer twice as large as a row of the surface. The first half // (which is as large as a row of the image) will contain green pixels, // while the second half will contain red pixels. uint32_t buffer[200]; for (int i = 0; i < 200; ++i) { buffer[i] = i < 100 ? BGRAColor::Green().AsPixel() : BGRAColor::Red().AsPixel(); } { // Write the buffer to successive rows until every row of the surface has // been written. The buffer extends 100 pixels to the right of a row of // the surface, but bounds checking will prevent us from overflowing the // buffer. We check that the generated image is entirely green since the // pixels on the right side of the buffer shouldn't have been written to // the surface. CheckIterativeWrite(aDecoder, aSink, IntRect(0, 0, 100, 100), [&] { return aSink->WriteBuffer(buffer, 0, 200); }); } ResetForNextPass(aSink); { // Write from the buffer to the middle of each row of the surface. That // means that the left side of each row should be transparent, since we // didn't write anything there. A buffer overflow would cause us to write // buffer contents into the left side of each row. We check that the // generated image is transparent on the left side and green on the right. CheckIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&] { return aSink->WriteBuffer(buffer, 50, 200); }); } }); } TEST(ImageSurfaceSink, SurfaceSinkWriteBufferFromNullSource) { WithSurfaceSink( [](image::Decoder* aDecoder, SurfaceSink* aSink) { // Calling WriteBuffer() with a null pointer should fail without making // any changes to the surface. uint32_t* nullBuffer = nullptr; WriteState result = aSink->WriteBuffer(nullBuffer); EXPECT_EQ(WriteState::FAILURE, result); EXPECT_FALSE(aSink->IsSurfaceFinished()); Maybe invalidRect = aSink->TakeInvalidRect(); EXPECT_TRUE(invalidRect.isNothing()); // Check that nothing got written to the surface. CheckGeneratedImage(aDecoder, IntRect(0, 0, 0, 0)); }); } TEST(ImageSurfaceSink, SurfaceSinkWriteEmptyRow) { WithSurfaceSink([](image::Decoder* aDecoder, SurfaceSink* aSink) { { // Write an empty row to each row of the surface. We check that the // generated image is entirely transparent. CheckIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0), [&] { return aSink->WriteEmptyRow(); }); } ResetForNextPass(aSink); { // Write a partial row before we begin calling WriteEmptyRow(). We check // that the generated image is entirely transparent, which is to be // expected since WriteEmptyRow() overwrites the current row even if some // data has already been written to it. 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()); CheckIterativeWrite(aDecoder, aSink, IntRect(0, 0, 0, 0), [&] { return aSink->WriteEmptyRow(); }); } ResetForNextPass(aSink); { // Create a buffer the same size as one row of the surface, containing all // green pixels. uint32_t buffer[100]; for (int i = 0; i < 100; ++i) { buffer[i] = BGRAColor::Green().AsPixel(); } // Write an empty row to the middle 60 rows of the surface. The first 20 // and last 20 rows will be green. (We need to use DoCheckIterativeWrite() // here because we need a custom function to check the output, since it // can't be described by a simple rect.) auto writeFunc = [&](uint32_t aRow) { if (aRow < 20 || aRow >= 80) { return aSink->WriteBuffer(buffer); } else { return aSink->WriteEmptyRow(); } }; auto checkFunc = [&] { RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); RefPtr surface = currentFrame->GetSourceSurface(); EXPECT_TRUE(RowsAreSolidColor(surface, 0, 20, BGRAColor::Green())); EXPECT_TRUE( RowsAreSolidColor(surface, 20, 60, BGRAColor::Transparent())); EXPECT_TRUE(RowsAreSolidColor(surface, 80, 20, BGRAColor::Green())); }; DoCheckIterativeWrite(aSink, writeFunc, checkFunc); } }); } TEST(ImageSurfaceSink, SurfaceSinkWriteUnsafeComputedRow) { WithSurfaceSink( [](image::Decoder* aDecoder, SurfaceSink* aSink) { // Create a green buffer the same size as one row of the surface. uint32_t buffer[100]; for (int i = 0; i < 100; ++i) { buffer[i] = BGRAColor::Green().AsPixel(); } // Write the buffer to successive rows until every row of the surface // has been written. We only write to the right half of each row, so we // check that the left side of the generated image is transparent and // the right side is green. CheckIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&] { return aSink->WriteUnsafeComputedRow( [&](uint32_t* aRow, uint32_t aLength) { EXPECT_EQ(100u, aLength); memcpy(aRow + 50, buffer, 50 * sizeof(uint32_t)); }); }); }); } TEST(ImageSurfaceSink, SurfaceSinkWritePixelBlocks) { WithSurfaceSink( [](image::Decoder* aDecoder, SurfaceSink* aSink) { // Create a green buffer the same size as one row of the surface (which // is 100x100), containing 60 pixels of green in the middle and 20 // transparent pixels on either side. uint32_t buffer[100]; InitializeRowBuffer(buffer, 100, 20, 80, BGRAColor::Green().AsPixel()); uint32_t count = 0; WriteState result = aSink->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); AssertCorrectPipelineFinalState(aSink, IntRect(0, 0, 100, 100), IntRect(0, 0, 100, 100)); // Check that the generated image is correct. CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, 100)); // Attempt to write more and make sure that nothing gets written. count = 0; result = aSink->WritePixelBlocks( [&](uint32_t* aBlockStart, int32_t aLength) { count++; for (int32_t i = 0; i < aLength; ++i) { aBlockStart[i] = BGRAColor::Red().AsPixel(); } return std::make_tuple(aLength, Maybe()); }); EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(0u, count); EXPECT_TRUE(aSink->IsSurfaceFinished()); // Check that the generated image is still correct. CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, 100)); }); } TEST(ImageSurfaceSink, SurfaceSinkWritePixelBlocksPartialRow) { WithSurfaceSink([](image::Decoder* aDecoder, SurfaceSink* aSink) { // Create a green buffer the same size as one row of the surface (which is // 100x100), containing 60 pixels of green in the middle and 20 transparent // pixels on either side. uint32_t buffer[100]; InitializeRowBuffer(buffer, 100, 20, 80, BGRAColor::Green().AsPixel()); // Write the first 99 rows of our 100x100 surface and verify that even // though our lambda will yield pixels forever, only one row is written per // call to WritePixelsToRow(). for (int row = 0; row < 99; ++row) { for (int32_t written = 0; written < 100;) { WriteState result = aSink->WritePixelBlocks( [&](uint32_t* aBlockStart, int32_t aLength) { // When we write the final block of pixels, it will request we // start another row. We should abort at that point. if (aLength == int32_t(100) && written == int32_t(100)) { return std::make_tuple(int32_t(0), Some(WriteState::NEED_MORE_DATA)); } // It should always request enough data to fill the row. So it // should request 100, 75, 50, and finally 25 pixels. EXPECT_EQ(int32_t(100) - written, aLength); // Only write one quarter of the pixels for the row. memcpy(aBlockStart, &buffer[written], 25 * sizeof(uint32_t)); written += 25; // We've written the last pixels remaining for the row. if (written == int32_t(100)) { return std::make_tuple(int32_t(25), Maybe()); } // We've written another quarter of the row but not yet all of it. return std::make_tuple(int32_t(25), Some(WriteState::NEED_MORE_DATA)); }); EXPECT_EQ(WriteState::NEED_MORE_DATA, result); } EXPECT_FALSE(aSink->IsSurfaceFinished()); Maybe invalidRect = aSink->TakeInvalidRect(); EXPECT_TRUE(invalidRect.isSome()); EXPECT_EQ(OrientedIntRect(0, row, 100, 1), invalidRect->mInputSpaceRect); EXPECT_EQ(OrientedIntRect(0, row, 100, 1), invalidRect->mOutputSpaceRect); CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, row + 1)); } // Write the final line, which should finish the surface. uint32_t count = 0; WriteState result = aSink->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(1u, count); // Note that the final invalid rect we expect here is only the last row; // that's because we called TakeInvalidRect() repeatedly in the loop above. AssertCorrectPipelineFinalState(aSink, IntRect(0, 99, 100, 1), IntRect(0, 99, 100, 1)); // Check that the generated image is correct. CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, 100)); // Attempt to write more and make sure that nothing gets written. count = 0; result = aSink->WritePixelBlocks( [&](uint32_t* aBlockStart, int32_t aLength) { count++; for (int32_t i = 0; i < aLength; ++i) { aBlockStart[i] = BGRAColor::Red().AsPixel(); } return std::make_tuple(aLength, Maybe()); }); EXPECT_EQ(WriteState::FINISHED, result); EXPECT_EQ(0u, count); EXPECT_TRUE(aSink->IsSurfaceFinished()); // Check that the generated image is still correct. CheckGeneratedImage(aDecoder, IntRect(20, 0, 60, 100)); }); } TEST(ImageSurfaceSink, SurfaceSinkProgressivePasses) { WithSurfaceSink( [](image::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->GetSourceSurface(); EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); } { ResetForNextPass(aSink); // Check that the generated image is still the first pass image. RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); RefPtr surface = currentFrame->GetSourceSurface(); 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->GetSourceSurface(); EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green())); } }); } TEST(ImageSurfaceSink, SurfaceSinkInvalidRect) { WithSurfaceSink([](image::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(OrientedIntRect(0, 0, 100, 1), invalidRect->mInputSpaceRect); EXPECT_EQ(OrientedIntRect(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(OrientedIntRect(0, 1, 100, 8), invalidRect->mInputSpaceRect); EXPECT_EQ(OrientedIntRect(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(OrientedIntRect(0, 9, 100, 1), invalidRect->mInputSpaceRect); EXPECT_EQ(OrientedIntRect(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(OrientedIntRect(0, 10, 100, 90), invalidRect->mInputSpaceRect); EXPECT_EQ(OrientedIntRect(0, 10, 100, 90), invalidRect->mOutputSpaceRect); // Check that the generated image is correct. RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); RefPtr surface = currentFrame->GetSourceSurface(); EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green())); } }); } TEST(ImageSurfaceSink, SurfaceSinkFlipVertically) { WithSurfaceSink([](image::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->GetSourceSurface(); EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Red())); } { ResetForNextPass(aSink); // Check that the generated image is still the first pass image. RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); RefPtr surface = currentFrame->GetSourceSurface(); 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(OrientedIntRect(0, 75, 100, 25), invalidRect->mInputSpaceRect); EXPECT_EQ(OrientedIntRect(0, 75, 100, 25), invalidRect->mOutputSpaceRect); // Check that the generated image is correct. RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef(); RefPtr surface = currentFrame->GetSourceSurface(); 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->GetSourceSurface(); EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green())); } }); }