/* -*- 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 "skia/include/core/SkColorPriv.h"  // for SkPMSrcOver
#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;

static already_AddRefed<image::Decoder> CreateTrivialBlendingDecoder() {
  DecoderType decoderType = DecoderFactory::GetDecoderType("image/gif");
  DecoderFlags decoderFlags = DefaultDecoderFlags();
  SurfaceFlags surfaceFlags = DefaultSurfaceFlags();
  auto sourceBuffer = MakeNotNull<RefPtr<SourceBuffer>>();
  return DecoderFactory::CreateAnonymousDecoder(
      decoderType, sourceBuffer, Nothing(), decoderFlags, surfaceFlags);
}

template <typename Func>
RawAccessFrameRef WithBlendAnimationFilter(image::Decoder* aDecoder,
                                           const AnimationParams& aAnimParams,
                                           const IntSize& aOutputSize,
                                           Func aFunc) {
  DecoderTestHelper decoderHelper(aDecoder);

  if (!aDecoder->HasAnimation()) {
    decoderHelper.PostIsAnimated(aAnimParams.mTimeout);
  }

  BlendAnimationConfig blendAnim{aDecoder};
  SurfaceConfig surfaceSink{aDecoder, aOutputSize, SurfaceFormat::OS_RGBA,
                            false, Some(aAnimParams)};

  auto func = [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
    aFunc(aDecoder, aFilter);
  };

  WithFilterPipeline(aDecoder, func, false, blendAnim, surfaceSink);

  RawAccessFrameRef current = aDecoder->GetCurrentFrameRef();
  if (current) {
    decoderHelper.PostFrameStop(Opacity::SOME_TRANSPARENCY);
  }

  return current;
}

void AssertConfiguringBlendAnimationFilterFails(const IntRect& aFrameRect,
                                                const IntSize& aOutputSize) {
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams animParams{aFrameRect, FrameTimeout::FromRawMilliseconds(0),
                             0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  BlendAnimationConfig blendAnim{decoder};
  SurfaceConfig surfaceSink{decoder, aOutputSize, SurfaceFormat::OS_RGBA, false,
                            Some(animParams)};
  AssertConfiguringPipelineFails(decoder, blendAnim, surfaceSink);
}

TEST(ImageBlendAnimationFilter, BlendFailsForNegativeFrameRect)
{
  // A negative frame rect size is disallowed.
  AssertConfiguringBlendAnimationFilterFails(
      IntRect(IntPoint(0, 0), IntSize(-1, -1)), IntSize(100, 100));
}

TEST(ImageBlendAnimationFilter, WriteFullFirstFrame)
{
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams params{
      IntRect(0, 0, 100, 100), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  RawAccessFrameRef frame0 = WithBlendAnimationFilter(
      decoder, params, IntSize(100, 100),
      [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        CheckWritePixels(aDecoder, aFilter, Some(IntRect(0, 0, 100, 100)));
      });
  EXPECT_EQ(IntRect(0, 0, 100, 100), frame0->GetDirtyRect());
}

TEST(ImageBlendAnimationFilter, WritePartialFirstFrame)
{
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams params{
      IntRect(25, 50, 50, 25), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  RawAccessFrameRef frame0 = WithBlendAnimationFilter(
      decoder, params, IntSize(100, 100),
      [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        CheckWritePixels(aDecoder, aFilter, Some(IntRect(0, 0, 100, 100)),
                         Nothing(), Some(IntRect(25, 50, 50, 25)),
                         Some(IntRect(25, 50, 50, 25)));
      });
  EXPECT_EQ(IntRect(0, 0, 100, 100), frame0->GetDirtyRect());
}

static void TestWithBlendAnimationFilterClear(BlendMethod aBlendMethod) {
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams params0{
      IntRect(0, 0, 100, 100), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  RawAccessFrameRef frame0 = WithBlendAnimationFilter(
      decoder, params0, IntSize(100, 100),
      [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(BGRAColor::Green().AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 100), frame0->GetDirtyRect());

  AnimationParams params1{
      IntRect(0, 40, 100, 20), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 1, BlendMethod::SOURCE, DisposalMethod::CLEAR};
  RawAccessFrameRef frame1 = WithBlendAnimationFilter(
      decoder, params1, IntSize(100, 100),
      [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(BGRAColor::Red().AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 40, 100, 20), frame1->GetDirtyRect());

  ASSERT_TRUE(frame1.get() != nullptr);

  RefPtr<SourceSurface> surface = frame1->GetSourceSurface();
  EXPECT_TRUE(RowsAreSolidColor(surface, 0, 40, BGRAColor::Green()));
  EXPECT_TRUE(RowsAreSolidColor(surface, 40, 20, BGRAColor::Red()));
  EXPECT_TRUE(RowsAreSolidColor(surface, 60, 40, BGRAColor::Green()));

  AnimationParams params2{
      IntRect(0, 50, 100, 20), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 2, aBlendMethod, DisposalMethod::KEEP};
  RawAccessFrameRef frame2 = WithBlendAnimationFilter(
      decoder, params2, IntSize(100, 100),
      [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(BGRAColor::Blue().AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });

  ASSERT_TRUE(frame2.get() != nullptr);

  surface = frame2->GetSourceSurface();
  EXPECT_TRUE(RowsAreSolidColor(surface, 0, 40, BGRAColor::Green()));
  EXPECT_TRUE(RowsAreSolidColor(surface, 40, 10, BGRAColor::Transparent()));
  EXPECT_TRUE(RowsAreSolidColor(surface, 50, 20, BGRAColor::Blue()));
  EXPECT_TRUE(RowsAreSolidColor(surface, 70, 30, BGRAColor::Green()));
}

TEST(ImageBlendAnimationFilter, ClearWithOver)
{ TestWithBlendAnimationFilterClear(BlendMethod::OVER); }

TEST(ImageBlendAnimationFilter, ClearWithSource)
{ TestWithBlendAnimationFilterClear(BlendMethod::SOURCE); }

TEST(ImageBlendAnimationFilter, KeepWithSource)
{
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams params0{
      IntRect(0, 0, 100, 100), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  RawAccessFrameRef frame0 = WithBlendAnimationFilter(
      decoder, params0, IntSize(100, 100),
      [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(BGRAColor::Green().AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 100), frame0->GetDirtyRect());

  AnimationParams params1{
      IntRect(0, 40, 100, 20), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 1, BlendMethod::SOURCE, DisposalMethod::KEEP};
  RawAccessFrameRef frame1 = WithBlendAnimationFilter(
      decoder, params1, IntSize(100, 100),
      [](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(BGRAColor::Red().AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 40, 100, 20), frame1->GetDirtyRect());

  ASSERT_TRUE(frame1.get() != nullptr);

  RefPtr<SourceSurface> surface = frame1->GetSourceSurface();
  EXPECT_TRUE(RowsAreSolidColor(surface, 0, 40, BGRAColor::Green()));
  EXPECT_TRUE(RowsAreSolidColor(surface, 40, 20, BGRAColor::Red()));
  EXPECT_TRUE(RowsAreSolidColor(surface, 60, 40, BGRAColor::Green()));
}

TEST(ImageBlendAnimationFilter, KeepWithOver)
{
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams params0{
      IntRect(0, 0, 100, 100), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  BGRAColor frameColor0(0, 0xFF, 0, 0x40);
  RawAccessFrameRef frame0 = WithBlendAnimationFilter(
      decoder, params0, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor0.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 100), frame0->GetDirtyRect());

  AnimationParams params1{
      IntRect(0, 40, 100, 20), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 1, BlendMethod::OVER, DisposalMethod::KEEP};
  BGRAColor frameColor1(0, 0, 0xFF, 0x80);
  RawAccessFrameRef frame1 = WithBlendAnimationFilter(
      decoder, params1, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor1.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 40, 100, 20), frame1->GetDirtyRect());

  ASSERT_TRUE(frame1.get() != nullptr);

  BGRAColor blendedColor(0, 0x20, 0x80, 0xA0, true);  // already premultiplied
  EXPECT_EQ(SkPMSrcOver(frameColor1.AsPixel(), frameColor0.AsPixel()),
            blendedColor.AsPixel());

  RefPtr<SourceSurface> surface = frame1->GetSourceSurface();
  EXPECT_TRUE(RowsAreSolidColor(surface, 0, 40, frameColor0));
  EXPECT_TRUE(RowsAreSolidColor(surface, 40, 20, blendedColor));
  EXPECT_TRUE(RowsAreSolidColor(surface, 60, 40, frameColor0));
}

TEST(ImageBlendAnimationFilter, RestorePreviousWithOver)
{
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams params0{
      IntRect(0, 0, 100, 100), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  BGRAColor frameColor0(0, 0xFF, 0, 0x40);
  RawAccessFrameRef frame0 = WithBlendAnimationFilter(
      decoder, params0, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor0.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 100), frame0->GetDirtyRect());

  AnimationParams params1{
      IntRect(0, 10, 100, 80), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 1, BlendMethod::SOURCE, DisposalMethod::RESTORE_PREVIOUS};
  BGRAColor frameColor1 = BGRAColor::Green();
  RawAccessFrameRef frame1 = WithBlendAnimationFilter(
      decoder, params1, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor1.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 10, 100, 80), frame1->GetDirtyRect());

  AnimationParams params2{
      IntRect(0, 40, 100, 20), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 2, BlendMethod::OVER, DisposalMethod::KEEP};
  BGRAColor frameColor2(0, 0, 0xFF, 0x80);
  RawAccessFrameRef frame2 = WithBlendAnimationFilter(
      decoder, params2, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor2.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 10, 100, 80), frame2->GetDirtyRect());

  ASSERT_TRUE(frame2.get() != nullptr);

  BGRAColor blendedColor(0, 0x20, 0x80, 0xA0, true);  // already premultiplied
  EXPECT_EQ(SkPMSrcOver(frameColor2.AsPixel(), frameColor0.AsPixel()),
            blendedColor.AsPixel());

  RefPtr<SourceSurface> surface = frame2->GetSourceSurface();
  EXPECT_TRUE(RowsAreSolidColor(surface, 0, 40, frameColor0));
  EXPECT_TRUE(RowsAreSolidColor(surface, 40, 20, blendedColor));
  EXPECT_TRUE(RowsAreSolidColor(surface, 60, 40, frameColor0));
}

TEST(ImageBlendAnimationFilter, RestorePreviousWithSource)
{
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams params0{
      IntRect(0, 0, 100, 100), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  BGRAColor frameColor0(0, 0xFF, 0, 0x40);
  RawAccessFrameRef frame0 = WithBlendAnimationFilter(
      decoder, params0, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor0.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 100), frame0->GetDirtyRect());

  AnimationParams params1{
      IntRect(0, 10, 100, 80), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 1, BlendMethod::SOURCE, DisposalMethod::RESTORE_PREVIOUS};
  BGRAColor frameColor1 = BGRAColor::Green();
  RawAccessFrameRef frame1 = WithBlendAnimationFilter(
      decoder, params1, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor1.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 10, 100, 80), frame1->GetDirtyRect());

  AnimationParams params2{
      IntRect(0, 40, 100, 20), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 2, BlendMethod::SOURCE, DisposalMethod::KEEP};
  BGRAColor frameColor2(0, 0, 0xFF, 0x80);
  RawAccessFrameRef frame2 = WithBlendAnimationFilter(
      decoder, params2, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor2.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 10, 100, 80), frame2->GetDirtyRect());

  ASSERT_TRUE(frame2.get() != nullptr);

  RefPtr<SourceSurface> surface = frame2->GetSourceSurface();
  EXPECT_TRUE(RowsAreSolidColor(surface, 0, 40, frameColor0));
  EXPECT_TRUE(RowsAreSolidColor(surface, 40, 20, frameColor2));
  EXPECT_TRUE(RowsAreSolidColor(surface, 60, 40, frameColor0));
}

TEST(ImageBlendAnimationFilter, RestorePreviousClearWithSource)
{
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams params0{
      IntRect(0, 0, 100, 100), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  BGRAColor frameColor0 = BGRAColor::Red();
  RawAccessFrameRef frame0 = WithBlendAnimationFilter(
      decoder, params0, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor0.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 100), frame0->GetDirtyRect());

  AnimationParams params1{
      IntRect(0, 0, 100, 20), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 1, BlendMethod::SOURCE, DisposalMethod::CLEAR};
  BGRAColor frameColor1 = BGRAColor::Blue();
  RawAccessFrameRef frame1 = WithBlendAnimationFilter(
      decoder, params1, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor1.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 20), frame1->GetDirtyRect());

  AnimationParams params2{
      IntRect(0, 10, 100, 80), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 2, BlendMethod::SOURCE, DisposalMethod::RESTORE_PREVIOUS};
  BGRAColor frameColor2 = BGRAColor::Green();
  RawAccessFrameRef frame2 = WithBlendAnimationFilter(
      decoder, params2, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor2.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 90), frame2->GetDirtyRect());

  AnimationParams params3{
      IntRect(0, 40, 100, 20), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 3, BlendMethod::SOURCE, DisposalMethod::KEEP};
  BGRAColor frameColor3 = BGRAColor::Blue();
  RawAccessFrameRef frame3 = WithBlendAnimationFilter(
      decoder, params3, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor3.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 90), frame3->GetDirtyRect());

  ASSERT_TRUE(frame3.get() != nullptr);

  RefPtr<SourceSurface> surface = frame3->GetSourceSurface();
  EXPECT_TRUE(RowsAreSolidColor(surface, 0, 20, BGRAColor::Transparent()));
  EXPECT_TRUE(RowsAreSolidColor(surface, 20, 20, frameColor0));
  EXPECT_TRUE(RowsAreSolidColor(surface, 40, 20, frameColor3));
  EXPECT_TRUE(RowsAreSolidColor(surface, 60, 40, frameColor0));
}

TEST(ImageBlendAnimationFilter, PartialOverlapFrameRect)
{
  RefPtr<image::Decoder> decoder = CreateTrivialBlendingDecoder();
  ASSERT_TRUE(decoder != nullptr);

  AnimationParams params0{
      IntRect(-10, -20, 110, 100), FrameTimeout::FromRawMilliseconds(0),
      /* aFrameNum */ 0, BlendMethod::SOURCE, DisposalMethod::KEEP};
  BGRAColor frameColor0 = BGRAColor::Red();
  RawAccessFrameRef frame0 = WithBlendAnimationFilter(
      decoder, params0, IntSize(100, 100),
      [&](image::Decoder* aDecoder, SurfaceFilter* aFilter) {
        auto result = aFilter->WritePixels<uint32_t>(
            [&] { return AsVariant(frameColor0.AsPixel()); });
        EXPECT_EQ(WriteState::FINISHED, result);
      });
  EXPECT_EQ(IntRect(0, 0, 100, 100), frame0->GetDirtyRect());

  RefPtr<SourceSurface> surface = frame0->GetSourceSurface();
  EXPECT_TRUE(RowsAreSolidColor(surface, 0, 80, frameColor0));
  EXPECT_TRUE(RowsAreSolidColor(surface, 80, 20, BGRAColor::Transparent()));
}
