image/test/gtest/TestFrameAnimator.cpp
author Andrew Osmond <aosmond@mozilla.com>
Fri, 02 Nov 2018 15:28:17 -0400
changeset 500768 5fb87d2312ea4881026ecaac6488fbdc2e4a7725
child 507657 09c71a7cf75aeaf2963050e315276fb9a866fd62
permissions -rw-r--r--
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 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);
}