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:
Andrew Osmond 2018-11-02 15:28:17 -04:00
Родитель e94e7c21a8
Коммит efc0d9172c
8 изменённых файлов: 222 добавлений и 16 удалений

Просмотреть файл

@ -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);
}

Двоичные данные
image/test/gtest/blend.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 412 B

Двоичные данные
image/test/gtest/blend.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 339 B

Двоичные данные
image/test/gtest/blend.webp Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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',