/* -*- 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 <algorithm>
#include <vector>

#include "gtest/gtest.h"

#include "mozilla/gfx/2D.h"
#include "mozilla/Maybe.h"
#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;

using std::generate;
using std::vector;

template <typename Func> void
WithADAM7InterpolatingFilter(const IntSize& aSize, Func aFunc)
{
  RefPtr<Decoder> decoder = CreateTrivialDecoder();
  ASSERT_TRUE(bool(decoder));

  WithFilterPipeline(decoder, Forward<Func>(aFunc),
                     ADAM7InterpolatingConfig { },
                     SurfaceConfig { decoder, 0, aSize,
                                     SurfaceFormat::B8G8R8A8, false });
}

void
AssertConfiguringADAM7InterpolatingFilterFails(const IntSize& aSize)
{
  RefPtr<Decoder> decoder = CreateTrivialDecoder();
  ASSERT_TRUE(bool(decoder));

  AssertConfiguringPipelineFails(decoder,
                                 ADAM7InterpolatingConfig { },
                                 SurfaceConfig { decoder, 0, aSize,
                                                 SurfaceFormat::B8G8R8A8, false });
}

uint8_t
InterpolateByte(uint8_t aByteA, uint8_t aByteB, float aWeight)
{
  return uint8_t(aByteA * aWeight + aByteB * (1.0f - aWeight));
}

BGRAColor
InterpolateColors(BGRAColor aColor1, BGRAColor aColor2, float aWeight)
{
  return BGRAColor(InterpolateByte(aColor1.mBlue,  aColor2.mBlue,  aWeight),
                   InterpolateByte(aColor1.mGreen, aColor2.mGreen, aWeight),
                   InterpolateByte(aColor1.mRed,   aColor2.mRed,   aWeight),
                   InterpolateByte(aColor1.mAlpha, aColor2.mAlpha, aWeight));
}

enum class ShouldInterpolate
{
  eYes,
  eNo
};

BGRAColor
HorizontallyInterpolatedPixel(uint32_t aCol,
                              uint32_t aWidth,
                              const vector<float>& aWeights,
                              ShouldInterpolate aShouldInterpolate,
                              const vector<BGRAColor>& aColors)
{
  // We cycle through the vector of weights forever.
  float weight = aWeights[aCol % aWeights.size()];

  // Find the columns of the two final pixels for this set of weights.
  uint32_t finalPixel1 = aCol - aCol % aWeights.size();
  uint32_t finalPixel2 = finalPixel1 + aWeights.size();

  // If |finalPixel2| is past the end of the row, that means that there is no
  // final pixel after the pixel at |finalPixel1|. In that case, we just want to
  // duplicate |finalPixel1|'s color until the end of the row. We can do that by
  // setting |finalPixel2| equal to |finalPixel1| so that the interpolation has
  // no effect.
  if (finalPixel2 >= aWidth) {
    finalPixel2 = finalPixel1;
  }

  // We cycle through the vector of colors forever (subject to the above
  // constraint about the end of the row).
  BGRAColor color1 = aColors[finalPixel1 % aColors.size()];
  BGRAColor color2 = aColors[finalPixel2 % aColors.size()];

  // If we're not interpolating, we treat all pixels which aren't final as
  // transparent. Since the number of weights we have is equal to the stride
  // between final pixels, we can check if |aCol| is a final pixel by checking
  // whether |aCol| is a multiple of |aWeights.size()|.
  if (aShouldInterpolate == ShouldInterpolate::eNo) {
    return aCol % aWeights.size() == 0 ? color1
                                       : BGRAColor::Transparent();
  }

  // Interpolate.
  return InterpolateColors(color1, color2, weight);
}

vector<float>&
InterpolationWeights(int32_t aStride)
{
  // Precalculated interpolation weights. These are used to interpolate
  // between final pixels or between important rows. Although no interpolation
  // is actually applied to the previous final pixel or important row value,
  // the arrays still start with 1.0f, which is always skipped, primarily
  // because otherwise |stride1Weights| would have zero elements.
  static vector<float> stride8Weights =
    { 1.0f, 7 / 8.0f, 6 / 8.0f, 5 / 8.0f, 4 / 8.0f, 3 / 8.0f, 2 / 8.0f, 1 / 8.0f };
  static vector<float> stride4Weights = { 1.0f, 3 / 4.0f, 2 / 4.0f, 1 / 4.0f };
  static vector<float> stride2Weights = { 1.0f, 1 / 2.0f };
  static vector<float> stride1Weights = { 1.0f };

  switch (aStride) {
    case 8: return stride8Weights;
    case 4: return stride4Weights;
    case 2: return stride2Weights;
    case 1: return stride1Weights;
    default:
      MOZ_CRASH();
  }
}

int32_t
ImportantRowStride(uint8_t aPass)
{
  // The stride between important rows for each pass, with a dummy value for
  // the nonexistent pass 0 and for pass 8, since the tests run an extra pass to
  // make sure nothing breaks.
  static int32_t strides[] = { 1, 8, 8, 4, 4, 2, 2, 1, 1 };

  return strides[aPass];
}

size_t
FinalPixelStride(uint8_t aPass)
{
  // The stride between the final pixels in important rows for each pass, with
  // a dummy value for the nonexistent pass 0 and for pass 8, since the tests
  // run an extra pass to make sure nothing breaks.
  static size_t strides[] = { 1, 8, 4, 4, 2, 2, 1, 1, 1 };

  return strides[aPass];
}

bool
IsImportantRow(int32_t aRow, uint8_t aPass)
{
  return aRow % ImportantRowStride(aPass) == 0;
}

/**
 * ADAM7 breaks up the image into 8x8 blocks. On each of the 7 passes, a new
 * set of pixels in each block receives their final values, according to the
 * following pattern:
 *
 *    1 6 4 6 2 6 4 6
 *    7 7 7 7 7 7 7 7
 *    5 6 5 6 5 6 5 6
 *    7 7 7 7 7 7 7 7
 *    3 6 4 6 3 6 4 6
 *    7 7 7 7 7 7 7 7
 *    5 6 5 6 5 6 5 6
 *    7 7 7 7 7 7 7 7
 *
 * This function produces a row of pixels @aWidth wide, suitable for testing
 * horizontal interpolation on pass @aPass. The pattern of pixels used is
 * determined by @aPass and @aRow, which determine which pixels are final
 * according to the table above, and @aColors, from which the pixel values
 * are selected.
 *
 * There are two different behaviors: if |eNo| is passed for
 * @aShouldInterpolate, non-final pixels are treated as transparent. If |eNo|
 * is passed, non-final pixels get interpolated in from the surrounding final
 * pixels. The intention is that |eNo| is passed to generate input which will
 * be run through ADAM7InterpolatingFilter, and |eYes| is passed to generate
 * reference data to check that the filter is performing horizontal
 * interpolation correctly.
 *
 * This function does not perform vertical interpolation. Rows which aren't on
 * the current pass are filled with transparent pixels.
 *
 * @return a vector<BGRAColor> representing a row of pixels.
 */
vector<BGRAColor>
ADAM7HorizontallyInterpolatedRow(uint8_t aPass,
                                 uint32_t aRow,
                                 uint32_t aWidth,
                                 ShouldInterpolate aShouldInterpolate,
                                 const vector<BGRAColor>& aColors)
{
  EXPECT_GT(aPass, 0);
  EXPECT_LE(aPass, 8);
  EXPECT_GT(aColors.size(), 0u);

  vector<BGRAColor> result(aWidth);

  if (IsImportantRow(aRow, aPass)) {
    vector<float>& weights = InterpolationWeights(FinalPixelStride(aPass));

    // Compute the horizontally interpolated row.
    uint32_t col = 0;
    generate(result.begin(), result.end(), [&]{
      return HorizontallyInterpolatedPixel(col++, aWidth, weights,
                                           aShouldInterpolate, aColors);
    });
  } else {
    // This is an unimportant row; just make the entire thing transparent.
    generate(result.begin(), result.end(), []{
      return BGRAColor::Transparent();
    });
  }

  EXPECT_EQ(result.size(), size_t(aWidth));

  return result;
}

WriteState
WriteUninterpolatedPixels(SurfaceFilter* aFilter,
                          const IntSize& aSize,
                          uint8_t aPass,
                          const vector<BGRAColor>& aColors)
{
  WriteState result = WriteState::NEED_MORE_DATA;

  for (int32_t row = 0; row < aSize.height; ++row) {
    // Compute uninterpolated pixels for this row.
    vector<BGRAColor> pixels =
      Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width,
                                            ShouldInterpolate::eNo, aColors));

    // Write them to the surface.
    auto pixelIterator = pixels.cbegin();
    result = aFilter->WritePixelsToRow<uint32_t>([&]{
      return AsVariant((*pixelIterator++).AsPixel());
    });

    if (result != WriteState::NEED_MORE_DATA) {
      break;
    }
  }

  return result;
}

bool
CheckHorizontallyInterpolatedImage(Decoder* aDecoder,
                                   const IntSize& aSize,
                                   uint8_t aPass,
                                   const vector<BGRAColor>& aColors)
{
  RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
  RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();

  for (int32_t row = 0; row < aSize.height; ++row) {
    if (!IsImportantRow(row, aPass)) {
      continue;  // Don't check rows which aren't important on this pass.
    }

    // Compute the expected pixels, *with* interpolation to match what the
    // filter should have done.
    vector<BGRAColor> expectedPixels =
      Move(ADAM7HorizontallyInterpolatedRow(aPass, row, aSize.width,
                                            ShouldInterpolate::eYes, aColors));

    if (!RowHasPixels(surface, row, expectedPixels)) {
      return false;
    }
  }

  return true;
}

void
CheckHorizontalInterpolation(const IntSize& aSize,
                             const vector<BGRAColor>& aColors)
{
  const IntRect surfaceRect(IntPoint(0, 0), aSize);

  WithADAM7InterpolatingFilter(aSize,
                               [&](Decoder* aDecoder, SurfaceFilter* aFilter) {
    // We check horizontal interpolation behavior for each pass individually. In
    // addition to the normal 7 passes that ADAM7 includes, we also check an
    // eighth pass to verify that nothing breaks if extra data is written.
    for (uint8_t pass = 1; pass <= 8; ++pass) {
      // Write our color pattern to the surface. We don't perform any
      // interpolation when writing to the filter so that we can check that the
      // filter itself *does*.
      WriteState result =
        WriteUninterpolatedPixels(aFilter, aSize, pass, aColors);

      EXPECT_EQ(WriteState::FINISHED, result);
      AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect);

      // Check that the generated image matches the expected pattern, with
      // interpolation applied.
      EXPECT_TRUE(CheckHorizontallyInterpolatedImage(aDecoder, aSize,
                                                     pass, aColors));

      // Prepare for the next pass.
      aFilter->ResetToFirstRow();
    }
  });
}

BGRAColor
ADAM7RowColor(int32_t aRow,
              uint8_t aPass,
              const vector<BGRAColor>& aColors)
{
  EXPECT_LT(0, aPass);
  EXPECT_GE(8, aPass);
  EXPECT_LT(0u, aColors.size());

  // If this is an important row, select the color from the provided vector of
  // colors, which we cycle through infinitely. If not, just fill the row with
  // transparent pixels.
  return IsImportantRow(aRow, aPass) ? aColors[aRow % aColors.size()]
                                     : BGRAColor::Transparent();
}

WriteState
WriteRowColorPixels(SurfaceFilter* aFilter,
                    const IntSize& aSize,
                    uint8_t aPass,
                    const vector<BGRAColor>& aColors)
{
  WriteState result = WriteState::NEED_MORE_DATA;

  for (int32_t row = 0; row < aSize.height; ++row) {
    const uint32_t color = ADAM7RowColor(row, aPass, aColors).AsPixel();

    // Fill the surface with |color| pixels.
    result = aFilter->WritePixelsToRow<uint32_t>([&]{ return AsVariant(color); });

    if (result != WriteState::NEED_MORE_DATA) {
      break;
    }
  }

  return result;
}

bool
CheckVerticallyInterpolatedImage(Decoder* aDecoder,
                                 const IntSize& aSize,
                                 uint8_t aPass,
                                 const vector<BGRAColor>& aColors)
{
  vector<float>& weights = InterpolationWeights(ImportantRowStride(aPass));

  for (int32_t row = 0; row < aSize.height; ++row) {
    // Vertically interpolation takes place between two important rows. The
    // separation between the important rows is determined by the stride of this
    // pass. When there is no "next" important row because we'd run off the
    // bottom of the image, we use the same row for both. This matches
    // ADAM7InterpolatingFilter's behavior of duplicating the last important row
    // since there isn't another important row to vertically interpolate it
    // with.
    const int32_t stride = ImportantRowStride(aPass);
    const int32_t prevImportantRow = row - row % stride;
    const int32_t maybeNextImportantRow = prevImportantRow + stride;
    const int32_t nextImportantRow = maybeNextImportantRow < aSize.height
                                   ? maybeNextImportantRow
                                   : prevImportantRow;

    // Retrieve the colors for the important rows we're going to interpolate.
    const BGRAColor prevImportantRowColor =
      ADAM7RowColor(prevImportantRow, aPass, aColors);
    const BGRAColor nextImportantRowColor =
      ADAM7RowColor(nextImportantRow, aPass, aColors);

    // The weight we'll use for interpolation is also determined by the stride.
    // A row halfway between two important rows should have pixels that have a
    // 50% contribution from each of the important rows, for example.
    const float weight = weights[row % stride];
    const BGRAColor interpolatedColor =
      InterpolateColors(prevImportantRowColor, nextImportantRowColor, weight);

    // Generate a row of expected pixels. Every pixel in the row is always the
    // same color since we're only testing vertical interpolation between
    // solid-colored rows.
    vector<BGRAColor> expectedPixels(aSize.width);
    generate(expectedPixels.begin(), expectedPixels.end(), [&]{
      return interpolatedColor;
    });

    // Check that the pixels match.
    RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
    RefPtr<SourceSurface> surface = currentFrame->GetSourceSurface();
    if (!RowHasPixels(surface, row, expectedPixels)) {
      return false;
    }
  }

  return true;
}

void
CheckVerticalInterpolation(const IntSize& aSize,
                           const vector<BGRAColor>& aColors)
{
  const IntRect surfaceRect(IntPoint(0, 0), aSize);

  WithADAM7InterpolatingFilter(aSize,
                               [&](Decoder* aDecoder, SurfaceFilter* aFilter) {
    for (uint8_t pass = 1; pass <= 8; ++pass) {
      // Write a pattern of rows to the surface. Important rows will receive a
      // color selected from |aColors|; unimportant rows will be transparent.
      WriteState result = WriteRowColorPixels(aFilter, aSize, pass, aColors);

      EXPECT_EQ(WriteState::FINISHED, result);
      AssertCorrectPipelineFinalState(aFilter, surfaceRect, surfaceRect);

      // Check that the generated image matches the expected pattern, with
      // interpolation applied.
      EXPECT_TRUE(CheckVerticallyInterpolatedImage(aDecoder, aSize,
                                                   pass, aColors));

      // Prepare for the next pass.
      aFilter->ResetToFirstRow();
    }
  });
}

void
CheckInterpolation(const IntSize& aSize, const vector<BGRAColor>& aColors)
{
  CheckHorizontalInterpolation(aSize, aColors);
  CheckVerticalInterpolation(aSize, aColors);
}

void
CheckADAM7InterpolatingWritePixels(const IntSize& aSize)
{
  // This test writes 8 passes of green pixels (the seven ADAM7 passes, plus one
  // extra to make sure nothing goes wrong if we write too much input) and verifies
  // that the output is a solid green surface each time. Because all the pixels
  // are the same color, interpolation doesn't matter; we test the correctness
  // of the interpolation algorithm itself separately.
  WithADAM7InterpolatingFilter(aSize,
                               [&](Decoder* aDecoder, SurfaceFilter* aFilter) {
    IntRect rect(IntPoint(0, 0), aSize);

    for (int32_t pass = 1; pass <= 8; ++pass) {
      // We only actually write up to the last important row for each pass,
      // because that row unambiguously determines the remaining rows.
      const int32_t lastRow = aSize.height - 1;
      const int32_t lastImportantRow =
        lastRow - (lastRow % ImportantRowStride(pass));
      const IntRect inputWriteRect(0, 0, aSize.width, lastImportantRow + 1);

      CheckWritePixels(aDecoder, aFilter,
                       /* aOutputRect = */ Some(rect),
                       /* aInputRect = */ Some(rect),
                       /* aInputWriteRect = */ Some(inputWriteRect));

      aFilter->ResetToFirstRow();
      EXPECT_FALSE(aFilter->IsSurfaceFinished());
      Maybe<SurfaceInvalidRect> invalidRect = aFilter->TakeInvalidRect();
      EXPECT_TRUE(invalidRect.isNothing());
    }
  });
}

TEST(ImageADAM7InterpolatingFilter, WritePixels100_100)
{
  CheckADAM7InterpolatingWritePixels(IntSize(100, 100));
}

TEST(ImageADAM7InterpolatingFilter, WritePixels99_99)
{
  CheckADAM7InterpolatingWritePixels(IntSize(99, 99));
}

TEST(ImageADAM7InterpolatingFilter, WritePixels66_33)
{
  CheckADAM7InterpolatingWritePixels(IntSize(66, 33));
}

TEST(ImageADAM7InterpolatingFilter, WritePixels33_66)
{
  CheckADAM7InterpolatingWritePixels(IntSize(33, 66));
}

TEST(ImageADAM7InterpolatingFilter, WritePixels15_15)
{
  CheckADAM7InterpolatingWritePixels(IntSize(15, 15));
}

TEST(ImageADAM7InterpolatingFilter, WritePixels9_9)
{
  CheckADAM7InterpolatingWritePixels(IntSize(9, 9));
}

TEST(ImageADAM7InterpolatingFilter, WritePixels8_8)
{
  CheckADAM7InterpolatingWritePixels(IntSize(8, 8));
}

TEST(ImageADAM7InterpolatingFilter, WritePixels7_7)
{
  CheckADAM7InterpolatingWritePixels(IntSize(7, 7));
}

TEST(ImageADAM7InterpolatingFilter, WritePixels3_3)
{
  CheckADAM7InterpolatingWritePixels(IntSize(3, 3));
}

TEST(ImageADAM7InterpolatingFilter, WritePixels1_1)
{
  CheckADAM7InterpolatingWritePixels(IntSize(1, 1));
}

TEST(ImageADAM7InterpolatingFilter, TrivialInterpolation48_48)
{
  CheckInterpolation(IntSize(48, 48), { BGRAColor::Green() });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput33_17)
{
  // We check interpolation using irregular patterns to make sure that the
  // interpolation will look different for different passes.
  CheckInterpolation(IntSize(33, 17), {
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),
    BGRAColor::Blue(),  BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Green(),
    BGRAColor::Red(),   BGRAColor::Red(),   BGRAColor::Blue(),  BGRAColor::Blue(),
    BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Blue(),
    BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Red(),   BGRAColor::Blue(),
    BGRAColor::Blue(),  BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Green(),
    BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Blue()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput32_16)
{
  CheckInterpolation(IntSize(32, 16), {
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),
    BGRAColor::Blue(),  BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Green(),
    BGRAColor::Red(),   BGRAColor::Red(),   BGRAColor::Blue(),  BGRAColor::Blue(),
    BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Blue(),
    BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Red(),   BGRAColor::Blue(),
    BGRAColor::Blue(),  BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Green(),
    BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Blue()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput31_15)
{
  CheckInterpolation(IntSize(31, 15), {
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),
    BGRAColor::Blue(),  BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Green(),
    BGRAColor::Red(),   BGRAColor::Red(),   BGRAColor::Blue(),  BGRAColor::Blue(),
    BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Blue(),
    BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Red(),   BGRAColor::Blue(),
    BGRAColor::Blue(),  BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Green(),
    BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Blue()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput17_33)
{
  CheckInterpolation(IntSize(17, 33), {
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),
    BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),
    BGRAColor::Blue(),  BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Green(),
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Red(),   BGRAColor::Blue()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput16_32)
{
  CheckInterpolation(IntSize(16, 32), {
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),
    BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),
    BGRAColor::Blue(),  BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Green(),
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Red(),   BGRAColor::Blue()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput15_31)
{
  CheckInterpolation(IntSize(15, 31), {
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),
    BGRAColor::Red(),   BGRAColor::Green(), BGRAColor::Blue(),  BGRAColor::Red(),
    BGRAColor::Blue(),  BGRAColor::Blue(),  BGRAColor::Red(),   BGRAColor::Green(),
    BGRAColor::Green(), BGRAColor::Red(),   BGRAColor::Red(),   BGRAColor::Blue()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput9_9)
{
  CheckInterpolation(IntSize(9, 9), {
    BGRAColor::Blue(),  BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
    BGRAColor::Green(), BGRAColor::Red(),  BGRAColor::Red(), BGRAColor::Blue()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput8_8)
{
  CheckInterpolation(IntSize(8, 8), {
    BGRAColor::Blue(),  BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
    BGRAColor::Green(), BGRAColor::Red(),  BGRAColor::Red(), BGRAColor::Blue()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput7_7)
{
  CheckInterpolation(IntSize(7, 7), {
    BGRAColor::Blue(),  BGRAColor::Blue(), BGRAColor::Red(), BGRAColor::Green(),
    BGRAColor::Green(), BGRAColor::Red(),  BGRAColor::Red(), BGRAColor::Blue()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput3_3)
{
  CheckInterpolation(IntSize(3, 3), {
    BGRAColor::Green(), BGRAColor::Red(), BGRAColor::Blue(), BGRAColor::Red()
  });
}

TEST(ImageADAM7InterpolatingFilter, InterpolationOutput1_1)
{
  CheckInterpolation(IntSize(1, 1), { BGRAColor::Blue() });
}

TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsFor0_0)
{
  // A 0x0 input size is invalid, so configuration should fail.
  AssertConfiguringADAM7InterpolatingFilterFails(IntSize(0, 0));
}

TEST(ImageADAM7InterpolatingFilter, ADAM7InterpolationFailsForMinus1_Minus1)
{
  // A negative input size is invalid, so configuration should fail.
  AssertConfiguringADAM7InterpolatingFilterFails(IntSize(-1, -1));
}

TEST(ImageADAM7InterpolatingFilter, ConfiguringPalettedADAM7InterpolatingFilterFails)
{
  RefPtr<Decoder> decoder = CreateTrivialDecoder();
  ASSERT_TRUE(decoder != nullptr);

  // ADAM7InterpolatingFilter does not support paletted images, so configuration
  // should fail.
  AssertConfiguringPipelineFails(decoder,
                                 ADAM7InterpolatingConfig { },
                                 PalettedSurfaceConfig { decoder, 0, IntSize(100, 100),
                                                         IntRect(0, 0, 50, 50),
                                                         SurfaceFormat::B8G8R8A8, 8,
                                                         false });
}
