/* 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 "Common.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "SourceBuffer.h"
#include "SurfaceFilters.h"
#include "SurfacePipe.h"
#include "gtest/gtest.h"
#include "mozilla/Maybe.h"
#include "mozilla/gfx/2D.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<image::Decoder> decoder = CreateTrivialDecoder();
  ASSERT_TRUE(bool(decoder));

  WithFilterPipeline(
      decoder, std::forward<Func>(aFunc), ADAM7InterpolatingConfig{},
      SurfaceConfig{decoder, aSize, SurfaceFormat::OS_RGBA, false});
}

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

  AssertConfiguringPipelineFails(
      decoder, ADAM7InterpolatingConfig{},
      SurfaceConfig{decoder, aSize, SurfaceFormat::OS_RGBA, 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 = 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(image::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 = 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, [&](image::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(image::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, [&](image::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, [&](image::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));
}
