gecko-dev/image/test/gtest/TestSurfaceSink.cpp
Andrew Osmond 42e6712830 Bug 1388332 - Fix a shutdown crash when destroying image decoders before initializing its SurfacePipe. r=tnikkel
A default constructed SurfacePipe contains a NullSurfaceSink as its
filter in mHead. This filter does nothing and is merely a placeholder.
Since most SurfacePipe objects are constructed with the default
constructor, and NullSurfaceSink has no (modified) state, we use a
singleton to represent it. Normally the SurfacePipe owns its filter, so
it needs to do a special check for NullSurfaceSink to ensure it doesn't
free it explicitly.

A Decoder object contains a default constructed SurfacePipe until it
needs to create the first frame from an image. This is a very brief
window because it does not take very long or much data to get to this
stage of decoding.

The NullSurfaceSink singleton is freed upon shutdown, however some
ISurfaceProvider objects may be lingering after this. If their Decoder
has yet to create the first frame, that means the SurfacePipe actually
contains a dangling pointer to the already freed singleton. To make
things worse, it actually tried to free the filter because it didn't
match the singleton (it got freed!).

As such, this change removes NullSurfaceSink entirely. We never use the
SurfacePipe before initializing it with a proper filter, and it would be
considered a programming error to do so. Instead let SurfacePipe::mHead
be null, and assert that it is not null when any operations are
performed on the SurfacePipe.
2017-08-09 06:54:55 -04:00

1427 lines
51 KiB
C++

/* -*- 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 <Orient Orientation, typename Func> void
WithSurfaceSink(Func aFunc)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
const bool flipVertically = Orientation == Orient::FLIP_VERTICALLY;
WithFilterPipeline(decoder, Forward<Func>(aFunc),
SurfaceConfig { decoder, 0, IntSize(100, 100),
SurfaceFormat::B8G8R8A8, flipVertically });
}
template <typename Func> void
WithPalettedSurfaceSink(const IntRect& aFrameRect, Func aFunc)
{
RefPtr<Decoder> decoder = CreateTrivialDecoder();
ASSERT_TRUE(decoder != nullptr);
WithFilterPipeline(decoder, Forward<Func>(aFunc),
PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
aFrameRect, SurfaceFormat::B8G8R8A8,
8, false });
}
void
ResetForNextPass(SurfaceFilter* aSink)
{
aSink->ResetToFirstRow();
EXPECT_FALSE(aSink->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
}
template <typename WriteFunc, typename CheckFunc> 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 <typename WriteFunc> void
CheckIterativeWrite(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);
});
}
template <typename WriteFunc> void
CheckPalettedIterativeWrite(Decoder* aDecoder,
PalettedSurfaceSink* 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, [&]{
CheckGeneratedPalettedImage(aDecoder, aOutputRect);
});
}
TEST(ImageSurfaceSink, SurfaceSinkInitialization)
{
WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
// Check initial state.
EXPECT_FALSE(aSink->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> 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<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
CheckWritePixels(aDecoder, aSink);
});
}
TEST(ImageSurfaceSink, SurfaceSinkWritePixelsFinish)
{
WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
// Write nothing into the surface; just finish immediately.
uint32_t count = 0;
auto result = aSink->WritePixels<uint32_t>([&]() {
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<uint32_t>([&]() {
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<SourceSurface> surface = currentFrame->GetSourceSurface();
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Transparent()));
});
}
TEST(ImageSurfaceSink, SurfaceSinkWritePixelsEarlyExit)
{
auto checkEarlyExit =
[](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<uint32_t>([&]() -> NextPixel<uint32_t> {
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<uint32_t>([&]() -> NextPixel<uint32_t> {
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<uint32_t>([&]{
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<Orient::NORMAL>([&](Decoder* aDecoder, SurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA);
});
WithSurfaceSink<Orient::NORMAL>([&](Decoder* aDecoder, SurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FAILURE);
});
WithSurfaceSink<Orient::NORMAL>([&](Decoder* aDecoder, SurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FINISHED);
});
}
TEST(ImageSurfaceSink, SurfaceSinkWritePixelsToRow)
{
WithSurfaceSink<Orient::NORMAL>([](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<uint32_t>([&]{
++count;
return AsVariant(BGRAColor::Green().AsPixel());
});
EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
EXPECT_EQ(100u, count);
EXPECT_FALSE(aSink->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mInputSpaceRect);
EXPECT_EQ(IntRect(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<uint32_t>([&]{
++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<uint32_t>([&]{
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 =
[](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<uint32_t>([&]() -> NextPixel<uint32_t> {
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<uint32_t>([&]{
++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<uint32_t>([&]{
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<Orient::NORMAL>([&](Decoder* aDecoder, SurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA);
});
WithSurfaceSink<Orient::NORMAL>([&](Decoder* aDecoder, SurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FAILURE);
});
WithSurfaceSink<Orient::NORMAL>([&](Decoder* aDecoder, SurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FINISHED);
});
}
TEST(ImageSurfaceSink, SurfaceSinkWriteBuffer)
{
WithSurfaceSink<Orient::NORMAL>([](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];
for (int i = 0; i < 100; ++i) {
buffer[i] = 20 <= i && i < 80 ? BGRAColor::Green().AsPixel()
: BGRAColor::Transparent().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<Orient::NORMAL>([](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<Orient::NORMAL>([](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<Orient::NORMAL>([](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<Orient::NORMAL>([](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<SurfaceInvalidRect> 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<Orient::NORMAL>([](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<uint32_t>([&]() -> NextPixel<uint32_t> {
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<SourceSurface> 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<Orient::NORMAL>([](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>([&](uint32_t* aRow,
uint32_t aLength) {
EXPECT_EQ(100u, aLength );
memcpy(aRow + 50, buffer, 50 * sizeof(uint32_t));
});
});
});
}
TEST(ImageSurfaceSink, SurfaceSinkProgressivePasses)
{
WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
{
// Fill the image with a first pass of red.
uint32_t count = 0;
auto result = aSink->WritePixels<uint32_t>([&]() {
++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<SourceSurface> 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<SourceSurface> 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<uint32_t>([&]() {
++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<SourceSurface> surface = currentFrame->GetSourceSurface();
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green()));
}
});
}
TEST(ImageSurfaceSink, SurfaceSinkInvalidRect)
{
WithSurfaceSink<Orient::NORMAL>([](Decoder* aDecoder, SurfaceSink* aSink) {
{
// Write one row.
uint32_t count = 0;
auto result = aSink->WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
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<SurfaceInvalidRect> 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<uint32_t>([&]() -> NextPixel<uint32_t> {
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<SurfaceInvalidRect> 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<uint32_t>([&]() -> NextPixel<uint32_t> {
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<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
}
{
// Write the right half of the same row.
uint32_t count = 0;
auto result = aSink->WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
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<SurfaceInvalidRect> 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<uint32_t>([&]() {
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<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
}
{
// Fill the rest of the image.
uint32_t count = 0;
auto result = aSink->WritePixels<uint32_t>([&]() {
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<SurfaceInvalidRect> 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<SourceSurface> surface = currentFrame->GetSourceSurface();
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green()));
}
});
}
TEST(ImageSurfaceSink, SurfaceSinkFlipVertically)
{
WithSurfaceSink<Orient::FLIP_VERTICALLY>([](Decoder* aDecoder,
SurfaceSink* aSink) {
{
// Fill the image with a first pass of red.
uint32_t count = 0;
auto result = aSink->WritePixels<uint32_t>([&]() {
++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<SourceSurface> 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<SourceSurface> 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<uint32_t>([&]() -> NextPixel<uint32_t> {
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<SurfaceInvalidRect> 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<SourceSurface> 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<uint32_t>([&]() {
++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<SourceSurface> surface = currentFrame->GetSourceSurface();
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green()));
}
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkInitialization)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
// Check initial state.
EXPECT_FALSE(aSink->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
// Check that the paletted image data is zero-initialized.
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
uint8_t* imageData = nullptr;
uint32_t imageLength = 0;
currentFrame->GetImageData(&imageData, &imageLength);
ASSERT_TRUE(imageData != nullptr);
ASSERT_EQ(100u * 100u, imageLength);
for (uint32_t i = 0; i < imageLength; ++i) {
ASSERT_EQ(uint8_t(0), imageData[i]);
}
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsFor0_0_100_100)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
CheckPalettedWritePixels(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, 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, 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, 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, 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, PalettedSurfaceSinkWritePixelsFinish)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
// Write nothing into the surface; just finish immediately.
uint32_t count = 0;
auto result = aSink->WritePixels<uint8_t>([&]{
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<uint8_t>([&]() {
count++;
return AsVariant(uint8_t(128));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(0u, count);
EXPECT_TRUE(aSink->IsSurfaceFinished());
// Check that the generated image is correct.
EXPECT_TRUE(IsSolidPalettedColor(aDecoder, 0));
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsEarlyExit)
{
auto checkEarlyExit =
[](Decoder* aDecoder, PalettedSurfaceSink* 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<uint8_t>([&]() -> NextPixel<uint8_t> {
if (count == 50) {
return AsVariant(aState);
}
return count++ < 50 ? AsVariant(uint8_t(255)) : AsVariant(uint8_t(128));
});
EXPECT_EQ(aState, result);
EXPECT_EQ(50u, count);
CheckGeneratedPalettedImage(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<uint8_t>([&]() -> NextPixel<uint8_t> {
if (count == 50) {
return AsVariant(WriteState::NEED_MORE_DATA);
}
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
EXPECT_EQ(50u, count);
EXPECT_FALSE(aSink->IsSurfaceFinished());
CheckGeneratedPalettedImage(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<uint8_t>([&]{
count++;
return AsVariant(uint8_t(128));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(0u, count);
EXPECT_TRUE(aSink->IsSurfaceFinished());
// Check that the generated image is still correct.
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1));
};
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA);
});
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FAILURE);
});
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FINISHED);
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsToRow)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* 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<uint8_t>([&]{
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
EXPECT_EQ(100u, count);
EXPECT_FALSE(aSink->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isSome());
EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mInputSpaceRect);
EXPECT_EQ(IntRect(0, row, 100, 1), invalidRect->mOutputSpaceRect);
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, row + 1));
}
// Write the final line, which should finish the surface.
uint32_t count = 0;
WriteState result = aSink->WritePixelsToRow<uint8_t>([&]{
++count;
return AsVariant(uint8_t(255));
});
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.
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 100));
// Attempt to write more and make sure that nothing gets written.
count = 0;
result = aSink->WritePixelsToRow<uint8_t>([&]{
count++;
return AsVariant(uint8_t(128));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(0u, count);
EXPECT_TRUE(aSink->IsSurfaceFinished());
// Check that the generated image is still correct.
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 100, 100));
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWritePixelsToRowEarlyExit)
{
auto checkEarlyExit =
[](Decoder* aDecoder, PalettedSurfaceSink* aSink, WriteState aState) {
// Write half a row of 255s and then exit early with |aState|. If the lambda
// keeps getting called, we'll write 128s, which will cause the test to
// fail.
uint32_t count = 0;
auto result = aSink->WritePixelsToRow<uint8_t>([&]() -> NextPixel<uint8_t> {
if (count == 50) {
return AsVariant(aState);
}
return count++ < 50 ? AsVariant(uint8_t(255))
: AsVariant(uint8_t(128));
});
EXPECT_EQ(aState, result);
EXPECT_EQ(50u, count);
CheckGeneratedPalettedImage(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<uint8_t>([&]{
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
EXPECT_EQ(50u, count);
EXPECT_FALSE(aSink->IsSurfaceFinished());
CheckGeneratedPalettedImage(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<uint8_t>([&]{
count++;
return AsVariant(uint8_t(128));
});
EXPECT_EQ(WriteState::FINISHED, result);
EXPECT_EQ(0u, count);
EXPECT_TRUE(aSink->IsSurfaceFinished());
// Check that the generated image is still correct.
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 50, 1));
};
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::NEED_MORE_DATA);
});
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FAILURE);
});
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[&](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
checkEarlyExit(aDecoder, aSink, WriteState::FINISHED);
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBuffer)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
// Create a buffer the same size as one row of the surface (which is 100x100),
// containing 60 pixels of 255 in the middle and 20 transparent pixels of 0 on
// either side.
uint8_t buffer[100];
for (int i = 0; i < 100; ++i) {
buffer[i] = 20 <= i && i < 80 ? 255 : 0;
}
// Write the buffer to every row of the surface and check that the generated
// image is correct.
CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(20, 0, 60, 100), [&]{
return aSink->WriteBuffer(buffer);
});
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferPartialRow)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
// Create a buffer the same size as one row of the surface, containing all
// 255 pixels.
uint8_t buffer[100];
for (int i = 0; i < 100; ++i) {
buffer[i] = 255;
}
// Write the buffer to the middle 60 pixels of every row of the surface and
// check that the generated image is correct.
CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(20, 0, 60, 100), [&]{
return aSink->WriteBuffer(buffer, 20, 60);
});
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferPartialRowStartColOverflow)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
// Create a buffer the same size as one row of the surface, containing all
// 255 pixels.
uint8_t buffer[100];
for (int i = 0; i < 100; ++i) {
buffer[i] = 255;
}
{
// 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 0.
CheckPalettedIterativeWrite(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
// 0 and the right half is 255.
CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&]{
return aSink->WriteBuffer(buffer, 50, 100);
});
}
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferPartialRowBufferOverflow)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* 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 255 pixels,
// while the second half will contain 128 pixels.
uint8_t buffer[200];
for (int i = 0; i < 200; ++i) {
buffer[i] = i < 100 ? 255 : 128;
}
{
// 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 255 since the
// pixels on the right side of the buffer shouldn't have been written to
// the surface.
CheckPalettedIterativeWrite(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 0, 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 0 on the left side and 255 on the right.
CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&]{
return aSink->WriteBuffer(buffer, 50, 200);
});
}
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteBufferFromNullSource)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
// Calling WriteBuffer() with a null pointer should fail without making any
// changes to the surface.
uint8_t* nullBuffer = nullptr;
WriteState result = aSink->WriteBuffer(nullBuffer);
EXPECT_EQ(WriteState::FAILURE, result);
EXPECT_FALSE(aSink->IsSurfaceFinished());
Maybe<SurfaceInvalidRect> invalidRect = aSink->TakeInvalidRect();
EXPECT_TRUE(invalidRect.isNothing());
// Check that nothing got written to the surface.
CheckGeneratedPalettedImage(aDecoder, IntRect(0, 0, 0, 0));
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteEmptyRow)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
{
// Write an empty row to each row of the surface. We check that the
// generated image is entirely 0.
CheckPalettedIterativeWrite(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 0, 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<uint8_t>([&]() -> NextPixel<uint8_t> {
if (count == 50) {
return AsVariant(WriteState::NEED_MORE_DATA);
}
++count;
return AsVariant(uint8_t(255));
});
EXPECT_EQ(WriteState::NEED_MORE_DATA, result);
EXPECT_EQ(50u, count);
EXPECT_FALSE(aSink->IsSurfaceFinished());
CheckPalettedIterativeWrite(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
// 255 pixels.
uint8_t buffer[100];
for (int i = 0; i < 100; ++i) {
buffer[i] = 255;
}
// Write an empty row to the middle 60 rows of the surface. The first 20
// and last 20 rows will be 255. (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 = [&]{
EXPECT_TRUE(PalettedRowsAreSolidColor(aDecoder, 0, 20, 255));
EXPECT_TRUE(PalettedRowsAreSolidColor(aDecoder, 20, 60, 0));
EXPECT_TRUE(PalettedRowsAreSolidColor(aDecoder, 80, 20, 255));
};
DoCheckIterativeWrite(aSink, writeFunc, checkFunc);
}
});
}
TEST(ImageSurfaceSink, PalettedSurfaceSinkWriteUnsafeComputedRow)
{
WithPalettedSurfaceSink(IntRect(0, 0, 100, 100),
[](Decoder* aDecoder, PalettedSurfaceSink* aSink) {
// Create an all-255 buffer the same size as one row of the surface.
uint8_t buffer[100];
for (int i = 0; i < 100; ++i) {
buffer[i] = 255;
}
// 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 0 and the right side is 255.
CheckPalettedIterativeWrite(aDecoder, aSink, IntRect(50, 0, 50, 100), [&]{
return aSink->WriteUnsafeComputedRow<uint8_t>([&](uint8_t* aRow,
uint32_t aLength) {
EXPECT_EQ(100u, aLength );
memcpy(aRow + 50, buffer, 50 * sizeof(uint8_t));
});
});
});
}