зеркало из https://github.com/mozilla/gecko-dev.git
807 строки
28 KiB
C++
807 строки
28 KiB
C++
/* -*- 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 "gfxPrefs.h"
|
|
#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;
|
|
|
|
static bool sImageLibInitialized = false;
|
|
|
|
AutoInitializeImageLib::AutoInitializeImageLib()
|
|
{
|
|
if (MOZ_LIKELY(sImageLibInitialized)) {
|
|
return;
|
|
}
|
|
|
|
EXPECT_TRUE(NS_IsMainThread());
|
|
sImageLibInitialized = true;
|
|
|
|
// Force sRGB to be consistent with reftests.
|
|
nsresult rv = Preferences::SetBool("gfx.color_management.force_srgb", true);
|
|
EXPECT_TRUE(rv == NS_OK);
|
|
|
|
// Ensure WebP is enabled to run decoder tests.
|
|
rv = Preferences::SetBool("image.webp.enabled", true);
|
|
EXPECT_TRUE(rv == NS_OK);
|
|
|
|
// Ensure that ImageLib services are initialized.
|
|
nsCOMPtr<imgITools> imgTools = do_CreateInstance("@mozilla.org/image/tools;1");
|
|
EXPECT_TRUE(imgTools != nullptr);
|
|
|
|
// Ensure gfxPlatform is initialized.
|
|
gfxPlatform::GetPlatform();
|
|
|
|
// Depending on initialization order, it is possible that our pref changes
|
|
// have not taken effect yet because there are pending gfx-related events on
|
|
// the main thread.
|
|
SpinPendingEvents();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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; \
|
|
}
|
|
|
|
void
|
|
SpinPendingEvents()
|
|
{
|
|
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
|
|
EXPECT_TRUE(mainThread != nullptr);
|
|
|
|
bool processed;
|
|
do {
|
|
processed = false;
|
|
nsresult rv = mainThread->ProcessNextEvent(false, &processed);
|
|
EXPECT_TRUE(NS_SUCCEEDED(rv));
|
|
} while (processed);
|
|
}
|
|
|
|
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.forget(), 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);
|
|
|
|
DataSourceSurface::ScopedMap mapping(dataSurface,
|
|
DataSourceSurface::MapType::READ);
|
|
ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
|
|
ASSERT_EQ_OR_RETURN(mapping.GetStride(), surfaceSize.width * 4, false);
|
|
|
|
uint8_t* data = mapping.GetData();
|
|
ASSERT_TRUE_OR_RETURN(data != nullptr, false);
|
|
|
|
BGRAColor pmColor = aColor.Premultiply();
|
|
int32_t rowLength = mapping.GetStride();
|
|
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(pmColor.mBlue - data[i + 0]), aFuzz, false);
|
|
ASSERT_LE_OR_RETURN(abs(pmColor.mGreen - data[i + 1]), aFuzz, false);
|
|
ASSERT_LE_OR_RETURN(abs(pmColor.mRed - data[i + 2]), aFuzz, false);
|
|
ASSERT_LE_OR_RETURN(abs(pmColor.mAlpha - data[i + 3]), aFuzz, false);
|
|
} else {
|
|
ASSERT_EQ_OR_RETURN(pmColor.mBlue, data[i + 0], false);
|
|
ASSERT_EQ_OR_RETURN(pmColor.mGreen, data[i + 1], false);
|
|
ASSERT_EQ_OR_RETURN(pmColor.mRed, data[i + 2], false);
|
|
ASSERT_EQ_OR_RETURN(pmColor.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);
|
|
|
|
DataSourceSurface::ScopedMap mapping(dataSurface,
|
|
DataSourceSurface::MapType::READ);
|
|
ASSERT_TRUE_OR_RETURN(mapping.IsMapped(), false);
|
|
ASSERT_EQ_OR_RETURN(mapping.GetStride(), surfaceSize.width * 4, false);
|
|
|
|
uint8_t* data = mapping.GetData();
|
|
ASSERT_TRUE_OR_RETURN(data != nullptr, false);
|
|
|
|
int32_t rowLength = mapping.GetStride();
|
|
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");
|
|
auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>();
|
|
RefPtr<Decoder> decoder =
|
|
DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
|
|
DefaultDecoderFlags(),
|
|
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();
|
|
CheckGeneratedSurface(surface, aRect,
|
|
BGRAColor::Green(),
|
|
BGRAColor::Transparent(),
|
|
aFuzz);
|
|
}
|
|
|
|
void
|
|
CheckGeneratedSurface(SourceSurface* aSurface,
|
|
const IntRect& aRect,
|
|
const BGRAColor& aInnerColor,
|
|
const BGRAColor& aOuterColor,
|
|
uint8_t aFuzz /* = 0 */)
|
|
{
|
|
const IntSize surfaceSize = aSurface->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 the inner color. (Region 'C'.)
|
|
EXPECT_TRUE(RectIsSolidColor(aSurface, aRect, aInnerColor, aFuzz));
|
|
|
|
// Check that the area above the output rect is the outer color. (Region 'A'.)
|
|
EXPECT_TRUE(RectIsSolidColor(aSurface,
|
|
IntRect(0, 0, surfaceSize.width, aRect.Y()),
|
|
aOuterColor, aFuzz));
|
|
|
|
// Check that the area to the left of the output rect is the outer color. (Region 'B'.)
|
|
EXPECT_TRUE(RectIsSolidColor(aSurface,
|
|
IntRect(0, aRect.Y(), aRect.X(), aRect.YMost()),
|
|
aOuterColor, aFuzz));
|
|
|
|
// Check that the area to the right of the output rect is the outer color. (Region 'D'.)
|
|
const int32_t widthOnRight = surfaceSize.width - aRect.XMost();
|
|
EXPECT_TRUE(RectIsSolidColor(aSurface,
|
|
IntRect(aRect.XMost(), aRect.Y(), widthOnRight, aRect.YMost()),
|
|
aOuterColor, aFuzz));
|
|
|
|
// Check that the area below the output rect is the outer color. (Region 'E'.)
|
|
const int32_t heightBelow = surfaceSize.height - aRect.YMost();
|
|
EXPECT_TRUE(RectIsSolidColor(aSurface,
|
|
IntRect(0, aRect.YMost(), surfaceSize.width, heightBelow),
|
|
aOuterColor, 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,
|
|
const Maybe<IntRect>& aOutputRect /* = Nothing() */,
|
|
const Maybe<IntRect>& aInputRect /* = Nothing() */,
|
|
const Maybe<IntRect>& aInputWriteRect /* = Nothing() */,
|
|
const 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,
|
|
const Maybe<IntRect>& aOutputRect /* = Nothing() */,
|
|
const Maybe<IntRect>& aInputRect /* = Nothing() */,
|
|
const Maybe<IntRect>& aInputWriteRect /* = Nothing() */,
|
|
const 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 GreenWebPTestCase()
|
|
{
|
|
return ImageTestCase("green.webp", "image/webp", IntSize(100, 100));
|
|
}
|
|
|
|
ImageTestCase LargeWebPTestCase()
|
|
{
|
|
return ImageTestCase("large.webp", "image/webp", IntSize(1200, 660),
|
|
TEST_CASE_IGNORE_OUTPUT);
|
|
}
|
|
|
|
ImageTestCase GreenWebPIccSrgbTestCase()
|
|
{
|
|
return ImageTestCase("green.icc_srgb.webp", "image/webp", IntSize(100, 100));
|
|
}
|
|
|
|
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 GreenFirstFrameAnimatedWebPTestCase()
|
|
{
|
|
return ImageTestCase("first-frame-green.webp", "image/webp", IntSize(100, 100),
|
|
TEST_CASE_IS_ANIMATED);
|
|
}
|
|
|
|
ImageTestCase BlendAnimatedGIFTestCase()
|
|
{
|
|
return ImageTestCase("blend.gif", "image/gif", IntSize(100, 100),
|
|
TEST_CASE_IS_ANIMATED);
|
|
}
|
|
|
|
ImageTestCase BlendAnimatedPNGTestCase()
|
|
{
|
|
return ImageTestCase("blend.png", "image/png", IntSize(100, 100),
|
|
TEST_CASE_IS_TRANSPARENT | TEST_CASE_IS_ANIMATED);
|
|
}
|
|
|
|
ImageTestCase BlendAnimatedWebPTestCase()
|
|
{
|
|
return ImageTestCase("blend.webp", "image/webp", 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 CorruptICOWithBadBppTestCase()
|
|
{
|
|
// This test case is an ICO with a BPP (15) in the ICO header which differs
|
|
// from that in the BMP header itself (1). It should ignore the ICO BPP when
|
|
// the BMP BPP is available and thus correctly decode the image.
|
|
return ImageTestCase("corrupt-with-bad-ico-bpp.ico", "image/x-icon",
|
|
IntSize(100, 100), TEST_CASE_IS_TRANSPARENT);
|
|
}
|
|
|
|
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 DownscaledWebPTestCase()
|
|
{
|
|
return ImageTestCase("downscaled.webp", "image/webp", IntSize(100, 100),
|
|
IntSize(20, 20));
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
ImageTestCase LargeICOWithBMPTestCase()
|
|
{
|
|
return ImageTestCase("green-large-bmp.ico", "image/x-icon", IntSize(256, 256),
|
|
TEST_CASE_IS_TRANSPARENT);
|
|
}
|
|
|
|
ImageTestCase LargeICOWithPNGTestCase()
|
|
{
|
|
return ImageTestCase("green-large-png.ico", "image/x-icon", IntSize(512, 512),
|
|
TEST_CASE_IS_TRANSPARENT);
|
|
}
|
|
|
|
ImageTestCase GreenMultipleSizesICOTestCase()
|
|
{
|
|
return ImageTestCase("green-multiple-sizes.ico", "image/x-icon",
|
|
IntSize(256, 256));
|
|
}
|
|
|
|
} // namespace image
|
|
} // namespace mozilla
|