Issue #439 - Remove, fix and clean up automated tests
With the big amount of code churn around DOM a lot of tests broke severely enough that they caused build bustage. This commit cleans up, removes or otherwise fixes tests that are broken, no longer relevant or obsolete.
|
|
@ -5,8 +5,6 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += ['build', 'decoders', 'encoders']
|
||||
if CONFIG['ENABLE_TESTS']:
|
||||
DIRS += ['test/gtest']
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Core', 'ImageLib')
|
||||
|
|
|
|||
|
|
@ -1,673 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsIDirectoryService.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIProperties.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
using namespace gfx;
|
||||
|
||||
using std::abs;
|
||||
using std::vector;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// General Helpers
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// These macros work like gtest's ASSERT_* macros, except that they can be used
|
||||
// in functions that return values.
|
||||
#define ASSERT_TRUE_OR_RETURN(e, rv) \
|
||||
EXPECT_TRUE(e); \
|
||||
if (!(e)) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
#define ASSERT_EQ_OR_RETURN(a, b, rv) \
|
||||
EXPECT_EQ(a, b); \
|
||||
if ((a) != (b)) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
#define ASSERT_GE_OR_RETURN(a, b, rv) \
|
||||
EXPECT_GE(a, b); \
|
||||
if (!((a) >= (b))) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
#define ASSERT_LE_OR_RETURN(a, b, rv) \
|
||||
EXPECT_LE(a, b); \
|
||||
if (!((a) <= (b))) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
#define ASSERT_LT_OR_RETURN(a, b, rv) \
|
||||
EXPECT_LT(a, b); \
|
||||
if (!((a) < (b))) { \
|
||||
return rv; \
|
||||
}
|
||||
|
||||
already_AddRefed<nsIInputStream>
|
||||
LoadFile(const char* aRelativePath)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
nsCOMPtr<nsIProperties> dirService =
|
||||
do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
|
||||
ASSERT_TRUE_OR_RETURN(dirService != nullptr, nullptr);
|
||||
|
||||
// Retrieve the current working directory.
|
||||
nsCOMPtr<nsIFile> file;
|
||||
rv = dirService->Get(NS_OS_CURRENT_WORKING_DIR,
|
||||
NS_GET_IID(nsIFile), getter_AddRefs(file));
|
||||
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
|
||||
|
||||
// Construct the final path by appending the working path to the current
|
||||
// working directory.
|
||||
file->AppendNative(nsDependentCString(aRelativePath));
|
||||
|
||||
// Construct an input stream for the requested file.
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file);
|
||||
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
|
||||
|
||||
// Ensure the resulting input stream is buffered.
|
||||
if (!NS_InputStreamIsBuffered(inputStream)) {
|
||||
nsCOMPtr<nsIInputStream> bufStream;
|
||||
rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
|
||||
inputStream, 1024);
|
||||
ASSERT_TRUE_OR_RETURN(NS_SUCCEEDED(rv), nullptr);
|
||||
inputStream = bufStream;
|
||||
}
|
||||
|
||||
return inputStream.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
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
|
||||
IsSolidPalettedColor(Decoder* aDecoder, uint8_t aColor)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
return PalettedRectIsSolidColor(aDecoder, currentFrame->GetRect(), aColor);
|
||||
}
|
||||
|
||||
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
|
||||
PalettedRowsAreSolidColor(Decoder* aDecoder,
|
||||
int32_t aStartRow,
|
||||
int32_t aRowCount,
|
||||
uint8_t aColor)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
IntRect frameRect = currentFrame->GetRect();
|
||||
IntRect solidColorRect(frameRect.x, aStartRow, frameRect.width, aRowCount);
|
||||
return PalettedRectIsSolidColor(aDecoder, solidColorRect, aColor);
|
||||
}
|
||||
|
||||
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<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
|
||||
ASSERT_TRUE_OR_RETURN(dataSurface != nullptr, false);
|
||||
|
||||
ASSERT_EQ_OR_RETURN(dataSurface->Stride(), surfaceSize.width * 4, false);
|
||||
|
||||
DataSourceSurface::ScopedMap mapping(dataSurface,
|
||||
DataSourceSurface::MapType::READ);
|
||||
ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
|
||||
|
||||
uint8_t* data = dataSurface->GetData();
|
||||
ASSERT_TRUE_OR_RETURN(data != nullptr, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PalettedRectIsSolidColor(Decoder* aDecoder, const IntRect& aRect, uint8_t aColor)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
uint8_t* imageData;
|
||||
uint32_t imageLength;
|
||||
currentFrame->GetImageData(&imageData, &imageLength);
|
||||
ASSERT_TRUE_OR_RETURN(imageData, false);
|
||||
|
||||
// Clamp to the frame rect. If any pixels outside the frame rect are included,
|
||||
// we immediately fail, because such pixels don't have any "color" in the
|
||||
// sense this function measures - they're transparent, and that doesn't
|
||||
// necessarily correspond to any color palette index at all.
|
||||
IntRect frameRect = currentFrame->GetRect();
|
||||
ASSERT_EQ_OR_RETURN(imageLength, uint32_t(frameRect.Area()), false);
|
||||
IntRect rect = aRect.Intersect(frameRect);
|
||||
ASSERT_EQ_OR_RETURN(rect.Area(), aRect.Area(), false);
|
||||
|
||||
// Translate |rect| by |frameRect.TopLeft()| to reflect the fact that the
|
||||
// frame rect's offset doesn't actually mean anything in terms of the
|
||||
// in-memory representation of the surface. The image data starts at the upper
|
||||
// left corner of the frame rect, in other words.
|
||||
rect -= frameRect.TopLeft();
|
||||
|
||||
// Walk through the image data and make sure that the entire rect has the
|
||||
// palette index |aColor|.
|
||||
int32_t rowLength = frameRect.width;
|
||||
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;
|
||||
ASSERT_EQ_OR_RETURN(aColor, imageData[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
RowHasPixels(SourceSurface* aSurface,
|
||||
int32_t aRow,
|
||||
const vector<BGRAColor>& aPixels)
|
||||
{
|
||||
ASSERT_GE_OR_RETURN(aRow, 0, false);
|
||||
|
||||
IntSize surfaceSize = aSurface->GetSize();
|
||||
ASSERT_EQ_OR_RETURN(aPixels.size(), size_t(surfaceSize.width), false);
|
||||
ASSERT_LT_OR_RETURN(aRow, surfaceSize.height, false);
|
||||
|
||||
RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
|
||||
ASSERT_TRUE_OR_RETURN(dataSurface, false);
|
||||
|
||||
ASSERT_EQ_OR_RETURN(dataSurface->Stride(), surfaceSize.width * 4, false);
|
||||
|
||||
DataSourceSurface::ScopedMap mapping(dataSurface,
|
||||
DataSourceSurface::MapType::READ);
|
||||
ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
|
||||
|
||||
uint8_t* data = dataSurface->GetData();
|
||||
ASSERT_TRUE_OR_RETURN(data != nullptr, false);
|
||||
|
||||
int32_t rowLength = dataSurface->Stride();
|
||||
for (int32_t col = 0; col < surfaceSize.width; ++col) {
|
||||
int32_t i = aRow * rowLength + col * 4;
|
||||
ASSERT_EQ_OR_RETURN(aPixels[col].mBlue, data[i + 0], false);
|
||||
ASSERT_EQ_OR_RETURN(aPixels[col].mGreen, data[i + 1], false);
|
||||
ASSERT_EQ_OR_RETURN(aPixels[col].mRed, data[i + 2], false);
|
||||
ASSERT_EQ_OR_RETURN(aPixels[col].mAlpha, data[i + 3], false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SurfacePipe Helpers
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
already_AddRefed<Decoder>
|
||||
CreateTrivialDecoder()
|
||||
{
|
||||
gfxPrefs::GetSingleton();
|
||||
DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif");
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
|
||||
DefaultSurfaceFlags());
|
||||
return decoder.forget();
|
||||
}
|
||||
|
||||
void
|
||||
AssertCorrectPipelineFinalState(SurfaceFilter* aFilter,
|
||||
const gfx::IntRect& aInputSpaceRect,
|
||||
const gfx::IntRect& aOutputSpaceRect)
|
||||
{
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> 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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
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));
|
||||
}
|
||||
|
||||
void
|
||||
CheckGeneratedPalettedImage(Decoder* aDecoder, const IntRect& aRect)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
IntSize imageSize = currentFrame->GetImageSize();
|
||||
|
||||
// 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 all 255's. (Region 'C'.)
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder, aRect, 255));
|
||||
|
||||
// Check that the area above the output rect is all 0's. (Region 'A'.)
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(0, 0, imageSize.width, aRect.y),
|
||||
0));
|
||||
|
||||
// Check that the area to the left of the output rect is all 0's. (Region 'B'.)
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(0, aRect.y, aRect.x, aRect.YMost()),
|
||||
0));
|
||||
|
||||
// Check that the area to the right of the output rect is all 0's. (Region 'D'.)
|
||||
const int32_t widthOnRight = imageSize.width - aRect.XMost();
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(aRect.XMost(), aRect.y, widthOnRight, aRect.YMost()),
|
||||
0));
|
||||
|
||||
// Check that the area below the output rect is transparent. (Region 'E'.)
|
||||
const int32_t heightBelow = imageSize.height - aRect.YMost();
|
||||
EXPECT_TRUE(PalettedRectIsSolidColor(aDecoder,
|
||||
IntRect(0, aRect.YMost(), imageSize.width, heightBelow),
|
||||
0));
|
||||
}
|
||||
|
||||
void
|
||||
CheckWritePixels(Decoder* aDecoder,
|
||||
SurfaceFilter* aFilter,
|
||||
Maybe<IntRect> aOutputRect /* = Nothing() */,
|
||||
Maybe<IntRect> aInputRect /* = Nothing() */,
|
||||
Maybe<IntRect> aInputWriteRect /* = Nothing() */,
|
||||
Maybe<IntRect> aOutputWriteRect /* = Nothing() */,
|
||||
uint8_t aFuzz /* = 0 */)
|
||||
{
|
||||
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 = aFilter->WritePixels<uint32_t>([&] {
|
||||
++count;
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
});
|
||||
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 = aFilter->WritePixels<uint32_t>([&] {
|
||||
++count;
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
});
|
||||
EXPECT_EQ(oldCount, count);
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> 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
|
||||
CheckPalettedWritePixels(Decoder* aDecoder,
|
||||
SurfaceFilter* aFilter,
|
||||
Maybe<IntRect> aOutputRect /* = Nothing() */,
|
||||
Maybe<IntRect> aInputRect /* = Nothing() */,
|
||||
Maybe<IntRect> aInputWriteRect /* = Nothing() */,
|
||||
Maybe<IntRect> aOutputWriteRect /* = Nothing() */,
|
||||
uint8_t aFuzz /* = 0 */)
|
||||
{
|
||||
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 = aFilter->WritePixels<uint8_t>([&] {
|
||||
++count;
|
||||
return AsVariant(uint8_t(255));
|
||||
});
|
||||
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 = aFilter->WritePixels<uint8_t>([&] {
|
||||
++count;
|
||||
return AsVariant(uint8_t(255));
|
||||
});
|
||||
EXPECT_EQ(oldCount, count);
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_TRUE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> 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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Test Data
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ImageTestCase GreenPNGTestCase()
|
||||
{
|
||||
return ImageTestCase("green.png", "image/png", IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase GreenGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("green.gif", "image/gif", IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase GreenJPGTestCase()
|
||||
{
|
||||
return ImageTestCase("green.jpg", "image/jpeg", IntSize(100, 100),
|
||||
TEST_CASE_IS_FUZZY);
|
||||
}
|
||||
|
||||
ImageTestCase GreenBMPTestCase()
|
||||
{
|
||||
return ImageTestCase("green.bmp", "image/bmp", IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase GreenICOTestCase()
|
||||
{
|
||||
// This ICO contains a 32-bit BMP, and we use a BMP's alpha data by default
|
||||
// when the BMP is embedded in an ICO, so it's transparent.
|
||||
return ImageTestCase("green.ico", "image/x-icon", IntSize(100, 100),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase GreenIconTestCase()
|
||||
{
|
||||
return ImageTestCase("green.icon", "image/icon", IntSize(100, 100),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase GreenFirstFrameAnimatedGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("first-frame-green.gif", "image/gif", IntSize(100, 100),
|
||||
TEST_CASE_IS_ANIMATED);
|
||||
}
|
||||
|
||||
ImageTestCase GreenFirstFrameAnimatedPNGTestCase()
|
||||
{
|
||||
return ImageTestCase("first-frame-green.png", "image/png", IntSize(100, 100),
|
||||
TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED);
|
||||
}
|
||||
|
||||
ImageTestCase CorruptTestCase()
|
||||
{
|
||||
return ImageTestCase("corrupt.jpg", "image/jpeg", IntSize(100, 100),
|
||||
TEST_CASE_HAS_ERROR);
|
||||
}
|
||||
|
||||
ImageTestCase CorruptBMPWithTruncatedHeader()
|
||||
{
|
||||
// This BMP has a header which is truncated right between the BIH and the
|
||||
// bitfields, which is a particularly error-prone place w.r.t. the BMP decoder
|
||||
// state machine.
|
||||
return ImageTestCase("invalid-truncated-metadata.bmp", "image/bmp",
|
||||
IntSize(100, 100), TEST_CASE_HAS_ERROR);
|
||||
}
|
||||
|
||||
ImageTestCase CorruptICOWithBadBMPWidthTestCase()
|
||||
{
|
||||
// This ICO contains a BMP icon which has a width that doesn't match the size
|
||||
// listed in the corresponding ICO directory entry.
|
||||
return ImageTestCase("corrupt-with-bad-bmp-width.ico", "image/x-icon",
|
||||
IntSize(100, 100), TEST_CASE_HAS_ERROR);
|
||||
}
|
||||
|
||||
ImageTestCase CorruptICOWithBadBMPHeightTestCase()
|
||||
{
|
||||
// This ICO contains a BMP icon which has a height that doesn't match the size
|
||||
// listed in the corresponding ICO directory entry.
|
||||
return ImageTestCase("corrupt-with-bad-bmp-height.ico", "image/x-icon",
|
||||
IntSize(100, 100), TEST_CASE_HAS_ERROR);
|
||||
}
|
||||
|
||||
ImageTestCase TransparentPNGTestCase()
|
||||
{
|
||||
return ImageTestCase("transparent.png", "image/png", IntSize(32, 32),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase TransparentGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase FirstFramePaddingGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("transparent.gif", "image/gif", IntSize(16, 16),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase TransparentIfWithinICOBMPTestCase(TestCaseFlags aFlags)
|
||||
{
|
||||
// This is a BMP that is only transparent when decoded as if it is within an
|
||||
// ICO file. (Note: aFlags needs to be set to TEST_CASE_DEFAULT_FLAGS or
|
||||
// TEST_CASE_IS_TRANSPARENT accordingly.)
|
||||
return ImageTestCase("transparent-if-within-ico.bmp", "image/bmp",
|
||||
IntSize(32, 32), aFlags);
|
||||
}
|
||||
|
||||
ImageTestCase RLE4BMPTestCase()
|
||||
{
|
||||
return ImageTestCase("rle4.bmp", "image/bmp", IntSize(320, 240),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase RLE8BMPTestCase()
|
||||
{
|
||||
return ImageTestCase("rle8.bmp", "image/bmp", IntSize(32, 32),
|
||||
TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase NoFrameDelayGIFTestCase()
|
||||
{
|
||||
// This is an invalid (or at least, questionably valid) GIF that's animated
|
||||
// even though it specifies a frame delay of zero. It's animated, but it's not
|
||||
// marked TEST_CASE_IS_ANIMATED because the metadata decoder can't detect that
|
||||
// it's animated.
|
||||
return ImageTestCase("no-frame-delay.gif", "image/gif", IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase()
|
||||
{
|
||||
// This is a corrupt GIF that has extra image sub blocks between the first and
|
||||
// second frame.
|
||||
return ImageTestCase("animated-with-extra-image-sub-blocks.gif", "image/gif",
|
||||
IntSize(100, 100));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledPNGTestCase()
|
||||
{
|
||||
// This testcase (and all the other "downscaled") testcases) 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. It's intended that tests downscale it
|
||||
// from 100x100 to 20x20, so we specify a 20x20 output size.
|
||||
return ImageTestCase("downscaled.png", "image/png", IntSize(100, 100),
|
||||
IntSize(20, 20));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.gif", "image/gif", IntSize(100, 100),
|
||||
IntSize(20, 20));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledJPGTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.jpg", "image/jpeg", IntSize(100, 100),
|
||||
IntSize(20, 20));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledBMPTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.bmp", "image/bmp", IntSize(100, 100),
|
||||
IntSize(20, 20));
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledICOTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.ico", "image/x-icon", IntSize(100, 100),
|
||||
IntSize(20, 20), TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledIconTestCase()
|
||||
{
|
||||
return ImageTestCase("downscaled.icon", "image/icon", IntSize(100, 100),
|
||||
IntSize(20, 20), TEST_CASE_IS_TRANSPARENT);
|
||||
}
|
||||
|
||||
ImageTestCase DownscaledTransparentICOWithANDMaskTestCase()
|
||||
{
|
||||
// This test case is an ICO with AND mask transparency. We want to ensure that
|
||||
// we can downscale it without crashing or triggering ASAN failures, but its
|
||||
// content isn't simple to verify, so for now we don't check the output.
|
||||
return ImageTestCase("transparent-ico-with-and-mask.ico", "image/x-icon",
|
||||
IntSize(32, 32), IntSize(20, 20),
|
||||
TEST_CASE_IS_TRANSPARENT | TEST_CASE_IGNORE_OUTPUT);
|
||||
}
|
||||
|
||||
ImageTestCase TruncatedSmallGIFTestCase()
|
||||
{
|
||||
return ImageTestCase("green-1x1-truncated.gif", "image/gif", IntSize(1, 1));
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
|
@ -1,419 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_image_test_gtest_Common_h
|
||||
#define mozilla_image_test_gtest_Common_h
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "Decoder.h"
|
||||
#include "gfxColor.h"
|
||||
#include "imgITools.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "SurfacePipe.h"
|
||||
#include "SurfacePipeFactory.h"
|
||||
|
||||
class nsIInputStream;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Types
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum TestCaseFlags
|
||||
{
|
||||
TEST_CASE_DEFAULT_FLAGS = 0,
|
||||
TEST_CASE_IS_FUZZY = 1 << 0,
|
||||
TEST_CASE_HAS_ERROR = 1 << 1,
|
||||
TEST_CASE_IS_TRANSPARENT = 1 << 2,
|
||||
TEST_CASE_IS_ANIMATED = 1 << 3,
|
||||
TEST_CASE_IGNORE_OUTPUT = 1 << 4,
|
||||
};
|
||||
|
||||
struct ImageTestCase
|
||||
{
|
||||
ImageTestCase(const char* aPath,
|
||||
const char* aMimeType,
|
||||
gfx::IntSize aSize,
|
||||
uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS)
|
||||
: mPath(aPath)
|
||||
, mMimeType(aMimeType)
|
||||
, mSize(aSize)
|
||||
, mOutputSize(aSize)
|
||||
, mFlags(aFlags)
|
||||
{ }
|
||||
|
||||
ImageTestCase(const char* aPath,
|
||||
const char* aMimeType,
|
||||
gfx::IntSize aSize,
|
||||
gfx::IntSize aOutputSize,
|
||||
uint32_t aFlags = TEST_CASE_DEFAULT_FLAGS)
|
||||
: mPath(aPath)
|
||||
, mMimeType(aMimeType)
|
||||
, mSize(aSize)
|
||||
, mOutputSize(aOutputSize)
|
||||
, mFlags(aFlags)
|
||||
{ }
|
||||
|
||||
const char* mPath;
|
||||
const char* mMimeType;
|
||||
gfx::IntSize mSize;
|
||||
gfx::IntSize mOutputSize;
|
||||
uint32_t mFlags;
|
||||
};
|
||||
|
||||
struct BGRAColor
|
||||
{
|
||||
BGRAColor() : BGRAColor(0, 0, 0, 0) { }
|
||||
|
||||
BGRAColor(uint8_t aBlue, uint8_t aGreen, uint8_t aRed, uint8_t aAlpha)
|
||||
: mBlue(aBlue)
|
||||
, mGreen(aGreen)
|
||||
, mRed(aRed)
|
||||
, mAlpha(aAlpha)
|
||||
{ }
|
||||
|
||||
static BGRAColor Green() { return BGRAColor(0x00, 0xFF, 0x00, 0xFF); }
|
||||
static BGRAColor Red() { return BGRAColor(0x00, 0x00, 0xFF, 0xFF); }
|
||||
static BGRAColor Blue() { return BGRAColor(0xFF, 0x00, 0x00, 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;
|
||||
uint8_t mRed;
|
||||
uint8_t mAlpha;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// General Helpers
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* A RAII class that ensure that ImageLib services are available. Any tests that
|
||||
* require ImageLib to be initialized (for example, any test that uses the
|
||||
* SurfaceCache; see image::EnsureModuleInitialized() for the full list) can
|
||||
* use this class to ensure that ImageLib services are available. Failure to do
|
||||
* so can result in strange, non-deterministic failures.
|
||||
*/
|
||||
struct AutoInitializeImageLib
|
||||
{
|
||||
AutoInitializeImageLib()
|
||||
{
|
||||
// Ensure that ImageLib services are initialized.
|
||||
nsCOMPtr<imgITools> imgTools = do_CreateInstance("@mozilla.org/image/tools;1");
|
||||
EXPECT_TRUE(imgTools != nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
/// Loads a file from the current directory. @return an nsIInputStream for it.
|
||||
already_AddRefed<nsIInputStream> LoadFile(const char* aRelativePath);
|
||||
|
||||
/**
|
||||
* @returns true if every pixel 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 IsSolidColor(gfx::SourceSurface* aSurface,
|
||||
BGRAColor aColor,
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
/**
|
||||
* @returns true if every pixel of @aDecoder's surface has the palette index
|
||||
* specified by @aColor.
|
||||
*/
|
||||
bool IsSolidPalettedColor(Decoder* aDecoder, uint8_t aColor);
|
||||
|
||||
/**
|
||||
* @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 range of rows specified by @aStartRow and
|
||||
* @aRowCount of @aDecoder's surface has the palette index specified by @aColor.
|
||||
*/
|
||||
bool PalettedRowsAreSolidColor(Decoder* aDecoder,
|
||||
int32_t aStartRow,
|
||||
int32_t aRowCount,
|
||||
uint8_t aColor);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @returns true if every pixel in the rect specified by @aRect has the palette
|
||||
* index specified by @aColor.
|
||||
*/
|
||||
bool PalettedRectIsSolidColor(Decoder* aDecoder,
|
||||
const gfx::IntRect& aRect,
|
||||
uint8_t aColor);
|
||||
|
||||
/**
|
||||
* @returns true if the pixels in @aRow of @aSurface match the pixels given in
|
||||
* @aPixels.
|
||||
*/
|
||||
bool RowHasPixels(gfx::SourceSurface* aSurface,
|
||||
int32_t aRow,
|
||||
const std::vector<BGRAColor>& aPixels);
|
||||
|
||||
// ExpectNoResume is an IResumable implementation for use by tests that expect
|
||||
// Resume() to never get called.
|
||||
class ExpectNoResume final : public IResumable
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ExpectNoResume, override)
|
||||
|
||||
void Resume() override { FAIL() << "Resume() should not get called"; }
|
||||
|
||||
private:
|
||||
~ExpectNoResume() override { }
|
||||
};
|
||||
|
||||
// CountResumes is an IResumable implementation for use by tests that expect
|
||||
// Resume() to get called a certain number of times.
|
||||
class CountResumes : public IResumable
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CountResumes, override)
|
||||
|
||||
CountResumes() : mCount(0) { }
|
||||
|
||||
void Resume() override { mCount++; }
|
||||
uint32_t Count() const { return mCount; }
|
||||
|
||||
private:
|
||||
~CountResumes() override { }
|
||||
|
||||
uint32_t mCount;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// 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<Decoder> 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 <typename Func, typename... Configs>
|
||||
void WithFilterPipeline(Decoder* aDecoder, Func aFunc, const Configs&... aConfigs)
|
||||
{
|
||||
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::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 <typename... Configs>
|
||||
void AssertConfiguringPipelineFails(Decoder* aDecoder, const Configs&... aConfigs)
|
||||
{
|
||||
auto pipe = MakeUnique<typename detail::FilterPipeline<Configs...>::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.
|
||||
* @param aFuzz The amount of fuzz to use in pixel comparisons.
|
||||
*/
|
||||
void CheckGeneratedImage(Decoder* aDecoder,
|
||||
const gfx::IntRect& aRect,
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
/**
|
||||
* Checks a generated paletted 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 have a palette index of 255, while pixels outside
|
||||
* this region have a palette index of 0.
|
||||
*/
|
||||
void CheckGeneratedPalettedImage(Decoder* aDecoder, const gfx::IntRect& aRect);
|
||||
|
||||
/**
|
||||
* 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<gfx::IntRect> aOutputRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aInputRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aInputWriteRect = Nothing(),
|
||||
Maybe<gfx::IntRect> 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<gfx::IntRect> aOutputRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aInputRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aInputWriteRect = Nothing(),
|
||||
Maybe<gfx::IntRect> aOutputWriteRect = Nothing(),
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Test Data
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ImageTestCase GreenPNGTestCase();
|
||||
ImageTestCase GreenGIFTestCase();
|
||||
ImageTestCase GreenJPGTestCase();
|
||||
ImageTestCase GreenBMPTestCase();
|
||||
ImageTestCase GreenICOTestCase();
|
||||
ImageTestCase GreenIconTestCase();
|
||||
|
||||
ImageTestCase GreenFirstFrameAnimatedGIFTestCase();
|
||||
ImageTestCase GreenFirstFrameAnimatedPNGTestCase();
|
||||
|
||||
ImageTestCase CorruptTestCase();
|
||||
ImageTestCase CorruptBMPWithTruncatedHeader();
|
||||
ImageTestCase CorruptICOWithBadBMPWidthTestCase();
|
||||
ImageTestCase CorruptICOWithBadBMPHeightTestCase();
|
||||
|
||||
ImageTestCase TransparentPNGTestCase();
|
||||
ImageTestCase TransparentGIFTestCase();
|
||||
ImageTestCase FirstFramePaddingGIFTestCase();
|
||||
ImageTestCase NoFrameDelayGIFTestCase();
|
||||
ImageTestCase ExtraImageSubBlocksAnimatedGIFTestCase();
|
||||
|
||||
ImageTestCase TransparentBMPWhenBMPAlphaEnabledTestCase();
|
||||
ImageTestCase RLE4BMPTestCase();
|
||||
ImageTestCase RLE8BMPTestCase();
|
||||
|
||||
ImageTestCase DownscaledPNGTestCase();
|
||||
ImageTestCase DownscaledGIFTestCase();
|
||||
ImageTestCase DownscaledJPGTestCase();
|
||||
ImageTestCase DownscaledBMPTestCase();
|
||||
ImageTestCase DownscaledICOTestCase();
|
||||
ImageTestCase DownscaledIconTestCase();
|
||||
ImageTestCase DownscaledTransparentICOWithANDMaskTestCase();
|
||||
|
||||
ImageTestCase TruncatedSmallGIFTestCase();
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_test_gtest_Common_h
|
||||
|
|
@ -1,671 +0,0 @@
|
|||
/* -*- 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 <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/Maybe.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;
|
||||
|
||||
using std::generate;
|
||||
using std::vector;
|
||||
|
||||
template <typename Func> void
|
||||
WithADAM7InterpolatingFilter(const IntSize& aSize, Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(bool(decoder));
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
ADAM7InterpolatingConfig { },
|
||||
SurfaceConfig { decoder, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
void
|
||||
AssertConfiguringADAM7InterpolatingFilterFails(const IntSize& aSize)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(bool(decoder));
|
||||
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
ADAM7InterpolatingConfig { },
|
||||
SurfaceConfig { decoder, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
uint8_t
|
||||
InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight)
|
||||
{
|
||||
return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight));
|
||||
}
|
||||
|
||||
BGRAColor
|
||||
InterpolateColors(BGRAColor aColor1, BGRAColor aColor2, float aWeight)
|
||||
{
|
||||
return BGRAColor(InterpolateByte(aColor1.mBlue, aColor2.mBlue, aWeight),
|
||||
InterpolateByte(aColor1.mGreen, aColor2.mGreen, aWeight),
|
||||
InterpolateByte(aColor1.mRed, aColor2.mRed, aWeight),
|
||||
InterpolateByte(aColor1.mAlpha, aColor2.mAlpha, aWeight));
|
||||
}
|
||||
|
||||
enum class ShouldInterpolate
|
||||
{
|
||||
eYes,
|
||||
eNo
|
||||
};
|
||||
|
||||
BGRAColor
|
||||
HorizontallyInterpolatedPixel(uint32_t aCol,
|
||||
uint32_t aWidth,
|
||||
const vector<float>& aWeights,
|
||||
ShouldInterpolate aShouldInterpolate,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
// We cycle through the vector of weights forever.
|
||||
float weight = aWeights[aCol % aWeights.size()];
|
||||
|
||||
// Find the columns of the two final pixels for this set of weights.
|
||||
uint32_t finalPixel1 = aCol - aCol % aWeights.size();
|
||||
uint32_t finalPixel2 = finalPixel1 + aWeights.size();
|
||||
|
||||
// If |finalPixel2| is past the end of the row, that means that there is no
|
||||
// final pixel after the pixel at |finalPixel1|. In that case, we just want to
|
||||
// duplicate |finalPixel1|'s color until the end of the row. We can do that by
|
||||
// setting |finalPixel2| equal to |finalPixel1| so that the interpolation has
|
||||
// no effect.
|
||||
if (finalPixel2 >= aWidth) {
|
||||
finalPixel2 = finalPixel1;
|
||||
}
|
||||
|
||||
// We cycle through the vector of colors forever (subject to the above
|
||||
// constraint about the end of the row).
|
||||
BGRAColor color1 = aColors[finalPixel1 % aColors.size()];
|
||||
BGRAColor color2 = aColors[finalPixel2 % aColors.size()];
|
||||
|
||||
// If we're not interpolating, we treat all pixels which aren't final as
|
||||
// transparent. Since the number of weights we have is equal to the stride
|
||||
// between final pixels, we can check if |aCol| is a final pixel by checking
|
||||
// whether |aCol| is a multiple of |aWeights.size()|.
|
||||
if (aShouldInterpolate == ShouldInterpolate::eNo) {
|
||||
return aCol % aWeights.size() == 0 ? color1
|
||||
: BGRAColor::Transparent();
|
||||
}
|
||||
|
||||
// Interpolate.
|
||||
return InterpolateColors(color1, color2, weight);
|
||||
}
|
||||
|
||||
vector<float>&
|
||||
InterpolationWeights(int32_t aStride)
|
||||
{
|
||||
// Precalculated interpolation weights. These are used to interpolate
|
||||
// between final pixels or between important rows. Although no interpolation
|
||||
// is actually applied to the previous final pixel or important row value,
|
||||
// the arrays still start with 1.0f, which is always skipped, primarily
|
||||
// because otherwise |stride1Weights| would have zero elements.
|
||||
static vector<float> stride8Weights =
|
||||
{ 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f };
|
||||
static vector<float> stride4Weights = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f };
|
||||
static vector<float> stride2Weights = { 1.0f, 1 / 2.0f };
|
||||
static vector<float> stride1Weights = { 1.0f };
|
||||
|
||||
switch (aStride) {
|
||||
case 8: return stride8Weights;
|
||||
case 4: return stride4Weights;
|
||||
case 2: return stride2Weights;
|
||||
case 1: return stride1Weights;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
int32_t
|
||||
ImportantRowStride(uint8_t aPass)
|
||||
{
|
||||
// The stride between important rows for each pass, with a dummy value for
|
||||
// the nonexistent pass 0 and for pass 8, since the tests run an extra pass to
|
||||
// make sure nothing breaks.
|
||||
static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1, 1 };
|
||||
|
||||
return strides[aPass];
|
||||
}
|
||||
|
||||
size_t
|
||||
FinalPixelStride(uint8_t aPass)
|
||||
{
|
||||
// The stride between the final pixels in important rows for each pass, with
|
||||
// a dummy value for the nonexistent pass 0 and for pass 8, since the tests
|
||||
// run an extra pass to make sure nothing breaks.
|
||||
static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1, 1 };
|
||||
|
||||
return strides[aPass];
|
||||
}
|
||||
|
||||
bool
|
||||
IsImportantRow(int32_t aRow, uint8_t aPass)
|
||||
{
|
||||
return aRow % ImportantRowStride(aPass) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new
|
||||
* set of pixels in each block receives their final values, according to the
|
||||
* following pattern:
|
||||
*
|
||||
* 1 6 4 6 2 6 4 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
* 5 6 5 6 5 6 5 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
* 3 6 4 6 3 6 4 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
* 5 6 5 6 5 6 5 6
|
||||
* 7 7 7 7 7 7 7 7
|
||||
*
|
||||
* This function produces a row of pixels @aWidth wide, suitable for testing
|
||||
* horizontal interpolation on pass @aPass. The pattern of pixels used is
|
||||
* determined by @aPass and @aRow, which determine which pixels are final
|
||||
* according to the table above, and @aColors, from which the pixel values
|
||||
* are selected.
|
||||
*
|
||||
* There are two different behaviors: if |eNo| is passed for
|
||||
* @aShouldInterpolate, non-final pixels are treated as transparent. If |eNo|
|
||||
* is passed, non-final pixels get interpolated in from the surrounding final
|
||||
* pixels. The intention is that |eNo| is passed to generate input which will
|
||||
* be run through ADAM7InterpolatingFilter, and |eYes| is passed to generate
|
||||
* reference data to check that the filter is performing horizontal
|
||||
* interpolation correctly.
|
||||
*
|
||||
* This function does not perform vertical interpolation. Rows which aren't on
|
||||
* the current pass are filled with transparent pixels.
|
||||
*
|
||||
* @return a vector<BGRAColor> representing a row of pixels.
|
||||
*/
|
||||
vector<BGRAColor>
|
||||
ADAM7HorizontallyInterpolatedRow(uint8_t aPass,
|
||||
uint32_t aRow,
|
||||
uint32_t aWidth,
|
||||
ShouldInterpolate aShouldInterpolate,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
EXPECT_GT(aPass, 0);
|
||||
EXPECT_LE(aPass, 8);
|
||||
EXPECT_GT(aColors.size(), 0u);
|
||||
|
||||
vector<BGRAColor> result(aWidth);
|
||||
|
||||
if (IsImportantRow(aRow, aPass)) {
|
||||
vector<float>& weights = InterpolationWeights(FinalPixelStride(aPass));
|
||||
|
||||
// Compute the horizontally interpolated row.
|
||||
uint32_t col = 0;
|
||||
generate(result.begin(), result.end(), [&]{
|
||||
return HorizontallyInterpolatedPixel(col++, aWidth, weights,
|
||||
aShouldInterpolate, aColors);
|
||||
});
|
||||
} else {
|
||||
// This is an unimportant row; just make the entire thing transparent.
|
||||
generate(result.begin(), result.end(), []{
|
||||
return BGRAColor::Transparent();
|
||||
});
|
||||
}
|
||||
|
||||
EXPECT_EQ(result.size(), size_t(aWidth));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WriteState
|
||||
WriteUninterpolatedPixels(SurfaceFilter* aFilter,
|
||||
const IntSize& aSize,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
|
||||
for (int32_t row = 0; row < aSize.height; ++row) {
|
||||
// Compute uninterpolated pixels for this row.
|
||||
vector<BGRAColor> pixels =
|
||||
Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width,
|
||||
ShouldInterpolate::eNo, aColors));
|
||||
|
||||
// Write them to the surface.
|
||||
auto pixelIterator = pixels.cbegin();
|
||||
result = aFilter->WritePixelsToRow<uint32_t>([&]{
|
||||
return AsVariant((*pixelIterator++).AsPixel());
|
||||
});
|
||||
|
||||
if (result != WriteState::NEED_MORE_DATA) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
CheckHorizontallyInterpolatedImage(Decoder* aDecoder,
|
||||
const IntSize& aSize,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (int32_t row = 0; row < aSize.height; ++row) {
|
||||
if (!IsImportantRow(row, aPass)) {
|
||||
continue; // Don't check rows which aren't important on this pass.
|
||||
}
|
||||
|
||||
// Compute the expected pixels, *with* interpolation to match what the
|
||||
// filter should have done.
|
||||
vector<BGRAColor> expectedPixels =
|
||||
Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width,
|
||||
ShouldInterpolate::eYes, aColors));
|
||||
|
||||
if (!RowHasPixels(surface, row, expectedPixels)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
CheckHorizontalInterpolation(const IntSize& aSize,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
const IntRect surfaceRect(IntPoint(0, 0), aSize);
|
||||
|
||||
WithADAM7InterpolatingFilter(aSize,
|
||||
[&](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
// We check horizontal interpolation behavior for each pass individually. In
|
||||
// addition to the normal 7 passes that ADAM7 includes, we also check an
|
||||
// eighth pass to verify that nothing breaks if extra data is written.
|
||||
for (uint8_t pass = 1; pass <= 8; ++pass) {
|
||||
// Write our color pattern to the surface. We don't perform any
|
||||
// interpolation when writing to the filter so that we can check that the
|
||||
// filter itself *does*.
|
||||
WriteState result =
|
||||
WriteUninterpolatedPixels(aFilter, aSize, pass, aColors);
|
||||
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect);
|
||||
|
||||
// Check that the generated image matches the expected pattern, with
|
||||
// interpolation applied.
|
||||
EXPECT_TRUE(CheckHorizontallyInterpolatedImage(aDecoder, aSize,
|
||||
pass, aColors));
|
||||
|
||||
// Prepare for the next pass.
|
||||
aFilter->ResetToFirstRow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BGRAColor
|
||||
ADAM7RowColor(int32_t aRow,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
EXPECT_LT(0, aPass);
|
||||
EXPECT_GE(8, aPass);
|
||||
EXPECT_LT(0u, aColors.size());
|
||||
|
||||
// If this is an important row, select the color from the provided vector of
|
||||
// colors, which we cycle through infinitely. If not, just fill the row with
|
||||
// transparent pixels.
|
||||
return IsImportantRow(aRow, aPass) ? aColors[aRow % aColors.size()]
|
||||
: BGRAColor::Transparent();
|
||||
}
|
||||
|
||||
WriteState
|
||||
WriteRowColorPixels(SurfaceFilter* aFilter,
|
||||
const IntSize& aSize,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
WriteState result = WriteState::NEED_MORE_DATA;
|
||||
|
||||
for (int32_t row = 0; row < aSize.height; ++row) {
|
||||
const uint32_t color = ADAM7RowColor(row, aPass, aColors).AsPixel();
|
||||
|
||||
// Fill the surface with |color| pixels.
|
||||
result = aFilter->WritePixelsToRow<uint32_t>([&]{ return AsVariant(color); });
|
||||
|
||||
if (result != WriteState::NEED_MORE_DATA) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
CheckVerticallyInterpolatedImage(Decoder* aDecoder,
|
||||
const IntSize& aSize,
|
||||
uint8_t aPass,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
vector<float>& weights = InterpolationWeights(ImportantRowStride(aPass));
|
||||
|
||||
for (int32_t row = 0; row < aSize.height; ++row) {
|
||||
// Vertically interpolation takes place between two important rows. The
|
||||
// separation between the important rows is determined by the stride of this
|
||||
// pass. When there is no "next" important row because we'd run off the
|
||||
// bottom of the image, we use the same row for both. This matches
|
||||
// ADAM7InterpolatingFilter's behavior of duplicating the last important row
|
||||
// since there isn't another important row to vertically interpolate it
|
||||
// with.
|
||||
const int32_t stride = ImportantRowStride(aPass);
|
||||
const int32_t prevImportantRow = row - row % stride;
|
||||
const int32_t maybeNextImportantRow = prevImportantRow + stride;
|
||||
const int32_t nextImportantRow = maybeNextImportantRow < aSize.height
|
||||
? maybeNextImportantRow
|
||||
: prevImportantRow;
|
||||
|
||||
// Retrieve the colors for the important rows we're going to interpolate.
|
||||
const BGRAColor prevImportantRowColor =
|
||||
ADAM7RowColor(prevImportantRow, aPass, aColors);
|
||||
const BGRAColor nextImportantRowColor =
|
||||
ADAM7RowColor(nextImportantRow, aPass, aColors);
|
||||
|
||||
// The weight we'll use for interpolation is also determined by the stride.
|
||||
// A row halfway between two important rows should have pixels that have a
|
||||
// 50% contribution from each of the important rows, for example.
|
||||
const float weight = weights[row % stride];
|
||||
const BGRAColor interpolatedColor =
|
||||
InterpolateColors(prevImportantRowColor, nextImportantRowColor, weight);
|
||||
|
||||
// Generate a row of expected pixels. Every pixel in the row is always the
|
||||
// same color since we're only testing vertical interpolation between
|
||||
// solid-colored rows.
|
||||
vector<BGRAColor> expectedPixels(aSize.width);
|
||||
generate(expectedPixels.begin(), expectedPixels.end(), [&]{
|
||||
return interpolatedColor;
|
||||
});
|
||||
|
||||
// Check that the pixels match.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
if (!RowHasPixels(surface, row, expectedPixels)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
CheckVerticalInterpolation(const IntSize& aSize,
|
||||
const vector<BGRAColor>& aColors)
|
||||
{
|
||||
const IntRect surfaceRect(IntPoint(0, 0), aSize);
|
||||
|
||||
WithADAM7InterpolatingFilter(aSize,
|
||||
[&](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
for (uint8_t pass = 1; pass <= 8; ++pass) {
|
||||
// Write a pattern of rows to the surface. Important rows will receive a
|
||||
// color selected from |aColors|; unimportant rows will be transparent.
|
||||
WriteState result = WriteRowColorPixels(aFilter, aSize, pass, aColors);
|
||||
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect);
|
||||
|
||||
// Check that the generated image matches the expected pattern, with
|
||||
// interpolation applied.
|
||||
EXPECT_TRUE(CheckVerticallyInterpolatedImage(aDecoder, aSize,
|
||||
pass, aColors));
|
||||
|
||||
// Prepare for the next pass.
|
||||
aFilter->ResetToFirstRow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
CheckInterpolation(const IntSize& aSize, const vector<BGRAColor>& aColors)
|
||||
{
|
||||
CheckHorizontalInterpolation(aSize, aColors);
|
||||
CheckVerticalInterpolation(aSize, aColors);
|
||||
}
|
||||
|
||||
void
|
||||
CheckADAM7InterpolatingWritePixels(const IntSize& aSize)
|
||||
{
|
||||
// This test writes 8 passes of green pixels (the seven ADAM7 passes, plus one
|
||||
// extra to make sure nothing goes wrong if we write too much input) and verifies
|
||||
// that the output is a solid green surface each time. Because all the pixels
|
||||
// are the same color, interpolation doesn't matter; we test the correctness
|
||||
// of the interpolation algorithm itself separately.
|
||||
WithADAM7InterpolatingFilter(aSize,
|
||||
[&](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
IntRect rect(IntPoint(0, 0), aSize);
|
||||
|
||||
for (int32_t pass = 1; pass <= 8; ++pass) {
|
||||
// We only actually write up to the last important row for each pass,
|
||||
// because that row unambiguously determines the remaining rows.
|
||||
const int32_t lastRow = aSize.height - 1;
|
||||
const int32_t lastImportantRow =
|
||||
lastRow - (lastRow % ImportantRowStride(pass));
|
||||
const IntRect inputWriteRect(0, 0, aSize.width, lastImportantRow + 1);
|
||||
|
||||
CheckWritePixels(aDecoder, aFilter,
|
||||
/* aOutputRect = */ Some(rect),
|
||||
/* aInputRect = */ Some(rect),
|
||||
/* aInputWriteRect = */ Some(inputWriteRect));
|
||||
|
||||
aFilter->ResetToFirstRow();
|
||||
EXPECT_FALSE(aFilter->IsSurfaceFinished());
|
||||
Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels100_100)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(100, 100));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels99_99)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(99, 99));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels66_33)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(66, 33));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels33_66)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(33, 66));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels15_15)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(15, 15));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels9_9)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(9, 9));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels8_8)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(8, 8));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels7_7)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(7, 7));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels3_3)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(3, 3));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, WritePixels1_1)
|
||||
{
|
||||
CheckADAM7InterpolatingWritePixels(IntSize(1, 1));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, TrivialInterpolation48_48)
|
||||
{
|
||||
CheckInterpolation(IntSize(48, 48), { BGRAColor::Green() });
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput33_17)
|
||||
{
|
||||
// We check interpolation using irregular patterns to make sure that the
|
||||
// interpolation will look different for different passes.
|
||||
CheckInterpolation(IntSize(33, 17), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput32_16)
|
||||
{
|
||||
CheckInterpolation(IntSize(32, 16), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput31_15)
|
||||
{
|
||||
CheckInterpolation(IntSize(31, 15), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Blue(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput17_33)
|
||||
{
|
||||
CheckInterpolation(IntSize(17, 33), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput16_32)
|
||||
{
|
||||
CheckInterpolation(IntSize(16, 32), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput15_31)
|
||||
{
|
||||
CheckInterpolation(IntSize(15, 31), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(),
|
||||
BGRAColor::Red(), BGRAColor::Green(), BGRAColor::Blue(), BGRAColor::Red(),
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput9_9)
|
||||
{
|
||||
CheckInterpolation(IntSize(9, 9), {
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput8_8)
|
||||
{
|
||||
CheckInterpolation(IntSize(8, 8), {
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput7_7)
|
||||
{
|
||||
CheckInterpolation(IntSize(7, 7), {
|
||||
BGRAColor::Blue(), BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Red(), BGRAColor::Blue()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput3_3)
|
||||
{
|
||||
CheckInterpolation(IntSize(3, 3), {
|
||||
BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Red()
|
||||
});
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, InterpolationOutput1_1)
|
||||
{
|
||||
CheckInterpolation(IntSize(1, 1), { BGRAColor::Blue() });
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsFor0_0)
|
||||
{
|
||||
// A 0x0 input size is invalid, so configuration should fail.
|
||||
AssertConfiguringADAM7InterpolatingFilterFails(IntSize(0, 0));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsForMinus1_Minus1)
|
||||
{
|
||||
// A negative input size is invalid, so configuration should fail.
|
||||
AssertConfiguringADAM7InterpolatingFilterFails(IntSize(-1, -1));
|
||||
}
|
||||
|
||||
TEST(ImageADAM7InterpolatingFilter, ConfiguringPalettedADAM7InterpolatingFilterFails)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// ADAM7InterpolatingFilter does not support paletted images, so configuration
|
||||
// should fail.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
ADAM7InterpolatingConfig { },
|
||||
PalettedSurfaceConfig { decoder, IntSize(100, 100),
|
||||
IntRect(0, 0, 50, 50),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
/* 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 "CopyOnWrite.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
struct ValueStats
|
||||
{
|
||||
int32_t mCopies = 0;
|
||||
int32_t mFrees = 0;
|
||||
int32_t mCalls = 0;
|
||||
int32_t mConstCalls = 0;
|
||||
int32_t mSerial = 0;
|
||||
};
|
||||
|
||||
struct Value
|
||||
{
|
||||
NS_INLINE_DECL_REFCOUNTING(Value)
|
||||
|
||||
explicit Value(ValueStats& aStats)
|
||||
: mStats(aStats)
|
||||
, mSerial(mStats.mSerial++)
|
||||
{ }
|
||||
|
||||
Value(const Value& aOther)
|
||||
: mStats(aOther.mStats)
|
||||
, mSerial(mStats.mSerial++)
|
||||
{
|
||||
mStats.mCopies++;
|
||||
}
|
||||
|
||||
void Go() { mStats.mCalls++; }
|
||||
void Go() const { mStats.mConstCalls++; }
|
||||
|
||||
int32_t Serial() const { return mSerial; }
|
||||
|
||||
protected:
|
||||
~Value() { mStats.mFrees++; }
|
||||
|
||||
private:
|
||||
ValueStats& mStats;
|
||||
int32_t mSerial;
|
||||
};
|
||||
|
||||
TEST(ImageCopyOnWrite, Read)
|
||||
{
|
||||
ValueStats stats;
|
||||
|
||||
{
|
||||
CopyOnWrite<Value> cow(new Value(stats));
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
|
||||
cow.Read([&](const Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(0, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
});
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(1, stats.mFrees);
|
||||
}
|
||||
|
||||
TEST(ImageCopyOnWrite, RecursiveRead)
|
||||
{
|
||||
ValueStats stats;
|
||||
|
||||
{
|
||||
CopyOnWrite<Value> cow(new Value(stats));
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
|
||||
cow.Read([&](const Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
// Make sure that Read() inside a Read() succeeds.
|
||||
cow.Read([&](const Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(0, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
}, []() {
|
||||
// This gets called if we can't read. We shouldn't get here.
|
||||
EXPECT_TRUE(false);
|
||||
});
|
||||
});
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(1, stats.mFrees);
|
||||
}
|
||||
|
||||
TEST(ImageCopyOnWrite, Write)
|
||||
{
|
||||
ValueStats stats;
|
||||
|
||||
{
|
||||
CopyOnWrite<Value> cow(new Value(stats));
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
cow.Write([&](Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(!cow.CanRead());
|
||||
EXPECT_TRUE(!cow.CanWrite());
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(0, stats.mConstCalls);
|
||||
});
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(0, stats.mConstCalls);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(1, stats.mFrees);
|
||||
}
|
||||
|
||||
TEST(ImageCopyOnWrite, WriteRecursive)
|
||||
{
|
||||
ValueStats stats;
|
||||
|
||||
{
|
||||
CopyOnWrite<Value> cow(new Value(stats));
|
||||
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
cow.Read([&](const Value* aValue) {
|
||||
EXPECT_EQ(0, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(0, aValue->Serial());
|
||||
EXPECT_TRUE(cow.CanRead());
|
||||
EXPECT_TRUE(cow.CanWrite());
|
||||
|
||||
// Make sure Write() inside a Read() succeeds.
|
||||
cow.Write([&](Value* aValue) {
|
||||
EXPECT_EQ(1, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(1, aValue->Serial());
|
||||
EXPECT_TRUE(!cow.CanRead());
|
||||
EXPECT_TRUE(!cow.CanWrite());
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(0, stats.mConstCalls);
|
||||
|
||||
// Make sure Read() inside a Write() fails.
|
||||
cow.Read([](const Value* aValue) {
|
||||
// This gets called if we can read. We shouldn't get here.
|
||||
EXPECT_TRUE(false);
|
||||
}, []() {
|
||||
// This gets called if we can't read. We *should* get here.
|
||||
EXPECT_TRUE(true);
|
||||
});
|
||||
|
||||
// Make sure Write() inside a Write() fails.
|
||||
cow.Write([](Value* aValue) {
|
||||
// This gets called if we can write. We shouldn't get here.
|
||||
EXPECT_TRUE(false);
|
||||
}, []() {
|
||||
// This gets called if we can't write. We *should* get here.
|
||||
EXPECT_TRUE(true);
|
||||
});
|
||||
}, []() {
|
||||
// This gets called if we can't write. We shouldn't get here.
|
||||
EXPECT_TRUE(false);
|
||||
});
|
||||
|
||||
aValue->Go();
|
||||
|
||||
EXPECT_EQ(1, stats.mCopies);
|
||||
EXPECT_EQ(0, stats.mFrees);
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
});
|
||||
|
||||
EXPECT_EQ(1, stats.mCopies);
|
||||
EXPECT_EQ(1, stats.mFrees);
|
||||
EXPECT_EQ(1, stats.mCalls);
|
||||
EXPECT_EQ(1, stats.mConstCalls);
|
||||
}
|
||||
|
||||
EXPECT_EQ(1, stats.mCopies);
|
||||
EXPECT_EQ(2, stats.mFrees);
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
/* 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 "Common.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "imgITools.h"
|
||||
#include "ImageOps.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsIThread.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
class DecodeToSurfaceRunnable : public Runnable
|
||||
{
|
||||
public:
|
||||
DecodeToSurfaceRunnable(RefPtr<SourceSurface>& aSurface,
|
||||
nsIInputStream* aInputStream,
|
||||
const ImageTestCase& aTestCase)
|
||||
: mSurface(aSurface)
|
||||
, mInputStream(aInputStream)
|
||||
, mTestCase(aTestCase)
|
||||
{ }
|
||||
|
||||
NS_IMETHOD Run() override
|
||||
{
|
||||
Go();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void Go()
|
||||
{
|
||||
mSurface =
|
||||
ImageOps::DecodeToSurface(mInputStream,
|
||||
nsDependentCString(mTestCase.mMimeType),
|
||||
imgIContainer::DECODE_FLAGS_DEFAULT);
|
||||
ASSERT_TRUE(mSurface != nullptr);
|
||||
|
||||
EXPECT_EQ(SurfaceType::DATA, mSurface->GetType());
|
||||
EXPECT_TRUE(mSurface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
|
||||
mSurface->GetFormat() == SurfaceFormat::B8G8R8A8);
|
||||
EXPECT_EQ(mTestCase.mSize, mSurface->GetSize());
|
||||
|
||||
EXPECT_TRUE(IsSolidColor(mSurface, BGRAColor::Green(),
|
||||
mTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<SourceSurface>& mSurface;
|
||||
nsCOMPtr<nsIInputStream> mInputStream;
|
||||
ImageTestCase mTestCase;
|
||||
};
|
||||
|
||||
static void
|
||||
RunDecodeToSurface(const ImageTestCase& aTestCase)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
nsresult rv = NS_NewThread(getter_AddRefs(thread), nullptr);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// We run the DecodeToSurface tests off-main-thread to ensure that
|
||||
// DecodeToSurface doesn't require any main-thread-only code.
|
||||
RefPtr<SourceSurface> surface;
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
new DecodeToSurfaceRunnable(surface, inputStream, aTestCase);
|
||||
thread->Dispatch(runnable, nsIThread::DISPATCH_SYNC);
|
||||
|
||||
thread->Shutdown();
|
||||
|
||||
// Explicitly release the SourceSurface on the main thread.
|
||||
surface = nullptr;
|
||||
}
|
||||
|
||||
class ImageDecodeToSurface : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageDecodeToSurface, PNG) { RunDecodeToSurface(GreenPNGTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, GIF) { RunDecodeToSurface(GreenGIFTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, JPG) { RunDecodeToSurface(GreenJPGTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, BMP) { RunDecodeToSurface(GreenBMPTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, ICO) { RunDecodeToSurface(GreenICOTestCase()); }
|
||||
TEST_F(ImageDecodeToSurface, Icon) { RunDecodeToSurface(GreenIconTestCase()); }
|
||||
|
||||
TEST_F(ImageDecodeToSurface, AnimatedGIF)
|
||||
{
|
||||
RunDecodeToSurface(GreenFirstFrameAnimatedGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecodeToSurface, AnimatedPNG)
|
||||
{
|
||||
RunDecodeToSurface(GreenFirstFrameAnimatedPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecodeToSurface, Corrupt)
|
||||
{
|
||||
ImageTestCase testCase = CorruptTestCase();
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
RefPtr<SourceSurface> surface =
|
||||
ImageOps::DecodeToSurface(inputStream,
|
||||
nsDependentCString(testCase.mMimeType),
|
||||
imgIContainer::DECODE_FLAGS_DEFAULT);
|
||||
EXPECT_TRUE(surface == nullptr);
|
||||
}
|
||||
|
|
@ -1,669 +0,0 @@
|
|||
/* 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 "Common.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "decoders/nsBMPDecoder.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "imgITools.h"
|
||||
#include "ImageFactory.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsIThread.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "ProgressTracker.h"
|
||||
#include "SourceBuffer.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
static already_AddRefed<SourceSurface>
|
||||
CheckDecoderState(const ImageTestCase& aTestCase, Decoder* aDecoder)
|
||||
{
|
||||
EXPECT_TRUE(aDecoder->GetDecodeDone());
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
|
||||
aDecoder->HasError());
|
||||
|
||||
// Verify that the decoder made the expected progress.
|
||||
Progress progress = aDecoder->TakeProgress();
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
|
||||
bool(progress & FLAG_HAS_ERROR));
|
||||
|
||||
if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
|
||||
return nullptr; // That's all we can check for bad images.
|
||||
}
|
||||
|
||||
EXPECT_TRUE(bool(progress & FLAG_SIZE_AVAILABLE));
|
||||
EXPECT_TRUE(bool(progress & FLAG_DECODE_COMPLETE));
|
||||
EXPECT_TRUE(bool(progress & FLAG_FRAME_COMPLETE));
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT),
|
||||
bool(progress & FLAG_HAS_TRANSPARENCY));
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
|
||||
bool(progress & FLAG_IS_ANIMATED));
|
||||
|
||||
// The decoder should get the correct size.
|
||||
IntSize size = aDecoder->Size();
|
||||
EXPECT_EQ(aTestCase.mSize.width, size.width);
|
||||
EXPECT_EQ(aTestCase.mSize.height, size.height);
|
||||
|
||||
// Get the current frame, which is always the first frame of the image
|
||||
// because CreateAnonymousDecoder() forces a first-frame-only decode.
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
// Verify that the resulting surfaces matches our expectations.
|
||||
EXPECT_EQ(SurfaceType::DATA, surface->GetType());
|
||||
EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
|
||||
surface->GetFormat() == SurfaceFormat::B8G8R8A8);
|
||||
EXPECT_EQ(aTestCase.mOutputSize, surface->GetSize());
|
||||
|
||||
return surface.forget();
|
||||
}
|
||||
|
||||
static void
|
||||
CheckDecoderResults(const ImageTestCase& aTestCase, Decoder* aDecoder)
|
||||
{
|
||||
RefPtr<SourceSurface> surface = CheckDecoderState(aTestCase, aDecoder);
|
||||
if (!surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the output.
|
||||
EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(),
|
||||
aTestCase.mFlags & TEST_CASE_IS_FUZZY ? 1 : 0));
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void WithSingleChunkDecode(const ImageTestCase& aTestCase,
|
||||
const Maybe<IntSize>& aOutputSize,
|
||||
Func aResultChecker)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into a SourceBuffer.
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
sourceBuffer->ExpectLength(length);
|
||||
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a decoder.
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aOutputSize,
|
||||
DefaultSurfaceFlags());
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
|
||||
// Run the full decoder synchronously.
|
||||
task->Run();
|
||||
|
||||
// Call the lambda to verify the expected results.
|
||||
aResultChecker(decoder);
|
||||
}
|
||||
|
||||
static void
|
||||
CheckDecoderSingleChunk(const ImageTestCase& aTestCase)
|
||||
{
|
||||
WithSingleChunkDecode(aTestCase, Nothing(), [&](Decoder* aDecoder) {
|
||||
CheckDecoderResults(aTestCase, aDecoder);
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
CheckDecoderMultiChunk(const ImageTestCase& aTestCase)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Create a SourceBuffer and a decoder.
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
sourceBuffer->ExpectLength(length);
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
|
||||
DefaultSurfaceFlags());
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
|
||||
for (uint64_t read = 0; read < length ; ++read) {
|
||||
uint64_t available = 0;
|
||||
rv = inputStream->Available(&available);
|
||||
ASSERT_TRUE(available > 0);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
rv = sourceBuffer->AppendFromInputStream(inputStream, 1);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
task->Run();
|
||||
}
|
||||
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
task->Run();
|
||||
|
||||
CheckDecoderResults(aTestCase, decoder);
|
||||
}
|
||||
|
||||
static void
|
||||
CheckDownscaleDuringDecode(const ImageTestCase& aTestCase)
|
||||
{
|
||||
// This function expects that |aTestCase| 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. We'll downscale it from 100x100 to 20x20.
|
||||
IntSize outputSize(20, 20);
|
||||
|
||||
WithSingleChunkDecode(aTestCase, Some(outputSize), [&](Decoder* aDecoder) {
|
||||
RefPtr<SourceSurface> surface = CheckDecoderState(aTestCase, aDecoder);
|
||||
|
||||
// There are no downscale-during-decode tests that have TEST_CASE_HAS_ERROR
|
||||
// set, so we expect to always get a surface here.
|
||||
EXPECT_TRUE(surface != nullptr);
|
||||
|
||||
if (aTestCase.mFlags & TEST_CASE_IGNORE_OUTPUT) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the downscaled 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.
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 0, 4, BGRAColor::Green(), /* aFuzz = */ 47));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 6, 3, BGRAColor::Red(), /* aFuzz = */ 27));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 11, 3, BGRAColor::Green(), /* aFuzz = */ 47));
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, 16, 4, BGRAColor::Red(), /* aFuzz = */ 27));
|
||||
});
|
||||
}
|
||||
|
||||
class ImageDecoders : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageDecoders, PNGSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, PNGMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, PNGDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, GIFSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, GIFMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, GIFDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, JPGSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenJPGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, JPGMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenJPGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, JPGDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledJPGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, BMPSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenBMPTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, BMPMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenBMPTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, BMPDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledBMPTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, ICOSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenICOTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, ICOMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenICOTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, ICODownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledICOTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, ICOWithANDMaskDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledTransparentICOWithANDMaskTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, IconSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenIconTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, IconMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenIconTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, IconDownscaleDuringDecode)
|
||||
{
|
||||
CheckDownscaleDuringDecode(DownscaledIconTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenFirstFrameAnimatedGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedPNGSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(GreenFirstFrameAnimatedPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedPNGMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenFirstFrameAnimatedPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(CorruptTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(CorruptTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(CorruptBMPWithTruncatedHeader());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptBMPWithTruncatedHeaderMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(CorruptBMPWithTruncatedHeader());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(CorruptICOWithBadBMPWidthTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptICOWithBadBMPWidthMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(CorruptICOWithBadBMPWidthTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(CorruptICOWithBadBMPHeightTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, CorruptICOWithBadBMPHeightMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(CorruptICOWithBadBMPHeightTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFWithFRAME_FIRST)
|
||||
{
|
||||
ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase();
|
||||
|
||||
// Verify that we can decode this test case and retrieve the first frame using
|
||||
// imgIContainer::FRAME_FIRST. This ensures that we correctly trigger a
|
||||
// single-frame decode rather than an animated decode when
|
||||
// imgIContainer::FRAME_FIRST is requested.
|
||||
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into the image.
|
||||
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
|
||||
static_cast<uint32_t>(length));
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Let the image know we've sent all the data.
|
||||
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
|
||||
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
|
||||
// Lock the image so its surfaces don't disappear during the test.
|
||||
image->LockImage();
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image, specifying FRAME_FIRST
|
||||
// to ensure that we don't get an animated decode.
|
||||
RefPtr<SourceSurface> surface =
|
||||
image->GetFrame(imgIContainer::FRAME_FIRST,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that the image's metadata meets our expectations.
|
||||
IntSize imageSize(0, 0);
|
||||
rv = image->GetWidth(&imageSize.width);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
rv = image->GetHeight(&imageSize.height);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
EXPECT_EQ(testCase.mSize.width, imageSize.width);
|
||||
EXPECT_EQ(testCase.mSize.height, imageSize.height);
|
||||
|
||||
Progress imageProgress = tracker->GetProgress();
|
||||
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
|
||||
|
||||
// Ensure that we decoded the static version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eStatic));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
// Ensure that we didn't decode the animated version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
|
||||
}
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image, this time specifying
|
||||
// FRAME_CURRENT to ensure that we get an animated decode.
|
||||
RefPtr<SourceSurface> animatedSurface =
|
||||
image->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that we decoded both frames of the animated version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
// Ensure that the static version is still around.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eStatic));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFWithFRAME_CURRENT)
|
||||
{
|
||||
ImageTestCase testCase = GreenFirstFrameAnimatedGIFTestCase();
|
||||
|
||||
// Verify that we can decode this test case and retrieve the entire sequence
|
||||
// of frames using imgIContainer::FRAME_CURRENT. This ensures that we
|
||||
// correctly trigger an animated decode rather than a single-frame decode when
|
||||
// imgIContainer::FRAME_CURRENT is requested.
|
||||
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into the image.
|
||||
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
|
||||
static_cast<uint32_t>(length));
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Let the image know we've sent all the data.
|
||||
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
|
||||
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
|
||||
// Lock the image so its surfaces don't disappear during the test.
|
||||
image->LockImage();
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image, specifying
|
||||
// FRAME_CURRENT to ensure we get an animated decode.
|
||||
RefPtr<SourceSurface> surface =
|
||||
image->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that the image's metadata meets our expectations.
|
||||
IntSize imageSize(0, 0);
|
||||
rv = image->GetWidth(&imageSize.width);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
rv = image->GetHeight(&imageSize.height);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
EXPECT_EQ(testCase.mSize.width, imageSize.width);
|
||||
EXPECT_EQ(testCase.mSize.height, imageSize.height);
|
||||
|
||||
Progress imageProgress = tracker->GetProgress();
|
||||
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
|
||||
|
||||
// Ensure that we decoded both frames of the animated version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
// Ensure that we didn't decode the static version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eStatic));
|
||||
ASSERT_EQ(MatchType::NOT_FOUND, result.Type());
|
||||
}
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image, this time specifying
|
||||
// FRAME_FIRST to ensure that we get a single-frame decode.
|
||||
RefPtr<SourceSurface> animatedSurface =
|
||||
image->GetFrame(imgIContainer::FRAME_FIRST,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that we decoded the static version of the image.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eStatic));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
// Ensure that both frames of the animated version are still around.
|
||||
{
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, AnimatedGIFWithExtraImageSubBlocks)
|
||||
{
|
||||
ImageTestCase testCase = ExtraImageSubBlocksAnimatedGIFTestCase();
|
||||
|
||||
// Verify that we can decode this test case and get two frames, even though
|
||||
// there are extra image sub blocks between the first and second frame. The
|
||||
// extra data shouldn't confuse the decoder or cause the decode to fail.
|
||||
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into the image.
|
||||
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
|
||||
static_cast<uint32_t>(length));
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Let the image know we've sent all the data.
|
||||
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
|
||||
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image.
|
||||
RefPtr<SourceSurface> surface =
|
||||
image->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that the image's metadata meets our expectations.
|
||||
IntSize imageSize(0, 0);
|
||||
rv = image->GetWidth(&imageSize.width);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
rv = image->GetHeight(&imageSize.height);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
EXPECT_EQ(testCase.mSize.width, imageSize.width);
|
||||
EXPECT_EQ(testCase.mSize.height, imageSize.height);
|
||||
|
||||
Progress imageProgress = tracker->GetProgress();
|
||||
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
|
||||
|
||||
// Ensure that we decoded both frames of the image.
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoders, TruncatedSmallGIFSingleChunk)
|
||||
{
|
||||
CheckDecoderSingleChunk(TruncatedSmallGIFTestCase());
|
||||
}
|
||||
|
|
@ -1,672 +0,0 @@
|
|||
/* -*- 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 <typename Func> void
|
||||
WithDeinterlacingFilter(const IntSize& aSize,
|
||||
bool aProgressiveDisplay,
|
||||
Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(bool(decoder));
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
DeinterlacingConfig<uint32_t> { aProgressiveDisplay },
|
||||
SurfaceConfig { decoder, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
template <typename Func> void
|
||||
WithPalettedDeinterlacingFilter(const IntSize& aSize,
|
||||
Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
DeinterlacingConfig<uint8_t> { /* mProgressiveDisplay = */ true },
|
||||
PalettedSurfaceConfig { decoder, aSize,
|
||||
IntRect(0, 0, 100, 100),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
|
||||
void
|
||||
AssertConfiguringDeinterlacingFilterFails(const IntSize& aSize)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DeinterlacingConfig<uint32_t> { /* mProgressiveDisplay = */ true},
|
||||
SurfaceConfig { decoder, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
class ImageDeinterlacingFilter : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(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_F(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_F(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_F(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_F(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_F(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_F(ImageDeinterlacingFilter, PalettedWritePixels)
|
||||
{
|
||||
WithPalettedDeinterlacingFilter(IntSize(100, 100),
|
||||
[](Decoder* aDecoder, SurfaceFilter* aFilter) {
|
||||
CheckPalettedWritePixels(aDecoder, aFilter);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsNonProgressiveOutput51_52)
|
||||
{
|
||||
WithDeinterlacingFilter(IntSize(51, 52), /* aProgressiveDisplay = */ false,
|
||||
[](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>([&]() {
|
||||
uint32_t row = count / 51; // Integer division.
|
||||
++count;
|
||||
|
||||
// Note that we use a switch statement here, even though it's quite
|
||||
// verbose, because it's useful to have the mappings between input and
|
||||
// output rows available when debugging these tests.
|
||||
|
||||
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.
|
||||
case 3: // Output row 24.
|
||||
case 4: // Output row 32.
|
||||
case 5: // Output row 40.
|
||||
case 6: // Output row 48.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Second pass. Rows are positioned at 8n + 4.
|
||||
case 7: // Output row 4.
|
||||
case 8: // Output row 12.
|
||||
case 9: // Output row 20.
|
||||
case 10: // Output row 28.
|
||||
case 11: // Output row 36.
|
||||
case 12: // Output row 44.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Third pass. Rows are positioned at 4n + 2.
|
||||
case 13: // Output row 2.
|
||||
case 14: // Output row 6.
|
||||
case 15: // Output row 10.
|
||||
case 16: // Output row 14.
|
||||
case 17: // Output row 18.
|
||||
case 18: // Output row 22.
|
||||
case 19: // Output row 26.
|
||||
case 20: // Output row 30.
|
||||
case 21: // Output row 34.
|
||||
case 22: // Output row 38.
|
||||
case 23: // Output row 42.
|
||||
case 24: // Output row 46.
|
||||
case 25: // Output row 50.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Fourth pass. Rows are positioned at 2n + 1.
|
||||
case 26: // Output row 1.
|
||||
case 27: // Output row 3.
|
||||
case 28: // Output row 5.
|
||||
case 29: // Output row 7.
|
||||
case 30: // Output row 9.
|
||||
case 31: // Output row 11.
|
||||
case 32: // Output row 13.
|
||||
case 33: // Output row 15.
|
||||
case 34: // Output row 17.
|
||||
case 35: // Output row 19.
|
||||
case 36: // Output row 21.
|
||||
case 37: // Output row 23.
|
||||
case 38: // Output row 25.
|
||||
case 39: // Output row 27.
|
||||
case 40: // Output row 29.
|
||||
case 41: // Output row 31.
|
||||
case 42: // Output row 33.
|
||||
case 43: // Output row 35.
|
||||
case 44: // Output row 37.
|
||||
case 45: // Output row 39.
|
||||
case 46: // Output row 41.
|
||||
case 47: // Output row 43.
|
||||
case 48: // Output row 45.
|
||||
case 49: // Output row 47.
|
||||
case 50: // Output row 49.
|
||||
case 51: // Output row 51.
|
||||
return AsVariant(BGRAColor::Red().AsPixel());
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected row");
|
||||
return AsVariant(BGRAColor::Transparent().AsPixel());
|
||||
}
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(51u * 52u, count);
|
||||
|
||||
AssertCorrectPipelineFinalState(aFilter,
|
||||
IntRect(0, 0, 51, 52),
|
||||
IntRect(0, 0, 51, 52));
|
||||
|
||||
// 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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = 0; row < 52; ++row) {
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1,
|
||||
row % 2 == 0 ? BGRAColor::Green()
|
||||
: BGRAColor::Red()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(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>([&]() {
|
||||
uint32_t row = count / 20; // Integer division.
|
||||
++count;
|
||||
|
||||
// Note that we use a switch statement here, even though it's quite
|
||||
// verbose, because it's useful to have the mappings between input and
|
||||
// output rows available when debugging these tests.
|
||||
|
||||
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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = 0; row < 20; ++row) {
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1,
|
||||
row % 2 == 0 ? BGRAColor::Green()
|
||||
: BGRAColor::Red()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsOutput7_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;
|
||||
auto result = aFilter->WritePixels<uint32_t>([&]() {
|
||||
uint32_t row = count / 7; // 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.
|
||||
case 1: // Output row 4.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
// Third pass. Rows are positioned at 4n + 2.
|
||||
case 2: // Output row 2.
|
||||
case 3: // Output row 6.
|
||||
return AsVariant(BGRAColor::Red().AsPixel());
|
||||
|
||||
// Fourth pass. Rows are positioned at 2n + 1.
|
||||
case 4: // Output row 1.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
case 5: // Output row 3.
|
||||
return AsVariant(BGRAColor::Red().AsPixel());
|
||||
|
||||
case 6: // Output row 5.
|
||||
return AsVariant(BGRAColor::Green().AsPixel());
|
||||
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected row");
|
||||
return AsVariant(BGRAColor::Transparent().AsPixel());
|
||||
}
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(7u * 7u, count);
|
||||
|
||||
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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
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_F(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>([&]() {
|
||||
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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = 0; row < 3; ++row) {
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1,
|
||||
row == 0 || row == 2 ? BGRAColor::Green()
|
||||
: BGRAColor::Red()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(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<uint32_t>([&]() {
|
||||
++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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
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->WritePixels<uint32_t>([&]() -> NextPixel<uint32_t> {
|
||||
if (count < 7) {
|
||||
++count;
|
||||
return AsVariant(aColor.AsPixel());
|
||||
}
|
||||
return AsVariant(WriteState::NEED_MORE_DATA);
|
||||
});
|
||||
|
||||
EXPECT_EQ(aNextState, result);
|
||||
EXPECT_EQ(7u, count);
|
||||
|
||||
// Assert that we got the expected invalidation region.
|
||||
Maybe<SurfaceInvalidRect> 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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
for (uint32_t row = aFirstHaeberliRow; row <= aLastHaeberliRow; ++row) {
|
||||
EXPECT_TRUE(RowsAreSolidColor(surface, row, 1, aColor));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, WritePixelsIntermediateOutput7_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<SurfaceInvalidRect> 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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
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_F(ImageDeinterlacingFilter, WritePixelsNonProgressiveIntermediateOutput7_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<SurfaceInvalidRect> 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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
|
||||
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_F(ImageDeinterlacingFilter, DeinterlacingFailsFor0_0)
|
||||
{
|
||||
// A 0x0 input size is invalid, so configuration should fail.
|
||||
AssertConfiguringDeinterlacingFilterFails(IntSize(0, 0));
|
||||
}
|
||||
|
||||
TEST_F(ImageDeinterlacingFilter, DeinterlacingFailsForMinus1_Minus1)
|
||||
{
|
||||
// A negative input size is invalid, so configuration should fail.
|
||||
AssertConfiguringDeinterlacingFilterFails(IntSize(-1, -1));
|
||||
}
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
/* -*- 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 <typename Func> void
|
||||
WithDownscalingFilter(const IntSize& aInputSize,
|
||||
const IntSize& aOutputSize,
|
||||
Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
DownscalingConfig { aInputSize,
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, aOutputSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
void
|
||||
AssertConfiguringDownscalingFilterFails(const IntSize& aInputSize,
|
||||
const IntSize& aOutputSize)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DownscalingConfig { aInputSize,
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, 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, 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, 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, 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, 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, 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, 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, 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<uint32_t>([&]() -> NextPixel<uint32_t> {
|
||||
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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
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<uint32_t>([&]() -> NextPixel<uint32_t> {
|
||||
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<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
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> 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, IntSize(20, 20),
|
||||
IntRect(0, 0, 20, 20),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
/* -*- 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> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(bool(decoder));
|
||||
|
||||
// Configuring a DownscalingFilter should fail without Skia.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, IntSize(50, 50),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
/* -*- 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 "Common.h"
|
||||
#include "imgLoader.h"
|
||||
#include "nsMimeTypes.h"
|
||||
#include "nsString.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
static void
|
||||
CheckMimeType(const char* aContents, size_t aLength, const char* aExpected)
|
||||
{
|
||||
nsAutoCString detected;
|
||||
nsresult rv = imgLoader::GetMimeTypeFromContent(aContents, aLength, detected);
|
||||
if (aExpected) {
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
EXPECT_TRUE(detected.EqualsASCII(aExpected));
|
||||
} else {
|
||||
ASSERT_TRUE(NS_FAILED(rv));
|
||||
EXPECT_TRUE(detected.IsEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
class ImageLoader : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageLoader, DetectGIF)
|
||||
{
|
||||
const char buffer[] = "GIF87a";
|
||||
CheckMimeType(buffer, sizeof(buffer), IMAGE_GIF);
|
||||
}
|
||||
|
||||
TEST_F(ImageLoader, DetectPNG)
|
||||
{
|
||||
const char buffer[] = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
|
||||
CheckMimeType(buffer, sizeof(buffer), IMAGE_PNG);
|
||||
}
|
||||
|
||||
TEST_F(ImageLoader, DetectJPEG)
|
||||
{
|
||||
const char buffer[] = "\xFF\xD8\xFF";
|
||||
CheckMimeType(buffer, sizeof(buffer), IMAGE_JPEG);
|
||||
}
|
||||
|
||||
TEST_F(ImageLoader, DetectART)
|
||||
{
|
||||
const char buffer[] = "\x4A\x47\xFF\xFF\x00";
|
||||
CheckMimeType(buffer, sizeof(buffer), IMAGE_ART);
|
||||
}
|
||||
|
||||
TEST_F(ImageLoader, DetectBMP)
|
||||
{
|
||||
const char buffer[] = "BM";
|
||||
CheckMimeType(buffer, sizeof(buffer), IMAGE_BMP);
|
||||
}
|
||||
|
||||
TEST_F(ImageLoader, DetectICO)
|
||||
{
|
||||
const char buffer[] = "\x00\x00\x01\x00";
|
||||
CheckMimeType(buffer, sizeof(buffer), IMAGE_ICO);
|
||||
}
|
||||
|
||||
TEST_F(ImageLoader, DetectWebP)
|
||||
{
|
||||
const char buffer[] = "RIFF\xFF\xFF\xFF\xFFWEBPVP8L";
|
||||
CheckMimeType(buffer, sizeof(buffer), IMAGE_WEBP);
|
||||
}
|
||||
|
||||
TEST_F(ImageLoader, DetectNone)
|
||||
{
|
||||
const char buffer[] = "abcdefghijklmnop";
|
||||
CheckMimeType(buffer, sizeof(buffer), nullptr);
|
||||
}
|
||||
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
/* 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 "Common.h"
|
||||
#include "Decoder.h"
|
||||
#include "DecoderFactory.h"
|
||||
#include "decoders/nsBMPDecoder.h"
|
||||
#include "IDecodingTask.h"
|
||||
#include "imgIContainer.h"
|
||||
#include "imgITools.h"
|
||||
#include "ImageFactory.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIRunnable.h"
|
||||
#include "nsIThread.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "ProgressTracker.h"
|
||||
#include "SourceBuffer.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
enum class BMPWithinICO
|
||||
{
|
||||
NO,
|
||||
YES
|
||||
};
|
||||
|
||||
static void
|
||||
CheckMetadata(const ImageTestCase& aTestCase,
|
||||
BMPWithinICO aBMPWithinICO = BMPWithinICO::NO)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into a SourceBuffer.
|
||||
NotNull<RefPtr<SourceBuffer>> sourceBuffer = WrapNotNull(new SourceBuffer());
|
||||
sourceBuffer->ExpectLength(length);
|
||||
rv = sourceBuffer->AppendFromInputStream(inputStream, length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
sourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a metadata decoder.
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
|
||||
RefPtr<Decoder> decoder =
|
||||
DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer);
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
|
||||
if (aBMPWithinICO == BMPWithinICO::YES) {
|
||||
static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
|
||||
}
|
||||
|
||||
// Run the metadata decoder synchronously.
|
||||
task->Run();
|
||||
|
||||
// Ensure that the metadata decoder didn't make progress it shouldn't have
|
||||
// (which would indicate that it decoded past the header of the image).
|
||||
Progress metadataProgress = decoder->TakeProgress();
|
||||
EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE |
|
||||
FLAG_HAS_TRANSPARENCY |
|
||||
FLAG_IS_ANIMATED)));
|
||||
|
||||
// If the test case is corrupt, assert what we can and return early.
|
||||
if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
|
||||
EXPECT_TRUE(decoder->GetDecodeDone());
|
||||
EXPECT_TRUE(decoder->HasError());
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
|
||||
|
||||
// Check that we got the expected metadata.
|
||||
EXPECT_TRUE(metadataProgress & FLAG_SIZE_AVAILABLE);
|
||||
|
||||
IntSize metadataSize = decoder->Size();
|
||||
EXPECT_EQ(aTestCase.mSize.width, metadataSize.width);
|
||||
EXPECT_EQ(aTestCase.mSize.height, metadataSize.height);
|
||||
|
||||
bool expectTransparency = aBMPWithinICO == BMPWithinICO::YES
|
||||
? true
|
||||
: bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT);
|
||||
EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY));
|
||||
|
||||
EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
|
||||
bool(metadataProgress & FLAG_IS_ANIMATED));
|
||||
|
||||
// Create a full decoder, so we can compare the result.
|
||||
decoder =
|
||||
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
|
||||
DefaultSurfaceFlags());
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
task = new AnonymousDecodingTask(WrapNotNull(decoder));
|
||||
|
||||
if (aBMPWithinICO == BMPWithinICO::YES) {
|
||||
static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
|
||||
}
|
||||
|
||||
// Run the full decoder synchronously.
|
||||
task->Run();
|
||||
|
||||
EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
|
||||
Progress fullProgress = decoder->TakeProgress();
|
||||
|
||||
// If the metadata decoder set a progress bit, the full decoder should also
|
||||
// have set the same bit.
|
||||
EXPECT_EQ(fullProgress, metadataProgress | fullProgress);
|
||||
|
||||
// The full decoder and the metadata decoder should agree on the image's size.
|
||||
IntSize fullSize = decoder->Size();
|
||||
EXPECT_EQ(metadataSize.width, fullSize.width);
|
||||
EXPECT_EQ(metadataSize.height, fullSize.height);
|
||||
|
||||
// We should not discover transparency during the full decode that we didn't
|
||||
// discover during the metadata decode, unless the image is animated.
|
||||
EXPECT_TRUE(!(fullProgress & FLAG_HAS_TRANSPARENCY) ||
|
||||
(metadataProgress & FLAG_HAS_TRANSPARENCY) ||
|
||||
(fullProgress & FLAG_IS_ANIMATED));
|
||||
}
|
||||
|
||||
class ImageDecoderMetadata : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageDecoderMetadata, PNG) { CheckMetadata(GreenPNGTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, TransparentPNG) { CheckMetadata(TransparentPNGTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, GIF) { CheckMetadata(GreenGIFTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, TransparentGIF) { CheckMetadata(TransparentGIFTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, JPG) { CheckMetadata(GreenJPGTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, BMP) { CheckMetadata(GreenBMPTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, ICO) { CheckMetadata(GreenICOTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, Icon) { CheckMetadata(GreenIconTestCase()); }
|
||||
|
||||
TEST_F(ImageDecoderMetadata, AnimatedGIF)
|
||||
{
|
||||
CheckMetadata(GreenFirstFrameAnimatedGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, AnimatedPNG)
|
||||
{
|
||||
CheckMetadata(GreenFirstFrameAnimatedPNGTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, FirstFramePaddingGIF)
|
||||
{
|
||||
CheckMetadata(FirstFramePaddingGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPNotWithinICO)
|
||||
{
|
||||
CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_DEFAULT_FLAGS),
|
||||
BMPWithinICO::NO);
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, TransparentIfWithinICOBMPWithinICO)
|
||||
{
|
||||
CheckMetadata(TransparentIfWithinICOBMPTestCase(TEST_CASE_IS_TRANSPARENT),
|
||||
BMPWithinICO::YES);
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, RLE4BMP) { CheckMetadata(RLE4BMPTestCase()); }
|
||||
TEST_F(ImageDecoderMetadata, RLE8BMP) { CheckMetadata(RLE8BMPTestCase()); }
|
||||
|
||||
TEST_F(ImageDecoderMetadata, Corrupt) { CheckMetadata(CorruptTestCase()); }
|
||||
|
||||
TEST_F(ImageDecoderMetadata, NoFrameDelayGIF)
|
||||
{
|
||||
CheckMetadata(NoFrameDelayGIFTestCase());
|
||||
}
|
||||
|
||||
TEST_F(ImageDecoderMetadata, NoFrameDelayGIFFullDecode)
|
||||
{
|
||||
ImageTestCase testCase = NoFrameDelayGIFTestCase();
|
||||
|
||||
// The previous test (NoFrameDelayGIF) verifies that we *don't* detect that
|
||||
// this test case is animated, because it has a zero frame delay for the first
|
||||
// frame. This test verifies that when we do a full decode, we detect the
|
||||
// animation at that point and successfully decode all the frames.
|
||||
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(testCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(testCase.mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
nsresult rv = inputStream->Available(&length);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Write the data into the image.
|
||||
rv = image->OnImageDataAvailable(nullptr, nullptr, inputStream, 0,
|
||||
static_cast<uint32_t>(length));
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
// Let the image know we've sent all the data.
|
||||
rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
|
||||
tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
|
||||
|
||||
// Use GetFrame() to force a sync decode of the image.
|
||||
RefPtr<SourceSurface> surface =
|
||||
image->GetFrame(imgIContainer::FRAME_CURRENT,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
|
||||
// Ensure that the image's metadata meets our expectations.
|
||||
IntSize imageSize(0, 0);
|
||||
rv = image->GetWidth(&imageSize.width);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
rv = image->GetHeight(&imageSize.height);
|
||||
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
EXPECT_EQ(testCase.mSize.width, imageSize.width);
|
||||
EXPECT_EQ(testCase.mSize.height, imageSize.height);
|
||||
|
||||
Progress imageProgress = tracker->GetProgress();
|
||||
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
|
||||
EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
|
||||
|
||||
// Ensure that we decoded both frames of the image.
|
||||
LookupResult result =
|
||||
SurfaceCache::Lookup(ImageKey(image.get()),
|
||||
RasterSurfaceKey(imageSize,
|
||||
DefaultSurfaceFlags(),
|
||||
PlaybackType::eAnimated));
|
||||
ASSERT_EQ(MatchType::EXACT, result.Type());
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(0)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
|
||||
EXPECT_TRUE(NS_SUCCEEDED(result.Surface().Seek(1)));
|
||||
EXPECT_TRUE(bool(result.Surface()));
|
||||
}
|
||||
|
|
@ -1,327 +0,0 @@
|
|||
/* -*- 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 <typename Func> void
|
||||
WithRemoveFrameRectFilter(const IntSize& aSize,
|
||||
const IntRect& aFrameRect,
|
||||
Func aFunc)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
WithFilterPipeline(decoder, Forward<Func>(aFunc),
|
||||
RemoveFrameRectConfig { aFrameRect },
|
||||
SurfaceConfig { decoder, aSize,
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
void
|
||||
AssertConfiguringRemoveFrameRectFilterFails(const IntSize& aSize,
|
||||
const IntRect& aFrameRect)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
RemoveFrameRectConfig { aFrameRect },
|
||||
SurfaceConfig { decoder, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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> 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, IntSize(100, 100),
|
||||
IntRect(0, 0, 50, 50),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
|
|
@ -1,810 +0,0 @@
|
|||
/* 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 <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "mozilla/Move.h"
|
||||
#include "SourceBuffer.h"
|
||||
#include "SurfaceCache.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
using std::min;
|
||||
|
||||
void
|
||||
ExpectChunkAndByteCount(const SourceBufferIterator& aIterator,
|
||||
uint32_t aChunks,
|
||||
size_t aBytes)
|
||||
{
|
||||
EXPECT_EQ(aChunks, aIterator.ChunkCount());
|
||||
EXPECT_EQ(aBytes, aIterator.ByteCount());
|
||||
}
|
||||
|
||||
void
|
||||
ExpectRemainingBytes(const SourceBufferIterator& aIterator, size_t aBytes)
|
||||
{
|
||||
EXPECT_TRUE(aIterator.RemainingBytesIsNoMoreThan(aBytes));
|
||||
EXPECT_TRUE(aIterator.RemainingBytesIsNoMoreThan(aBytes + 1));
|
||||
|
||||
if (aBytes > 0) {
|
||||
EXPECT_FALSE(aIterator.RemainingBytesIsNoMoreThan(0));
|
||||
EXPECT_FALSE(aIterator.RemainingBytesIsNoMoreThan(aBytes - 1));
|
||||
}
|
||||
}
|
||||
|
||||
char
|
||||
GenerateByte(size_t aIndex)
|
||||
{
|
||||
uint8_t byte = aIndex % 256;
|
||||
return *reinterpret_cast<char*>(&byte);
|
||||
}
|
||||
|
||||
void
|
||||
GenerateData(char* aOutput, size_t aOffset, size_t aLength)
|
||||
{
|
||||
for (size_t i = 0; i < aLength; ++i) {
|
||||
aOutput[i] = GenerateByte(aOffset + i);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GenerateData(char* aOutput, size_t aLength)
|
||||
{
|
||||
GenerateData(aOutput, 0, aLength);
|
||||
}
|
||||
|
||||
void
|
||||
CheckData(const char* aData, size_t aOffset, size_t aLength)
|
||||
{
|
||||
for (size_t i = 0; i < aLength; ++i) {
|
||||
ASSERT_EQ(GenerateByte(aOffset + i), aData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
enum class AdvanceMode
|
||||
{
|
||||
eAdvanceAsMuchAsPossible,
|
||||
eAdvanceByLengthExactly
|
||||
};
|
||||
|
||||
class ImageSourceBuffer : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
ImageSourceBuffer()
|
||||
: mSourceBuffer(new SourceBuffer)
|
||||
, mExpectNoResume(new ExpectNoResume)
|
||||
, mCountResumes(new CountResumes)
|
||||
{
|
||||
GenerateData(mData, sizeof(mData));
|
||||
EXPECT_FALSE(mSourceBuffer->IsComplete());
|
||||
}
|
||||
|
||||
protected:
|
||||
void CheckedAppendToBuffer(const char* aData, size_t aLength)
|
||||
{
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->Append(aData, aLength)));
|
||||
}
|
||||
|
||||
void CheckedAppendToBufferLastByteForLength(size_t aLength)
|
||||
{
|
||||
const char lastByte = GenerateByte(aLength);
|
||||
CheckedAppendToBuffer(&lastByte, 1);
|
||||
}
|
||||
|
||||
void CheckedAppendToBufferInChunks(size_t aChunkLength, size_t aTotalLength)
|
||||
{
|
||||
char* data = new char[aChunkLength];
|
||||
|
||||
size_t bytesWritten = 0;
|
||||
while (bytesWritten < aTotalLength) {
|
||||
GenerateData(data, bytesWritten, aChunkLength);
|
||||
size_t toWrite = min(aChunkLength, aTotalLength - bytesWritten);
|
||||
CheckedAppendToBuffer(data, toWrite);
|
||||
bytesWritten += toWrite;
|
||||
}
|
||||
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
void CheckedCompleteBuffer(nsresult aCompletionStatus = NS_OK)
|
||||
{
|
||||
mSourceBuffer->Complete(aCompletionStatus);
|
||||
EXPECT_TRUE(mSourceBuffer->IsComplete());
|
||||
}
|
||||
|
||||
void CheckedCompleteBuffer(SourceBufferIterator& aIterator,
|
||||
size_t aLength,
|
||||
nsresult aCompletionStatus = NS_OK)
|
||||
{
|
||||
CheckedCompleteBuffer(aCompletionStatus);
|
||||
ExpectRemainingBytes(aIterator, aLength);
|
||||
}
|
||||
|
||||
void CheckedAdvanceIteratorStateOnly(SourceBufferIterator& aIterator,
|
||||
size_t aLength,
|
||||
uint32_t aChunks,
|
||||
size_t aTotalLength,
|
||||
AdvanceMode aAdvanceMode
|
||||
= AdvanceMode::eAdvanceAsMuchAsPossible)
|
||||
{
|
||||
const size_t advanceBy = aAdvanceMode == AdvanceMode::eAdvanceAsMuchAsPossible
|
||||
? SIZE_MAX
|
||||
: aLength;
|
||||
|
||||
auto state = aIterator.AdvanceOrScheduleResume(advanceBy, mExpectNoResume);
|
||||
ASSERT_EQ(SourceBufferIterator::READY, state);
|
||||
EXPECT_TRUE(aIterator.Data());
|
||||
EXPECT_EQ(aLength, aIterator.Length());
|
||||
|
||||
ExpectChunkAndByteCount(aIterator, aChunks, aTotalLength);
|
||||
}
|
||||
|
||||
void CheckedAdvanceIteratorStateOnly(SourceBufferIterator& aIterator,
|
||||
size_t aLength)
|
||||
{
|
||||
CheckedAdvanceIteratorStateOnly(aIterator, aLength, 1, aLength);
|
||||
}
|
||||
|
||||
void CheckedAdvanceIterator(SourceBufferIterator& aIterator,
|
||||
size_t aLength,
|
||||
uint32_t aChunks,
|
||||
size_t aTotalLength,
|
||||
AdvanceMode aAdvanceMode
|
||||
= AdvanceMode::eAdvanceAsMuchAsPossible)
|
||||
{
|
||||
// Check that the iterator is in the expected state.
|
||||
CheckedAdvanceIteratorStateOnly(aIterator, aLength, aChunks,
|
||||
aTotalLength, aAdvanceMode);
|
||||
|
||||
// Check that we read the expected data. To do this, we need to compute our
|
||||
// offset in the SourceBuffer, but fortunately that's pretty easy: it's the
|
||||
// total number of bytes the iterator has advanced through, minus the length
|
||||
// of the current chunk.
|
||||
const size_t offset = aIterator.ByteCount() - aIterator.Length();
|
||||
CheckData(aIterator.Data(), offset, aIterator.Length());
|
||||
}
|
||||
|
||||
void CheckedAdvanceIterator(SourceBufferIterator& aIterator, size_t aLength)
|
||||
{
|
||||
CheckedAdvanceIterator(aIterator, aLength, 1, aLength);
|
||||
}
|
||||
|
||||
void CheckIteratorMustWait(SourceBufferIterator& aIterator,
|
||||
IResumable* aOnResume)
|
||||
{
|
||||
auto state = aIterator.AdvanceOrScheduleResume(1, aOnResume);
|
||||
EXPECT_EQ(SourceBufferIterator::WAITING, state);
|
||||
}
|
||||
|
||||
void CheckIteratorIsComplete(SourceBufferIterator& aIterator,
|
||||
uint32_t aChunks,
|
||||
size_t aTotalLength,
|
||||
nsresult aCompletionStatus = NS_OK)
|
||||
{
|
||||
ASSERT_TRUE(mSourceBuffer->IsComplete());
|
||||
auto state = aIterator.AdvanceOrScheduleResume(1, mExpectNoResume);
|
||||
ASSERT_EQ(SourceBufferIterator::COMPLETE, state);
|
||||
EXPECT_EQ(aCompletionStatus, aIterator.CompletionStatus());
|
||||
ExpectRemainingBytes(aIterator, 0);
|
||||
ExpectChunkAndByteCount(aIterator, aChunks, aTotalLength);
|
||||
}
|
||||
|
||||
void CheckIteratorIsComplete(SourceBufferIterator& aIterator,
|
||||
size_t aTotalLength)
|
||||
{
|
||||
CheckIteratorIsComplete(aIterator, 1, aTotalLength);
|
||||
}
|
||||
|
||||
AutoInitializeImageLib mInit;
|
||||
char mData[9];
|
||||
RefPtr<SourceBuffer> mSourceBuffer;
|
||||
RefPtr<ExpectNoResume> mExpectNoResume;
|
||||
RefPtr<CountResumes> mCountResumes;
|
||||
};
|
||||
|
||||
TEST_F(ImageSourceBuffer, InitialState)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// RemainingBytesIsNoMoreThan() should always return false in the initial
|
||||
// state, since we can't know the answer until Complete() has been called.
|
||||
EXPECT_FALSE(iterator.RemainingBytesIsNoMoreThan(0));
|
||||
EXPECT_FALSE(iterator.RemainingBytesIsNoMoreThan(SIZE_MAX));
|
||||
|
||||
// We haven't advanced our iterator at all, so its counters should be zero.
|
||||
ExpectChunkAndByteCount(iterator, 0, 0);
|
||||
|
||||
// Attempt to advance; we should fail, and end up in the WAITING state. We
|
||||
// expect no resumes because we don't actually append anything to the
|
||||
// SourceBuffer in this test.
|
||||
CheckIteratorMustWait(iterator, mExpectNoResume);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, ZeroLengthBufferAlwaysFails)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Complete the buffer without writing to it, providing a successful
|
||||
// completion status.
|
||||
CheckedCompleteBuffer(iterator, 0);
|
||||
|
||||
// Completing a buffer without writing to it results in an automatic failure;
|
||||
// make sure that the actual completion status we get from the iterator
|
||||
// reflects this.
|
||||
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompleteSuccess)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write a single byte to the buffer and complete the buffer. (We have to
|
||||
// write at least one byte because completing a zero length buffer always
|
||||
// fails; see the ZeroLengthBufferAlwaysFails test.)
|
||||
CheckedAppendToBuffer(mData, 1);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// We should be able to advance once (to read the single byte) and then should
|
||||
// reach the COMPLETE state with a successful status.
|
||||
CheckedAdvanceIterator(iterator, 1);
|
||||
CheckIteratorIsComplete(iterator, 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompleteFailure)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write a single byte to the buffer and complete the buffer. (We have to
|
||||
// write at least one byte because completing a zero length buffer always
|
||||
// fails; see the ZeroLengthBufferAlwaysFails test.)
|
||||
CheckedAppendToBuffer(mData, 1);
|
||||
CheckedCompleteBuffer(iterator, 1, NS_ERROR_FAILURE);
|
||||
|
||||
// Advance the iterator. Because a failing status is propagated to the
|
||||
// iterator as soon as it advances, we won't be able to read the single byte
|
||||
// that we wrote above; we go directly into the COMPLETE state.
|
||||
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, Append)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write test data to the buffer.
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(sizeof(mData))));
|
||||
CheckedAppendToBuffer(mData, sizeof(mData));
|
||||
CheckedCompleteBuffer(iterator, sizeof(mData));
|
||||
|
||||
// Verify that we can read it back via the iterator, and that the final state
|
||||
// is what we expect.
|
||||
CheckedAdvanceIterator(iterator, sizeof(mData));
|
||||
CheckIteratorIsComplete(iterator, sizeof(mData));
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, HugeAppendFails)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// We should fail to append anything bigger than what the SurfaceCache can
|
||||
// hold, so use the SurfaceCache's maximum capacity to calculate what a
|
||||
// "massive amount of data" (see below) consists of on this platform.
|
||||
ASSERT_LT(SurfaceCache::MaximumCapacity(), SIZE_MAX);
|
||||
const size_t hugeSize = SurfaceCache::MaximumCapacity() + 1;
|
||||
|
||||
// Attempt to write a massive amount of data and verify that it fails. (We'd
|
||||
// get a buffer overrun during the test if it succeeds, but if it succeeds
|
||||
// that's the least of our problems.)
|
||||
EXPECT_TRUE(NS_FAILED(mSourceBuffer->Append(mData, hugeSize)));
|
||||
EXPECT_TRUE(mSourceBuffer->IsComplete());
|
||||
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, AppendFromInputStream)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Construct an input stream with some arbitrary data. (We use test data from
|
||||
// one of the decoder tests.)
|
||||
nsCOMPtr<nsIInputStream> inputStream = LoadFile(GreenPNGTestCase().mPath);
|
||||
ASSERT_TRUE(inputStream != nullptr);
|
||||
|
||||
// Figure out how much data we have.
|
||||
uint64_t length;
|
||||
ASSERT_TRUE(NS_SUCCEEDED(inputStream->Available(&length)));
|
||||
|
||||
// Write test data to the buffer.
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->AppendFromInputStream(inputStream,
|
||||
length)));
|
||||
CheckedCompleteBuffer(iterator, length);
|
||||
|
||||
// Verify that the iterator sees the appropriate amount of data.
|
||||
CheckedAdvanceIteratorStateOnly(iterator, length);
|
||||
CheckIteratorIsComplete(iterator, length);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, AppendAfterComplete)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write test data to the buffer.
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(sizeof(mData))));
|
||||
CheckedAppendToBuffer(mData, sizeof(mData));
|
||||
CheckedCompleteBuffer(iterator, sizeof(mData));
|
||||
|
||||
// Verify that we can read it back via the iterator, and that the final state
|
||||
// is what we expect.
|
||||
CheckedAdvanceIterator(iterator, sizeof(mData));
|
||||
CheckIteratorIsComplete(iterator, sizeof(mData));
|
||||
|
||||
// Write more data to the completed buffer.
|
||||
EXPECT_TRUE(NS_FAILED(mSourceBuffer->Append(mData, sizeof(mData))));
|
||||
|
||||
// Try to read with a new iterator and verify that the new data got ignored.
|
||||
SourceBufferIterator iterator2 = mSourceBuffer->Iterator();
|
||||
CheckedAdvanceIterator(iterator2, sizeof(mData));
|
||||
CheckIteratorIsComplete(iterator2, sizeof(mData));
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, MinChunkCapacity)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write test data to the buffer using many small appends. Since
|
||||
// ExpectLength() isn't being called, we should be able to write up to
|
||||
// SourceBuffer::MIN_CHUNK_CAPACITY bytes without a second chunk being
|
||||
// allocated.
|
||||
CheckedAppendToBufferInChunks(10, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
|
||||
// Verify that the iterator sees the appropriate amount of data.
|
||||
CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
|
||||
// Write one more byte; we expect to see that it triggers an allocation.
|
||||
CheckedAppendToBufferLastByteForLength(SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// Verify that the iterator sees the new byte and a new chunk has been
|
||||
// allocated.
|
||||
CheckedAdvanceIterator(iterator, 1, 2, SourceBuffer::MIN_CHUNK_CAPACITY + 1);
|
||||
CheckIteratorIsComplete(iterator, 2, SourceBuffer::MIN_CHUNK_CAPACITY + 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, ExpectLengthDoesNotShrinkBelowMinCapacity)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the buffer,
|
||||
// but call ExpectLength() first to make SourceBuffer expect only a single
|
||||
// byte. We expect this to still result in only one chunk, because
|
||||
// regardless of ExpectLength() we won't allocate a chunk smaller than
|
||||
// MIN_CHUNK_CAPACITY bytes.
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(1)));
|
||||
CheckedAppendToBufferInChunks(10, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
CheckedCompleteBuffer(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
|
||||
// Verify that the iterator sees a single chunk.
|
||||
CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
CheckIteratorIsComplete(iterator, 1, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, ExpectLengthGrowsAboveMinCapacity)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write two times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer, calling ExpectLength() with the correct length first. We expect
|
||||
// this to result in only one chunk, because ExpectLength() allows us to
|
||||
// allocate a larger first chunk than MIN_CHUNK_CAPACITY bytes.
|
||||
const size_t length = 2 * SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
EXPECT_TRUE(NS_SUCCEEDED(mSourceBuffer->ExpectLength(length)));
|
||||
CheckedAppendToBufferInChunks(10, length);
|
||||
|
||||
// Verify that the iterator sees a single chunk.
|
||||
CheckedAdvanceIterator(iterator, length);
|
||||
|
||||
// Write one more byte; we expect to see that it triggers an allocation.
|
||||
CheckedAppendToBufferLastByteForLength(length);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// Verify that the iterator sees the new byte and a new chunk has been
|
||||
// allocated.
|
||||
CheckedAdvanceIterator(iterator, 1, 2, length + 1);
|
||||
CheckIteratorIsComplete(iterator, 2, length + 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, HugeExpectLengthFails)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// ExpectLength() should fail if the length is bigger than what the
|
||||
// SurfaceCache can hold, so use the SurfaceCache's maximum capacity to
|
||||
// calculate what a "massive amount of data" (see below) consists of on this
|
||||
// platform.
|
||||
ASSERT_LT(SurfaceCache::MaximumCapacity(), SIZE_MAX);
|
||||
const size_t hugeSize = SurfaceCache::MaximumCapacity() + 1;
|
||||
|
||||
// Attempt to write a massive amount of data and verify that it fails. (We'd
|
||||
// get a buffer overrun during the test if it succeeds, but if it succeeds
|
||||
// that's the least of our problems.)
|
||||
EXPECT_TRUE(NS_FAILED(mSourceBuffer->ExpectLength(hugeSize)));
|
||||
EXPECT_TRUE(mSourceBuffer->IsComplete());
|
||||
CheckIteratorIsComplete(iterator, 0, 0, NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, LargeAppendsAllocateOnlyOneChunk)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write two times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. We expect this to result in only one
|
||||
// chunk even though ExpectLength() wasn't called, because we should always
|
||||
// allocate a new chunk large enough to store the data we have at hand.
|
||||
constexpr size_t length = 2 * SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
char data[length];
|
||||
GenerateData(data, sizeof(data));
|
||||
CheckedAppendToBuffer(data, length);
|
||||
|
||||
// Verify that the iterator sees a single chunk.
|
||||
CheckedAdvanceIterator(iterator, length);
|
||||
|
||||
// Write one more byte; we expect to see that it triggers an allocation.
|
||||
CheckedAppendToBufferLastByteForLength(length);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// Verify that the iterator sees the new byte and a new chunk has been
|
||||
// allocated.
|
||||
CheckedAdvanceIterator(iterator, 1, 2, length + 1);
|
||||
CheckIteratorIsComplete(iterator, 2, length + 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, LargeAppendsAllocateAtMostOneChunk)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Allocate some data we'll use below.
|
||||
constexpr size_t firstWriteLength = SourceBuffer::MIN_CHUNK_CAPACITY / 2;
|
||||
constexpr size_t secondWriteLength = 3 * SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = firstWriteLength + secondWriteLength;
|
||||
char data[totalLength];
|
||||
GenerateData(data, sizeof(data));
|
||||
|
||||
// Write half of SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. This should fill half of the first chunk.
|
||||
CheckedAppendToBuffer(data, firstWriteLength);
|
||||
|
||||
// Write three times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. We expect this to result in the first of
|
||||
// the first chunk being filled and a new chunk being allocated for the
|
||||
// remainder.
|
||||
CheckedAppendToBuffer(data + firstWriteLength, secondWriteLength);
|
||||
|
||||
// Verify that the iterator sees a MIN_CHUNK_CAPACITY-length chunk.
|
||||
CheckedAdvanceIterator(iterator, SourceBuffer::MIN_CHUNK_CAPACITY);
|
||||
|
||||
// Verify that the iterator sees a second chunk of the length we expect.
|
||||
const size_t expectedSecondChunkLength =
|
||||
totalLength - SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
CheckedAdvanceIterator(iterator, expectedSecondChunkLength, 2, totalLength);
|
||||
|
||||
// Write one more byte; we expect to see that it triggers an allocation.
|
||||
CheckedAppendToBufferLastByteForLength(totalLength);
|
||||
CheckedCompleteBuffer(iterator, 1);
|
||||
|
||||
// Verify that the iterator sees the new byte and a new chunk has been
|
||||
// allocated.
|
||||
CheckedAdvanceIterator(iterator, 1, 3, totalLength + 1);
|
||||
CheckIteratorIsComplete(iterator, 3, totalLength + 1);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompactionHappensWhenBufferIsComplete)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
// Write enough data to create two chunks.
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Verify that the iterator sees two chunks.
|
||||
CheckedAdvanceIterator(iterator, chunkLength);
|
||||
CheckedAdvanceIterator(iterator, chunkLength, 2, totalLength);
|
||||
}
|
||||
|
||||
// Complete the buffer, which should trigger compaction implicitly.
|
||||
CheckedCompleteBuffer();
|
||||
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Verify that compaction happened and there's now only one chunk.
|
||||
CheckedAdvanceIterator(iterator, totalLength);
|
||||
CheckIteratorIsComplete(iterator, 1, totalLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompactionIsDelayedWhileIteratorsExist)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
{
|
||||
SourceBufferIterator outerIterator = mSourceBuffer->Iterator();
|
||||
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Write enough data to create two chunks.
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
CheckedCompleteBuffer(iterator, totalLength);
|
||||
|
||||
// Verify that the iterator sees two chunks. Since there are live
|
||||
// iterators, compaction shouldn't have happened when we completed the
|
||||
// buffer.
|
||||
CheckedAdvanceIterator(iterator, chunkLength);
|
||||
CheckedAdvanceIterator(iterator, chunkLength, 2, totalLength);
|
||||
CheckIteratorIsComplete(iterator, 2, totalLength);
|
||||
}
|
||||
|
||||
// Now |iterator| has been destroyed, but |outerIterator| still exists, so
|
||||
// we expect no compaction to have occurred at this point.
|
||||
CheckedAdvanceIterator(outerIterator, chunkLength);
|
||||
CheckedAdvanceIterator(outerIterator, chunkLength, 2, totalLength);
|
||||
CheckIteratorIsComplete(outerIterator, 2, totalLength);
|
||||
}
|
||||
|
||||
// Now all iterators have been destroyed. Since the buffer was already
|
||||
// complete, we expect compaction to happen implicitly here.
|
||||
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Verify that compaction happened and there's now only one chunk.
|
||||
CheckedAdvanceIterator(iterator, totalLength);
|
||||
CheckIteratorIsComplete(iterator, 1, totalLength);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, SourceBufferIteratorsCanBeMoved)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
// Write enough data to create two chunks. We create an iterator here to make
|
||||
// sure that compaction doesn't happen during the test.
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
CheckedCompleteBuffer(iterator, totalLength);
|
||||
|
||||
auto GetIterator = [&]{
|
||||
SourceBufferIterator lambdaIterator = mSourceBuffer->Iterator();
|
||||
CheckedAdvanceIterator(lambdaIterator, chunkLength);
|
||||
return lambdaIterator;
|
||||
};
|
||||
|
||||
// Move-construct |movedIterator| from the iterator returned from
|
||||
// GetIterator() and check that its state is as we expect.
|
||||
SourceBufferIterator movedIterator = Move(GetIterator());
|
||||
EXPECT_TRUE(movedIterator.Data());
|
||||
EXPECT_EQ(chunkLength, movedIterator.Length());
|
||||
ExpectChunkAndByteCount(movedIterator, 1, chunkLength);
|
||||
|
||||
// Make sure that we can advance the iterator.
|
||||
CheckedAdvanceIterator(movedIterator, chunkLength, 2, totalLength);
|
||||
|
||||
// Make sure that the iterator handles completion properly.
|
||||
CheckIteratorIsComplete(movedIterator, 2, totalLength);
|
||||
|
||||
// Move-assign |movedIterator| from the iterator returned from
|
||||
// GetIterator() and check that its state is as we expect.
|
||||
movedIterator = Move(GetIterator());
|
||||
EXPECT_TRUE(movedIterator.Data());
|
||||
EXPECT_EQ(chunkLength, movedIterator.Length());
|
||||
ExpectChunkAndByteCount(movedIterator, 1, chunkLength);
|
||||
|
||||
// Make sure that we can advance the iterator.
|
||||
CheckedAdvanceIterator(movedIterator, chunkLength, 2, totalLength);
|
||||
|
||||
// Make sure that the iterator handles completion properly.
|
||||
CheckIteratorIsComplete(movedIterator, 2, totalLength);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, SubchunkAdvance)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
// Write enough data to create two chunks. We create our iterator here to make
|
||||
// sure that compaction doesn't happen during the test.
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
CheckedCompleteBuffer(iterator, totalLength);
|
||||
|
||||
// Advance through the first chunk. The chunk count should not increase.
|
||||
// We check that by always passing 1 for the |aChunks| parameter of
|
||||
// CheckedAdvanceIteratorStateOnly(). We have to call CheckData() manually
|
||||
// because the offset calculation in CheckedAdvanceIterator() assumes that
|
||||
// we're advancing a chunk at a time.
|
||||
size_t offset = 0;
|
||||
while (offset < chunkLength) {
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 1, chunkLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
}
|
||||
|
||||
// Read the first byte of the second chunk. This is the point at which we
|
||||
// can't advance within the same chunk, so the chunk count should increase. We
|
||||
// check that by passing 2 for the |aChunks| parameter of
|
||||
// CheckedAdvanceIteratorStateOnly().
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
|
||||
// Read the rest of the second chunk. The chunk count should not increase.
|
||||
while (offset < totalLength) {
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
}
|
||||
|
||||
// Make sure we reached the end.
|
||||
CheckIteratorIsComplete(iterator, 2, totalLength);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, SubchunkZeroByteAdvance)
|
||||
{
|
||||
constexpr size_t chunkLength = SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = 2 * chunkLength;
|
||||
|
||||
// Write enough data to create two chunks. We create our iterator here to make
|
||||
// sure that compaction doesn't happen during the test.
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
CheckedAppendToBufferInChunks(chunkLength, totalLength);
|
||||
CheckedCompleteBuffer(iterator, totalLength);
|
||||
|
||||
// Make an initial zero-length advance. Although a zero-length advance
|
||||
// normally won't cause us to read a chunk from the SourceBuffer, we'll do so
|
||||
// if the iterator is in the initial state to keep the invariant that
|
||||
// SourceBufferIterator in the READY state always returns a non-null pointer
|
||||
// from Data().
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 0, 1, chunkLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
|
||||
// Advance through the first chunk. As in the |SubchunkAdvance| test, the
|
||||
// chunk count should not increase. We do a zero-length advance after each
|
||||
// normal advance to ensure that zero-length advances do not change the
|
||||
// iterator's position or cause a new chunk to be read.
|
||||
size_t offset = 0;
|
||||
while (offset < chunkLength) {
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 1, chunkLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 0, 1, chunkLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
}
|
||||
|
||||
// Read the first byte of the second chunk. This is the point at which we
|
||||
// can't advance within the same chunk, so the chunk count should increase. As
|
||||
// before, we do a zero-length advance afterward.
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 0, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
|
||||
// Read the rest of the second chunk. The chunk count should not increase. As
|
||||
// before, we do a zero-length advance after each normal advance.
|
||||
while (offset < totalLength) {
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 1, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
CheckData(iterator.Data(), offset++, iterator.Length());
|
||||
CheckedAdvanceIteratorStateOnly(iterator, 0, 2, totalLength,
|
||||
AdvanceMode::eAdvanceByLengthExactly);
|
||||
}
|
||||
|
||||
// Make sure we reached the end.
|
||||
CheckIteratorIsComplete(iterator, 2, totalLength);
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, SubchunkZeroByteAdvanceWithNoData)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that advancing by zero bytes still makes us enter the WAITING state.
|
||||
// This is because if we entered the READY state before reading any data at
|
||||
// all, we'd break the invariant that SourceBufferIterator::Data() always
|
||||
// returns a non-null pointer in the READY state.
|
||||
auto state = iterator.AdvanceOrScheduleResume(0, mCountResumes);
|
||||
EXPECT_EQ(SourceBufferIterator::WAITING, state);
|
||||
|
||||
// Call Complete(). This should trigger a resume.
|
||||
CheckedCompleteBuffer();
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, NullIResumable)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, nullptr);
|
||||
|
||||
// Append to the buffer, which would cause a resume if we had passed a
|
||||
// non-null IResumable.
|
||||
CheckedAppendToBuffer(mData, sizeof(mData));
|
||||
CheckedCompleteBuffer(iterator, sizeof(mData));
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, AppendTriggersResume)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, mCountResumes);
|
||||
|
||||
// Call Append(). This should trigger a resume.
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, OnlyOneResumeTriggeredPerAppend)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, mCountResumes);
|
||||
|
||||
// Allocate some data we'll use below.
|
||||
constexpr size_t firstWriteLength = SourceBuffer::MIN_CHUNK_CAPACITY / 2;
|
||||
constexpr size_t secondWriteLength = 3 * SourceBuffer::MIN_CHUNK_CAPACITY;
|
||||
constexpr size_t totalLength = firstWriteLength + secondWriteLength;
|
||||
char data[totalLength];
|
||||
GenerateData(data, sizeof(data));
|
||||
|
||||
// Write half of SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. This should fill half of the first chunk.
|
||||
// This should trigger a resume.
|
||||
CheckedAppendToBuffer(data, firstWriteLength);
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
|
||||
// Advance past the new data and wait again.
|
||||
CheckedAdvanceIterator(iterator, firstWriteLength);
|
||||
CheckIteratorMustWait(iterator, mCountResumes);
|
||||
|
||||
// Write three times SourceBuffer::MIN_CHUNK_CAPACITY bytes of test data to the
|
||||
// buffer in a single Append() call. We expect this to result in the first of
|
||||
// the first chunk being filled and a new chunk being allocated for the
|
||||
// remainder. Even though two chunks are getting written to here, only *one*
|
||||
// resume should get triggered, for a total of two in this test.
|
||||
CheckedAppendToBuffer(data + firstWriteLength, secondWriteLength);
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, CompleteTriggersResume)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, mCountResumes);
|
||||
|
||||
// Call Complete(). This should trigger a resume.
|
||||
CheckedCompleteBuffer();
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageSourceBuffer, ExpectLengthDoesNotTriggerResume)
|
||||
{
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
|
||||
// Check that we can't advance.
|
||||
CheckIteratorMustWait(iterator, mExpectNoResume);
|
||||
|
||||
// Call ExpectLength(). If this triggers a resume, |mExpectNoResume| will
|
||||
// ensure that the test fails.
|
||||
mSourceBuffer->ExpectLength(1000);
|
||||
}
|
||||
|
|
@ -1,973 +0,0 @@
|
|||
/* 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/Vector.h"
|
||||
#include "StreamingLexer.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
enum class TestState
|
||||
{
|
||||
ONE,
|
||||
TWO,
|
||||
THREE,
|
||||
UNBUFFERED,
|
||||
TRUNCATED_SUCCESS,
|
||||
TRUNCATED_FAILURE
|
||||
};
|
||||
|
||||
void
|
||||
CheckLexedData(const char* aData,
|
||||
size_t aLength,
|
||||
size_t aOffset,
|
||||
size_t aExpectedLength)
|
||||
{
|
||||
EXPECT_TRUE(aLength == aExpectedLength);
|
||||
|
||||
for (size_t i = 0; i < aLength; ++i) {
|
||||
EXPECT_EQ(aData[i], char(aOffset + i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLex(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::To(TestState::TWO, 3);
|
||||
case TestState::TWO:
|
||||
CheckLexedData(aData, aLength, 3, 3);
|
||||
return Transition::To(TestState::THREE, 3);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 6, 3);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::TRUNCATED_SUCCESS:
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::TRUNCATED_FAILURE:
|
||||
return Transition::TerminateFailure();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithUnbuffered(TestState aState, const char* aData, size_t aLength,
|
||||
Vector<char>& aUnbufferedVector)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
|
||||
case TestState::TWO:
|
||||
CheckLexedData(aUnbufferedVector.begin(), aUnbufferedVector.length(), 3, 3);
|
||||
return Transition::To(TestState::THREE, 3);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 6, 3);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::UNBUFFERED:
|
||||
EXPECT_TRUE(aLength <= 3);
|
||||
EXPECT_TRUE(aUnbufferedVector.append(aData, aLength));
|
||||
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithUnbufferedTerminate(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
|
||||
case TestState::UNBUFFERED:
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithYield(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToAfterYield(TestState::TWO);
|
||||
case TestState::TWO:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::To(TestState::THREE, 6);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 3, 6);
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithTerminateAfterYield(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToAfterYield(TestState::TWO);
|
||||
case TestState::TWO:
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthStates(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::TWO, 0);
|
||||
case TestState::TWO:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::THREE, 9);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthStatesAtEnd(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::To(TestState::TWO, 0);
|
||||
case TestState::TWO:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::THREE, 0);
|
||||
case TestState::THREE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthYield(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_EQ(0u, aLength);
|
||||
return Transition::ToAfterYield(TestState::TWO);
|
||||
case TestState::TWO:
|
||||
EXPECT_EQ(0u, aLength);
|
||||
return Transition::To(TestState::THREE, 9);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::TerminateSuccess();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthStatesUnbuffered(TestState aState,
|
||||
const char* aData,
|
||||
size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 0);
|
||||
case TestState::TWO:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::THREE, 9);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::UNBUFFERED:
|
||||
ADD_FAILURE() << "Should not enter zero-length unbuffered state";
|
||||
return Transition::TerminateFailure();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithZeroLengthStatesAfterUnbuffered(TestState aState,
|
||||
const char* aData,
|
||||
size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 9);
|
||||
case TestState::TWO:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::To(TestState::THREE, 0);
|
||||
case TestState::THREE:
|
||||
EXPECT_TRUE(aLength == 0);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::UNBUFFERED:
|
||||
CheckLexedData(aData, aLength, 0, 9);
|
||||
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
}
|
||||
|
||||
class ImageStreamingLexer : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
// Note that mLexer is configured to enter TerminalState::FAILURE immediately
|
||||
// if the input data is truncated. We don't expect that to happen in most
|
||||
// tests, so we want to detect that issue. If a test needs a different
|
||||
// behavior, we create a special StreamingLexer just for that test.
|
||||
ImageStreamingLexer()
|
||||
: mLexer(Transition::To(TestState::ONE, 3), Transition::TerminateFailure())
|
||||
, mSourceBuffer(new SourceBuffer)
|
||||
, mIterator(mSourceBuffer->Iterator())
|
||||
, mExpectNoResume(new ExpectNoResume)
|
||||
, mCountResumes(new CountResumes)
|
||||
{ }
|
||||
|
||||
protected:
|
||||
void CheckTruncatedState(StreamingLexer<TestState>& aLexer,
|
||||
TerminalState aExpectedTerminalState,
|
||||
nsresult aCompletionStatus = NS_OK)
|
||||
{
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
if (i < 2) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
} else if (i == 2) {
|
||||
mSourceBuffer->Complete(aCompletionStatus);
|
||||
}
|
||||
|
||||
LexerResult result = aLexer.Lex(mIterator, mCountResumes, DoLex);
|
||||
|
||||
if (i >= 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(aExpectedTerminalState, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
AutoInitializeImageLib mInit;
|
||||
const char mData[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
StreamingLexer<TestState> mLexer;
|
||||
RefPtr<SourceBuffer> mSourceBuffer;
|
||||
SourceBufferIterator mIterator;
|
||||
RefPtr<ExpectNoResume> mExpectNoResume;
|
||||
RefPtr<CountResumes> mCountResumes;
|
||||
};
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthData)
|
||||
{
|
||||
// Test a zero-length input.
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthDataUnbuffered)
|
||||
{
|
||||
// Test a zero-length input.
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be unbuffered.
|
||||
StreamingLexer<TestState> lexer(Transition::ToUnbuffered(TestState::ONE,
|
||||
TestState::UNBUFFERED,
|
||||
sizeof(mData)),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, StartWithTerminal)
|
||||
{
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be a terminal state. This doesn't really make sense, but we should
|
||||
// handle it.
|
||||
StreamingLexer<TestState> lexer(Transition::TerminateSuccess(),
|
||||
Transition::TerminateFailure());
|
||||
LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SingleChunk)
|
||||
{
|
||||
// Test delivering all the data at once.
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SingleChunkWithUnbuffered)
|
||||
{
|
||||
Vector<char> unbufferedVector;
|
||||
|
||||
// Test delivering all the data at once.
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
|
||||
});
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SingleChunkWithYield)
|
||||
{
|
||||
// Test delivering all the data at once.
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
|
||||
result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield);
|
||||
ASSERT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ChunkPerState)
|
||||
{
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
mSourceBuffer->Append(mData + 3 * i, 3);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex);
|
||||
|
||||
if (i == 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbuffered)
|
||||
{
|
||||
Vector<char> unbufferedVector;
|
||||
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
mSourceBuffer->Append(mData + 3 * i, 3);
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mCountResumes,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
|
||||
});
|
||||
|
||||
if (i == 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ChunkPerStateWithYield)
|
||||
{
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
mSourceBuffer->Append(mData, 3);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
|
||||
result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
|
||||
mSourceBuffer->Append(mData + 3, 6);
|
||||
result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbufferedYield)
|
||||
{
|
||||
size_t unbufferedCallCount = 0;
|
||||
Vector<char> unbufferedVector;
|
||||
auto lexerFunc = [&](TestState aState, const char* aData, size_t aLength)
|
||||
-> LexerTransition<TestState> {
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
|
||||
case TestState::TWO:
|
||||
CheckLexedData(unbufferedVector.begin(), unbufferedVector.length(), 3, 3);
|
||||
return Transition::To(TestState::THREE, 3);
|
||||
case TestState::THREE:
|
||||
CheckLexedData(aData, aLength, 6, 3);
|
||||
return Transition::TerminateSuccess();
|
||||
case TestState::UNBUFFERED:
|
||||
switch (unbufferedCallCount) {
|
||||
case 0:
|
||||
CheckLexedData(aData, aLength, 3, 3);
|
||||
EXPECT_TRUE(unbufferedVector.append(aData, 2));
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 2 bytes.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 2);
|
||||
|
||||
case 1:
|
||||
CheckLexedData(aData, aLength, 5, 1);
|
||||
EXPECT_TRUE(unbufferedVector.append(aData, 1));
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 1 byte.
|
||||
// We should end up in the TWO state.
|
||||
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
|
||||
}
|
||||
ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED";
|
||||
return Transition::TerminateFailure();
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
};
|
||||
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
mSourceBuffer->Append(mData + 3 * i, 3);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc);
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
EXPECT_EQ(0u, unbufferedCallCount);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(1u, unbufferedCallCount);
|
||||
|
||||
result = mLexer.Lex(mIterator, mCountResumes, lexerFunc);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
EXPECT_EQ(2u, unbufferedCallCount);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc);
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, OneByteChunks)
|
||||
{
|
||||
// Test delivering in one byte chunks.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex);
|
||||
|
||||
if (i == 8) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(8u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, OneByteChunksWithUnbuffered)
|
||||
{
|
||||
Vector<char> unbufferedVector;
|
||||
|
||||
// Test delivering in one byte chunks.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mCountResumes,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
|
||||
});
|
||||
|
||||
if (i == 8) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(8u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, OneByteChunksWithYield)
|
||||
{
|
||||
// Test delivering in one byte chunks.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
|
||||
switch (i) {
|
||||
case 2:
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
|
||||
result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
break;
|
||||
|
||||
case 8:
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
break;
|
||||
|
||||
default:
|
||||
EXPECT_TRUE(i < 9);
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(8u, mCountResumes->Count());
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthState)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be zero length.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStates);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStatesAtEnd)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to consume the full input.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 9),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesAtEnd);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStateWithYield)
|
||||
{
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be zero length.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
mSourceBuffer->Append(mData, 3);
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
|
||||
result = lexer.Lex(mIterator, mCountResumes, DoLexWithZeroLengthYield);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
|
||||
mSourceBuffer->Append(mData + 3, sizeof(mData) - 3);
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield);
|
||||
ASSERT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbuffered)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be both zero length and unbuffered.
|
||||
StreamingLexer<TestState> lexer(Transition::ToUnbuffered(TestState::ONE,
|
||||
TestState::UNBUFFERED,
|
||||
0),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesUnbuffered);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStateAfterUnbuffered)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be zero length.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
LexerResult result =
|
||||
lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesAfterUnbuffered);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbufferedYield)
|
||||
{
|
||||
size_t unbufferedCallCount = 0;
|
||||
auto lexerFunc = [&](TestState aState, const char* aData, size_t aLength)
|
||||
-> LexerTransition<TestState> {
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
EXPECT_EQ(0u, aLength);
|
||||
return Transition::TerminateSuccess();
|
||||
|
||||
case TestState::UNBUFFERED:
|
||||
switch (unbufferedCallCount) {
|
||||
case 0:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 0 bytes.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 0);
|
||||
|
||||
case 1:
|
||||
CheckLexedData(aData, aLength, 0, 3);
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 2 bytes.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 2);
|
||||
|
||||
case 2:
|
||||
EXPECT_EQ(1u, aLength);
|
||||
CheckLexedData(aData, aLength, 2, 1);
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 1 bytes.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 1);
|
||||
|
||||
case 3:
|
||||
CheckLexedData(aData, aLength, 3, 6);
|
||||
unbufferedCallCount++;
|
||||
|
||||
// Continue after yield, telling StreamingLexer we consumed 6 bytes.
|
||||
// We should transition to TestState::ONE when we return from the
|
||||
// yield.
|
||||
return Transition::ContinueUnbufferedAfterYield(TestState::UNBUFFERED, 6);
|
||||
}
|
||||
|
||||
ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED";
|
||||
return Transition::TerminateFailure();
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Unexpected or unhandled TestState");
|
||||
}
|
||||
};
|
||||
|
||||
// Create a special StreamingLexer for this test because we want the first
|
||||
// state to be unbuffered.
|
||||
StreamingLexer<TestState> lexer(Transition::ToUnbuffered(TestState::ONE,
|
||||
TestState::UNBUFFERED,
|
||||
sizeof(mData)),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
mSourceBuffer->Append(mData, 3);
|
||||
LexerResult result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(1u, unbufferedCallCount);
|
||||
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(2u, unbufferedCallCount);
|
||||
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(3u, unbufferedCallCount);
|
||||
|
||||
result = lexer.Lex(mIterator, mCountResumes, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
EXPECT_EQ(3u, unbufferedCallCount);
|
||||
|
||||
mSourceBuffer->Append(mData + 3, 6);
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
EXPECT_EQ(1u, mCountResumes->Count());
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
EXPECT_EQ(4u, unbufferedCallCount);
|
||||
|
||||
result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc);
|
||||
ASSERT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, TerminateSuccess)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Test that Terminate is "sticky".
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
LexerResult result =
|
||||
mLexer.Lex(iterator, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(aState == TestState::ONE);
|
||||
return Transition::TerminateSuccess();
|
||||
});
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
|
||||
SourceBufferIterator iterator2 = mSourceBuffer->Iterator();
|
||||
result =
|
||||
mLexer.Lex(iterator2, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(false); // Shouldn't get here.
|
||||
return Transition::TerminateFailure();
|
||||
});
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, TerminateFailure)
|
||||
{
|
||||
mSourceBuffer->Append(mData, sizeof(mData));
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
// Test that Terminate is "sticky".
|
||||
SourceBufferIterator iterator = mSourceBuffer->Iterator();
|
||||
LexerResult result =
|
||||
mLexer.Lex(iterator, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(aState == TestState::ONE);
|
||||
return Transition::TerminateFailure();
|
||||
});
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
|
||||
SourceBufferIterator iterator2 = mSourceBuffer->Iterator();
|
||||
result =
|
||||
mLexer.Lex(iterator2, mExpectNoResume,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(false); // Shouldn't get here.
|
||||
return Transition::TerminateFailure();
|
||||
});
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, TerminateUnbuffered)
|
||||
{
|
||||
// Test that Terminate works during an unbuffered read.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mCountResumes, DoLexWithUnbufferedTerminate);
|
||||
|
||||
if (i > 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
// We expect 3 resumes because TestState::ONE consumes 3 bytes and then
|
||||
// transitions to TestState::UNBUFFERED, which calls TerminateSuccess() as
|
||||
// soon as it receives a single byte. That's four bytes total, which are
|
||||
// delivered one at a time, requiring 3 resumes.
|
||||
EXPECT_EQ(3u, mCountResumes->Count());
|
||||
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, TerminateAfterYield)
|
||||
{
|
||||
// Test that Terminate works after yielding.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result =
|
||||
mLexer.Lex(mIterator, mCountResumes, DoLexWithTerminateAfterYield);
|
||||
|
||||
if (i > 2) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else if (i == 2) {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
// We expect 2 resumes because TestState::ONE consumes 3 bytes and then
|
||||
// yields. When the lexer resumes at TestState::TWO, which receives the same 3
|
||||
// bytes, TerminateSuccess() gets called immediately. That's three bytes
|
||||
// total, which are delivered one at a time, requiring 2 resumes.
|
||||
EXPECT_EQ(2u, mCountResumes->Count());
|
||||
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferImmediateComplete)
|
||||
{
|
||||
// Test calling SourceBuffer::Complete() without appending any data. This
|
||||
// causes the SourceBuffer to automatically have a failing completion status,
|
||||
// no matter what you pass, so we expect TerminalState::FAILURE below.
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
|
||||
LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex);
|
||||
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>());
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateSuccess)
|
||||
{
|
||||
// Test that using a terminal state (in this case TerminalState::SUCCESS) as a
|
||||
// truncated state works.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::TerminateSuccess());
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::SUCCESS);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateFailure)
|
||||
{
|
||||
// Test that using a terminal state (in this case TerminalState::FAILURE) as a
|
||||
// truncated state works.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::TerminateFailure());
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningSuccess)
|
||||
{
|
||||
// Test that a truncated state that returns TerminalState::SUCCESS works. When
|
||||
// |lexer| discovers that the data is truncated, it invokes the
|
||||
// TRUNCATED_SUCCESS state, which returns TerminalState::SUCCESS.
|
||||
// CheckTruncatedState() verifies that this happens.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::To(TestState::TRUNCATED_SUCCESS, 0));
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::SUCCESS);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningFailure)
|
||||
{
|
||||
// Test that a truncated state that returns TerminalState::FAILURE works. When
|
||||
// |lexer| discovers that the data is truncated, it invokes the
|
||||
// TRUNCATED_FAILURE state, which returns TerminalState::FAILURE.
|
||||
// CheckTruncatedState() verifies that this happens.
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::To(TestState::TRUNCATED_FAILURE, 0));
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, SourceBufferTruncatedFailingCompleteStatus)
|
||||
{
|
||||
// Test that calling SourceBuffer::Complete() with a failing status results in
|
||||
// an immediate TerminalState::FAILURE result. (Note that |lexer|'s truncated
|
||||
// state is TerminalState::SUCCESS, so if we ignore the failing status, the
|
||||
// test will fail.)
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3),
|
||||
Transition::TerminateSuccess());
|
||||
|
||||
CheckTruncatedState(lexer, TerminalState::FAILURE, NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
TEST_F(ImageStreamingLexer, NoSourceBufferResumable)
|
||||
{
|
||||
// Test delivering in one byte chunks with no IResumable.
|
||||
for (unsigned i = 0; i < 9; ++i) {
|
||||
mSourceBuffer->Append(mData + i, 1);
|
||||
LexerResult result = mLexer.Lex(mIterator, nullptr, DoLex);
|
||||
|
||||
if (i == 8) {
|
||||
EXPECT_TRUE(result.is<TerminalState>());
|
||||
EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>());
|
||||
} else {
|
||||
EXPECT_TRUE(result.is<Yield>());
|
||||
EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>());
|
||||
}
|
||||
}
|
||||
|
||||
mSourceBuffer->Complete(NS_OK);
|
||||
}
|
||||
|
|
@ -1,508 +0,0 @@
|
|||
/* -*- 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 <typename T>
|
||||
static SurfacePipe SurfacePipeFromPipeline(T&& aPipeline)
|
||||
{
|
||||
return SurfacePipe { Move(aPipeline) };
|
||||
}
|
||||
|
||||
private:
|
||||
TestSurfacePipeFactory() { }
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
void
|
||||
CheckSurfacePipeMethodResults(SurfacePipe* aPipe,
|
||||
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<SurfaceInvalidRect> invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isSome());
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(IntRect(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<uint32_t>([&]() {
|
||||
++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(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
|
||||
|
||||
aPipe->ResetToFirstRow();
|
||||
EXPECT_FALSE(aPipe->IsSurfaceFinished());
|
||||
invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isNothing());
|
||||
}
|
||||
|
||||
void
|
||||
CheckPalettedSurfacePipeMethodResults(SurfacePipe* aPipe,
|
||||
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<SurfaceInvalidRect> invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isSome());
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mOutputSpaceRect);
|
||||
|
||||
// Check the generated image.
|
||||
CheckGeneratedPalettedImage(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<uint8_t>([&]() {
|
||||
++count;
|
||||
return AsVariant(uint8_t(0));
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u * 100u, count);
|
||||
|
||||
EXPECT_TRUE(aPipe->IsSurfaceFinished());
|
||||
invalidRect = aPipe->TakeInvalidRect();
|
||||
EXPECT_TRUE(invalidRect.isSome());
|
||||
EXPECT_EQ(IntRect(0, 0, 100, 100), invalidRect->mInputSpaceRect);
|
||||
EXPECT_EQ(IntRect(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> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
auto sink = MakeUnique<SurfaceSink>();
|
||||
nsresult rv =
|
||||
sink->Configure(SurfaceConfig { decoder, IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
ASSERT_TRUE(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<uint32_t>([&]() {
|
||||
++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 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, PalettedSurfacePipe)
|
||||
{
|
||||
// Create a SurfacePipe containing a PalettedSurfaceSink.
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
auto sink = MakeUnique<PalettedSurfaceSink>();
|
||||
nsresult rv =
|
||||
sink->Configure(PalettedSurfaceConfig { decoder, IntSize(100, 100),
|
||||
IntRect(0, 0, 100, 100),
|
||||
SurfaceFormat::B8G8R8A8,
|
||||
8, false });
|
||||
ASSERT_TRUE(NS_SUCCEEDED(rv));
|
||||
|
||||
SurfacePipe pipe = TestSurfacePipeFactory::SurfacePipeFromPipeline(sink);
|
||||
|
||||
// Test that WritePixels() gets passed through to the underlying pipeline.
|
||||
{
|
||||
uint32_t count = 0;
|
||||
auto result = pipe.WritePixels<uint8_t>([&]() {
|
||||
++count;
|
||||
return AsVariant(uint8_t(255));
|
||||
});
|
||||
EXPECT_EQ(WriteState::FINISHED, result);
|
||||
EXPECT_EQ(100u * 100u, count);
|
||||
CheckPalettedSurfacePipeMethodResults(&pipe, decoder);
|
||||
}
|
||||
|
||||
// Create a buffer the same size as one row of the surface, containing all
|
||||
// 255 pixels. We'll use this for the WriteBuffer() tests.
|
||||
uint8_t buffer[100];
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
buffer[i] = 255;
|
||||
}
|
||||
|
||||
// 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);
|
||||
CheckPalettedSurfacePipeMethodResults(&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);
|
||||
CheckPalettedSurfacePipeMethodResults(&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);
|
||||
CheckPalettedSurfacePipeMethodResults(&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> 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<uint32_t> { /* mProgressiveDisplay = */ true },
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, IntSize(25, 25),
|
||||
SurfaceFormat::B8G8R8A8, 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> 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, IntSize(20, 20),
|
||||
SurfaceFormat::B8G8R8A8, 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> 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(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::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, IntSize(20, 20),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectWritePixels)
|
||||
{
|
||||
RefPtr<Decoder> 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<uint32_t> { /* mProgressiveDisplay = */ true },
|
||||
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
|
||||
SurfaceConfig { decoder, IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, DeinterlaceRemoveFrameRectDownscaleWritePixels)
|
||||
{
|
||||
RefPtr<Decoder> 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<uint32_t> { /* mProgressiveDisplay = */ true },
|
||||
RemoveFrameRectConfig { IntRect(50, 50, 100, 100) },
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, IntSize(20, 20),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedRemoveFrameRectDownscaleFails)
|
||||
{
|
||||
RefPtr<Decoder> 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, IntSize(100, 100),
|
||||
IntRect(0, 0, 50, 50),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, ConfiguringPalettedDeinterlaceDownscaleFails)
|
||||
{
|
||||
RefPtr<Decoder> decoder = CreateTrivialDecoder();
|
||||
ASSERT_TRUE(decoder != nullptr);
|
||||
|
||||
// This is an invalid pipeline for paletted images, so configuration should
|
||||
// fail.
|
||||
AssertConfiguringPipelineFails(decoder,
|
||||
DeinterlacingConfig<uint8_t> { /* mProgressiveDisplay = */ true},
|
||||
DownscalingConfig { IntSize(100, 100),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
PalettedSurfaceConfig { decoder, IntSize(100, 100),
|
||||
IntRect(0, 0, 20, 20),
|
||||
SurfaceFormat::B8G8R8A8, 8,
|
||||
false });
|
||||
}
|
||||
|
||||
TEST_F(ImageSurfacePipeIntegration, ConfiguringHugeDeinterlacingBufferFails)
|
||||
{
|
||||
RefPtr<Decoder> 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<uint32_t> { /* mProgressiveDisplay = */ true},
|
||||
DownscalingConfig { IntSize(60000, 60000),
|
||||
SurfaceFormat::B8G8R8A8 },
|
||||
SurfaceConfig { decoder, IntSize(600, 600),
|
||||
SurfaceFormat::B8G8R8A8, false });
|
||||
}
|
||||
|
Before Width: | Height: | Size: 434 B |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 223 B |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 1,015 B |
|
Before Width: | Height: | Size: 317 B |
|
Before Width: | Height: | Size: 364 B |
|
Before Width: | Height: | Size: 49 B |
|
Before Width: | Height: | Size: 53 B |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 156 B |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 255 B |
|
Before Width: | Height: | Size: 54 B |
|
|
@ -1,79 +0,0 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
Library('imagetest')
|
||||
|
||||
UNIFIED_SOURCES = [
|
||||
'Common.cpp',
|
||||
'TestADAM7InterpolatingFilter.cpp',
|
||||
'TestCopyOnWrite.cpp',
|
||||
'TestDecoders.cpp',
|
||||
'TestDecodeToSurface.cpp',
|
||||
'TestDeinterlacingFilter.cpp',
|
||||
'TestLoader.cpp',
|
||||
'TestMetadata.cpp',
|
||||
'TestRemoveFrameRectFilter.cpp',
|
||||
'TestSourceBuffer.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 += [
|
||||
'animated-with-extra-image-sub-blocks.gif',
|
||||
'corrupt-with-bad-bmp-height.ico',
|
||||
'corrupt-with-bad-bmp-width.ico',
|
||||
'corrupt.jpg',
|
||||
'downscaled.bmp',
|
||||
'downscaled.gif',
|
||||
'downscaled.ico',
|
||||
'downscaled.icon',
|
||||
'downscaled.jpg',
|
||||
'downscaled.png',
|
||||
'first-frame-green.gif',
|
||||
'first-frame-green.png',
|
||||
'first-frame-padding.gif',
|
||||
'green-1x1-truncated.gif',
|
||||
'green.bmp',
|
||||
'green.gif',
|
||||
'green.ico',
|
||||
'green.icon',
|
||||
'green.jpg',
|
||||
'green.png',
|
||||
'invalid-truncated-metadata.bmp',
|
||||
'no-frame-delay.gif',
|
||||
'rle4.bmp',
|
||||
'rle8.bmp',
|
||||
'transparent-ico-with-and-mask.ico',
|
||||
'transparent-if-within-ico.bmp',
|
||||
'transparent.gif',
|
||||
'transparent.png',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/base',
|
||||
'/gfx/2d',
|
||||
'/image',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
||||
|
||||
if CONFIG['GNU_CXX']:
|
||||
CXXFLAGS += ['-Wno-error=shadow']
|
||||
|
Before Width: | Height: | Size: 317 B |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 419 B |