зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1504237 - Ensure partial animated frames always use BGRA instead of BGRX. r=tnikkel
For decoders which produce unpaletted partial frames (APNG, WebP), the surface format should always be BGRA. These frames while partial, are the same size as the output size of the animated image. When FrameAnimator performs the blend with the compositing frame, it expects all pixels we don't care about to be set to fully transparent. If it is BGRX, they will be set to solid white instead. Differential Revision: https://phabricator.services.mozilla.com/D10753
This commit is contained in:
Родитель
e94e7c21a8
Коммит
efc0d9172c
|
@ -532,7 +532,8 @@ nsWebPDecoder::ReadMultiple(WebPDemuxer* aDemuxer, bool aIsComplete)
|
|||
break;
|
||||
}
|
||||
|
||||
mFormat = iter.has_alpha ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
|
||||
mFormat = iter.has_alpha || mCurrentFrame > 0 ? SurfaceFormat::B8G8R8A8
|
||||
: SurfaceFormat::B8G8R8X8;
|
||||
mTimeout = FrameTimeout::FromRawMilliseconds(iter.duration);
|
||||
nsIntRect frameRect(iter.x_offset, iter.y_offset, iter.width, iter.height);
|
||||
|
||||
|
|
|
@ -336,7 +336,20 @@ CheckGeneratedImage(Decoder* aDecoder,
|
|||
{
|
||||
RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
|
||||
RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
|
||||
const IntSize surfaceSize = surface->GetSize();
|
||||
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
|
||||
|
@ -350,30 +363,30 @@ CheckGeneratedImage(Decoder* aDecoder,
|
|||
// | E |
|
||||
// +---------------------------+
|
||||
|
||||
// Check that the output rect itself is green. (Region 'C'.)
|
||||
EXPECT_TRUE(RectIsSolidColor(surface, aRect, BGRAColor::Green(), aFuzz));
|
||||
// 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 transparent. (Region 'A'.)
|
||||
EXPECT_TRUE(RectIsSolidColor(surface,
|
||||
// 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()),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
aOuterColor, aFuzz));
|
||||
|
||||
// Check that the area to the left of the output rect is transparent. (Region 'B'.)
|
||||
EXPECT_TRUE(RectIsSolidColor(surface,
|
||||
// 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()),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
aOuterColor, aFuzz));
|
||||
|
||||
// Check that the area to the right of the output rect is transparent. (Region 'D'.)
|
||||
// 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(surface,
|
||||
EXPECT_TRUE(RectIsSolidColor(aSurface,
|
||||
IntRect(aRect.XMost(), aRect.Y(), widthOnRight, aRect.YMost()),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
aOuterColor, aFuzz));
|
||||
|
||||
// Check that the area below the output rect is transparent. (Region 'E'.)
|
||||
// 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(surface,
|
||||
EXPECT_TRUE(RectIsSolidColor(aSurface,
|
||||
IntRect(0, aRect.YMost(), surfaceSize.width, heightBelow),
|
||||
BGRAColor::Transparent(), aFuzz));
|
||||
aOuterColor, aFuzz));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -590,6 +603,24 @@ ImageTestCase GreenFirstFrameAnimatedWebPTestCase()
|
|||
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),
|
||||
|
|
|
@ -338,6 +338,23 @@ void CheckGeneratedImage(Decoder* aDecoder,
|
|||
const gfx::IntRect& aRect,
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
/**
|
||||
* Checks a generated surface for correctness. Reports any unexpected deviation
|
||||
* from the expected image as GTest failures.
|
||||
*
|
||||
* @param aSurface The surface to check.
|
||||
* @param aRect The region in the space of the output surface that the filter
|
||||
* pipeline will actually write to.
|
||||
* @param aInnerColor Check that pixels inside of aRect are this color.
|
||||
* @param aOuterColor Check that pixels outside of aRect are this color.
|
||||
* @param aFuzz The amount of fuzz to use in pixel comparisons.
|
||||
*/
|
||||
void CheckGeneratedSurface(gfx::SourceSurface* aSurface,
|
||||
const gfx::IntRect& aRect,
|
||||
const BGRAColor& aInnerColor,
|
||||
const BGRAColor& aOuterColor,
|
||||
uint8_t aFuzz = 0);
|
||||
|
||||
/**
|
||||
* Checks a generated paletted image for correctness. Reports any unexpected
|
||||
* deviation from the expected image as GTest failures.
|
||||
|
@ -444,6 +461,10 @@ ImageTestCase GreenFirstFrameAnimatedGIFTestCase();
|
|||
ImageTestCase GreenFirstFrameAnimatedPNGTestCase();
|
||||
ImageTestCase GreenFirstFrameAnimatedWebPTestCase();
|
||||
|
||||
ImageTestCase BlendAnimatedGIFTestCase();
|
||||
ImageTestCase BlendAnimatedPNGTestCase();
|
||||
ImageTestCase BlendAnimatedWebPTestCase();
|
||||
|
||||
ImageTestCase CorruptTestCase();
|
||||
ImageTestCase CorruptBMPWithTruncatedHeader();
|
||||
ImageTestCase CorruptICOWithBadBMPWidthTestCase();
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
/* 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 "AnimationSurfaceProvider.h"
|
||||
#include "Decoder.h"
|
||||
#include "RasterImage.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla::image;
|
||||
|
||||
static void
|
||||
CheckFrameAnimatorBlendResults(const ImageTestCase& aTestCase,
|
||||
RasterImage* aImage)
|
||||
{
|
||||
// Allow the animation to actually begin.
|
||||
aImage->IncrementAnimationConsumers();
|
||||
|
||||
// Initialize for the first frame so we can advance.
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
aImage->RequestRefresh(now);
|
||||
|
||||
RefPtr<SourceSurface> surface =
|
||||
aImage->GetFrame(imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_NONE);
|
||||
ASSERT_TRUE(surface != nullptr);
|
||||
|
||||
CheckGeneratedSurface(surface, IntRect(0, 0, 50, 50),
|
||||
BGRAColor::Transparent(),
|
||||
BGRAColor::Red());
|
||||
|
||||
// Advance to the next/final frame.
|
||||
now = TimeStamp::Now() + TimeDuration::FromMilliseconds(500);
|
||||
aImage->RequestRefresh(now);
|
||||
|
||||
surface =
|
||||
aImage->GetFrame(imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_NONE);
|
||||
ASSERT_TRUE(surface != nullptr);
|
||||
CheckGeneratedSurface(surface, IntRect(0, 0, 50, 50),
|
||||
BGRAColor::Green(),
|
||||
BGRAColor::Red());
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
static void
|
||||
WithFrameAnimatorDecode(const ImageTestCase& aTestCase,
|
||||
bool aBlendFilter,
|
||||
Func aResultChecker)
|
||||
{
|
||||
// Create an image.
|
||||
RefPtr<Image> image =
|
||||
ImageFactory::CreateAnonymousImage(nsDependentCString(aTestCase.mMimeType));
|
||||
ASSERT_TRUE(!image->HasError());
|
||||
|
||||
NotNull<RefPtr<RasterImage>> rasterImage =
|
||||
WrapNotNull(static_cast<RasterImage*>(image.get()));
|
||||
|
||||
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 first, because otherwise RasterImage will get
|
||||
// unhappy about finding out the image is animated during a full decode.
|
||||
DecoderType decoderType =
|
||||
DecoderFactory::GetDecoderType(aTestCase.mMimeType);
|
||||
RefPtr<IDecodingTask> task =
|
||||
DecoderFactory::CreateMetadataDecoder(decoderType, rasterImage, sourceBuffer);
|
||||
ASSERT_TRUE(task != nullptr);
|
||||
|
||||
// Run the metadata decoder synchronously.
|
||||
task->Run();
|
||||
task = nullptr;
|
||||
|
||||
// Create an AnimationSurfaceProvider which will manage the decoding process
|
||||
// and make this decoder's output available in the surface cache.
|
||||
DecoderFlags decoderFlags = DefaultDecoderFlags();
|
||||
if (aBlendFilter) {
|
||||
decoderFlags |= DecoderFlags::BLEND_ANIMATION;
|
||||
}
|
||||
SurfaceFlags surfaceFlags = DefaultSurfaceFlags();
|
||||
rv = DecoderFactory::CreateAnimationDecoder(decoderType, rasterImage, sourceBuffer, aTestCase.mSize,
|
||||
decoderFlags, surfaceFlags, 0, getter_AddRefs(task));
|
||||
EXPECT_EQ(rv, NS_OK);
|
||||
ASSERT_TRUE(task != nullptr);
|
||||
|
||||
// Run the full decoder synchronously.
|
||||
task->Run();
|
||||
|
||||
// Call the lambda to verify the expected results.
|
||||
aResultChecker(rasterImage.get());
|
||||
}
|
||||
|
||||
static void
|
||||
CheckFrameAnimatorBlend(const ImageTestCase& aTestCase, bool aBlendFilter)
|
||||
{
|
||||
WithFrameAnimatorDecode(aTestCase, aBlendFilter, [&](RasterImage* aImage) {
|
||||
CheckFrameAnimatorBlendResults(aTestCase, aImage);
|
||||
});
|
||||
}
|
||||
|
||||
class ImageFrameAnimator : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
AutoInitializeImageLib mInit;
|
||||
};
|
||||
|
||||
TEST_F(ImageFrameAnimator, BlendGIFWithAnimator)
|
||||
{
|
||||
CheckFrameAnimatorBlend(BlendAnimatedGIFTestCase(), /* aBlendFilter */ false);
|
||||
}
|
||||
|
||||
TEST_F(ImageFrameAnimator, BlendGIFWithFilter)
|
||||
{
|
||||
CheckFrameAnimatorBlend(BlendAnimatedGIFTestCase(), /* aBlendFilter */ true);
|
||||
}
|
||||
|
||||
TEST_F(ImageFrameAnimator, BlendPNGWithAnimator)
|
||||
{
|
||||
CheckFrameAnimatorBlend(BlendAnimatedPNGTestCase(), /* aBlendFilter */ false);
|
||||
}
|
||||
|
||||
TEST_F(ImageFrameAnimator, BlendPNGWithFilter)
|
||||
{
|
||||
CheckFrameAnimatorBlend(BlendAnimatedPNGTestCase(), /* aBlendFilter */ true);
|
||||
}
|
||||
|
||||
TEST_F(ImageFrameAnimator, BlendWebPWithAnimator)
|
||||
{
|
||||
CheckFrameAnimatorBlend(BlendAnimatedWebPTestCase(), /* aBlendFilter */ false);
|
||||
}
|
||||
|
||||
TEST_F(ImageFrameAnimator, BlendWebPWithFilter)
|
||||
{
|
||||
CheckFrameAnimatorBlend(BlendAnimatedWebPTestCase(), /* aBlendFilter */ true);
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 412 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 339 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 160 B |
|
@ -16,6 +16,7 @@ UNIFIED_SOURCES = [
|
|||
'TestDecoders.cpp',
|
||||
'TestDecodeToSurface.cpp',
|
||||
'TestDeinterlacingFilter.cpp',
|
||||
'TestFrameAnimator.cpp',
|
||||
'TestLoader.cpp',
|
||||
'TestMetadata.cpp',
|
||||
'TestRemoveFrameRectFilter.cpp',
|
||||
|
@ -38,6 +39,9 @@ SOURCES += [
|
|||
|
||||
TEST_HARNESS_FILES.gtest += [
|
||||
'animated-with-extra-image-sub-blocks.gif',
|
||||
'blend.gif',
|
||||
'blend.png',
|
||||
'blend.webp',
|
||||
'corrupt-with-bad-bmp-height.ico',
|
||||
'corrupt-with-bad-bmp-width.ico',
|
||||
'corrupt-with-bad-ico-bpp.ico',
|
||||
|
|
Загрузка…
Ссылка в новой задаче