// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/display_list/dl_blend_mode.h"
#include "flutter/display_list/dl_color.h"
#include "flutter/display_list/dl_sampling_options.h"
#include "flutter/display_list/dl_tile_mode.h"
#include "flutter/display_list/effects/dl_color_filter.h"
#include "flutter/display_list/effects/dl_image_filter.h"
#include "flutter/display_list/testing/dl_test_equality.h"
#include "flutter/display_list/utils/dl_comparable.h"
#include "gtest/gtest.h"

#include "third_party/skia/include/core/SkBlendMode.h"
#include "third_party/skia/include/core/SkColorFilter.h"
#include "third_party/skia/include/core/SkSamplingOptions.h"
#include "third_party/skia/include/effects/SkImageFilters.h"

namespace flutter {
namespace testing {

// SkRect::contains treats the rect as a half-open interval which is
// appropriate for so many operations. Unfortunately, we are using
// it here to test containment of the corners of a transformed quad
// so the corners of the quad that are measured against the right
// and bottom edges are contained even if they are on the right or
// bottom edge. This method does the "all sides inclusive" version
// of SkRect::contains.
static bool containsInclusive(const SkRect rect, const SkPoint p) {
  // Test with a slight offset of 1E-9 to "forgive" IEEE bit-rounding
  // Ending up with bounds that are off by 1E-9 (these numbers are all
  // being tested in device space with this method) will be off by a
  // negligible amount of a pixel that wouldn't contribute to changing
  // the color of a pixel.
  return (p.fX >= rect.fLeft - 1E-9 &&   //
          p.fX <= rect.fRight + 1E-9 &&  //
          p.fY >= rect.fTop - 1E-9 &&    //
          p.fY <= rect.fBottom + 1E-9);
}

static bool containsInclusive(const SkRect rect, const SkPoint quad[4]) {
  return (containsInclusive(rect, quad[0]) &&  //
          containsInclusive(rect, quad[1]) &&  //
          containsInclusive(rect, quad[2]) &&  //
          containsInclusive(rect, quad[3]));
}

static bool containsInclusive(const SkIRect rect, const SkPoint quad[4]) {
  return containsInclusive(SkRect::Make(rect), quad);
}

static bool containsInclusive(const SkIRect rect, const SkRect bounds) {
  return (bounds.fLeft >= rect.fLeft - 1E-9 &&
          bounds.fTop >= rect.fTop - 1E-9 &&
          bounds.fRight <= rect.fRight + 1E-9 &&
          bounds.fBottom <= rect.fBottom + 1E-9);
}

// Used to verify that the expected output bounds and reverse-engineered
// "input bounds for output bounds" rectangles are included in the rectangle
// returned from the various bounds computation methods under the specified
// matrix.
static void TestBoundsWithMatrix(const DlImageFilter& filter,
                                 const SkMatrix& matrix,
                                 const SkRect& sourceBounds,
                                 const SkPoint expectedLocalOutputQuad[4]) {
  SkRect device_input_bounds = matrix.mapRect(sourceBounds);
  SkPoint expected_output_quad[4];
  matrix.mapPoints(expected_output_quad, expectedLocalOutputQuad, 4);

  SkIRect device_filter_ibounds;
  ASSERT_EQ(filter.map_device_bounds(device_input_bounds.roundOut(), matrix,
                                     device_filter_ibounds),
            &device_filter_ibounds);
  ASSERT_TRUE(containsInclusive(device_filter_ibounds, expected_output_quad));

  SkIRect reverse_input_ibounds;
  ASSERT_EQ(filter.get_input_device_bounds(device_filter_ibounds, matrix,
                                           reverse_input_ibounds),
            &reverse_input_ibounds);
  ASSERT_TRUE(containsInclusive(reverse_input_ibounds, device_input_bounds));
}

static void TestInvalidBounds(const DlImageFilter& filter,
                              const SkMatrix& matrix,
                              const SkRect& localInputBounds) {
  SkIRect device_input_bounds = matrix.mapRect(localInputBounds).roundOut();

  SkRect local_filter_bounds;
  ASSERT_EQ(filter.map_local_bounds(localInputBounds, local_filter_bounds),
            nullptr);
  ASSERT_EQ(local_filter_bounds, localInputBounds);

  SkIRect device_filter_ibounds;
  ASSERT_EQ(filter.map_device_bounds(device_input_bounds, matrix,
                                     device_filter_ibounds),
            nullptr);
  ASSERT_EQ(device_filter_ibounds, device_input_bounds);

  SkIRect reverse_input_ibounds;
  ASSERT_EQ(filter.get_input_device_bounds(device_input_bounds, matrix,
                                           reverse_input_ibounds),
            nullptr);
  ASSERT_EQ(reverse_input_ibounds, device_input_bounds);
}

// localInputBounds is a sample bounds for testing as input to the filter.
// localExpectOutputBounds is the theoretical output bounds for applying
// the filter to the localInputBounds.
// localExpectInputBounds is the theoretical input bounds required for the
// filter to cover the localExpectOutputBounds
// If either of the expected bounds are nullptr then the bounds methods will
// be assumed to be unable to perform their computations for the given
// image filter and will be returning null.
static void TestBounds(const DlImageFilter& filter,
                       const SkRect& sourceBounds,
                       const SkPoint expectedLocalOutputQuad[4]) {
  SkRect local_filter_bounds;
  ASSERT_EQ(filter.map_local_bounds(sourceBounds, local_filter_bounds),
            &local_filter_bounds);
  ASSERT_TRUE(containsInclusive(local_filter_bounds, expectedLocalOutputQuad));

  for (int scale = 1; scale <= 4; scale++) {
    for (int skew = 0; skew < 8; skew++) {
      for (int degrees = 0; degrees <= 360; degrees += 15) {
        SkMatrix matrix;
        matrix.setScale(scale, scale);
        matrix.postSkew(skew / 8.0, skew / 8.0);
        matrix.postRotate(degrees);
        ASSERT_TRUE(matrix.invert(nullptr));
        TestBoundsWithMatrix(filter, matrix, sourceBounds,
                             expectedLocalOutputQuad);
        matrix.setPerspX(0.001);
        matrix.setPerspY(0.001);
        ASSERT_TRUE(matrix.invert(nullptr));
        TestBoundsWithMatrix(filter, matrix, sourceBounds,
                             expectedLocalOutputQuad);
      }
    }
  }
}

static void TestBounds(const DlImageFilter& filter,
                       const SkRect& sourceBounds,
                       const SkRect& expectedLocalOutputBounds) {
  SkPoint expected_local_output_quad[4];
  expectedLocalOutputBounds.toQuad(expected_local_output_quad);
  TestBounds(filter, sourceBounds, expected_local_output_quad);
}

TEST(DisplayListImageFilter, BlurConstructor) {
  DlBlurImageFilter filter(5.0, 6.0, DlTileMode::kMirror);
}

TEST(DisplayListImageFilter, BlurShared) {
  DlBlurImageFilter filter(5.0, 6.0, DlTileMode::kMirror);

  ASSERT_NE(filter.shared().get(), &filter);
  ASSERT_EQ(*filter.shared(), filter);
}

TEST(DisplayListImageFilter, BlurAsBlur) {
  DlBlurImageFilter filter(5.0, 6.0, DlTileMode::kMirror);

  ASSERT_NE(filter.asBlur(), nullptr);
  ASSERT_EQ(filter.asBlur(), &filter);
}

TEST(DisplayListImageFilter, BlurContents) {
  DlBlurImageFilter filter(5.0, 6.0, DlTileMode::kMirror);

  ASSERT_EQ(filter.sigma_x(), 5.0);
  ASSERT_EQ(filter.sigma_y(), 6.0);
  ASSERT_EQ(filter.tile_mode(), DlTileMode::kMirror);
}

TEST(DisplayListImageFilter, BlurEquals) {
  DlBlurImageFilter filter1(5.0, 6.0, DlTileMode::kMirror);
  DlBlurImageFilter filter2(5.0, 6.0, DlTileMode::kMirror);

  TestEquals(filter1, filter2);
}

TEST(DisplayListImageFilter, BlurWithLocalMatrixEquals) {
  DlBlurImageFilter filter1(5.0, 6.0, DlTileMode::kMirror);
  DlBlurImageFilter filter2(5.0, 6.0, DlTileMode::kMirror);

  SkMatrix local_matrix = SkMatrix::Translate(10, 10);
  TestEquals(*filter1.makeWithLocalMatrix(local_matrix),
             *filter2.makeWithLocalMatrix(local_matrix));
}

TEST(DisplayListImageFilter, BlurNotEquals) {
  DlBlurImageFilter filter1(5.0, 6.0, DlTileMode::kMirror);
  DlBlurImageFilter filter2(7.0, 6.0, DlTileMode::kMirror);
  DlBlurImageFilter filter3(5.0, 8.0, DlTileMode::kMirror);
  DlBlurImageFilter filter4(5.0, 6.0, DlTileMode::kRepeat);

  TestNotEquals(filter1, filter2, "Sigma X differs");
  TestNotEquals(filter1, filter3, "Sigma Y differs");
  TestNotEquals(filter1, filter4, "Tile Mode differs");
}

TEST(DisplayListImageFilter, BlurBounds) {
  DlBlurImageFilter filter = DlBlurImageFilter(5, 10, DlTileMode::kDecal);
  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  SkRect expected_output_bounds = input_bounds.makeOutset(15, 30);
  TestBounds(filter, input_bounds, expected_output_bounds);
}

TEST(DisplayListImageFilter, BlurZeroSigma) {
  std::shared_ptr<DlImageFilter> filter =
      DlBlurImageFilter::Make(0, 0, DlTileMode::kMirror);
  ASSERT_EQ(filter, nullptr);
  filter = DlBlurImageFilter::Make(3, SK_ScalarNaN, DlTileMode::kMirror);
  ASSERT_EQ(filter, nullptr);
  filter = DlBlurImageFilter::Make(SK_ScalarNaN, 3, DlTileMode::kMirror);
  ASSERT_EQ(filter, nullptr);
  filter =
      DlBlurImageFilter::Make(SK_ScalarNaN, SK_ScalarNaN, DlTileMode::kMirror);
  ASSERT_EQ(filter, nullptr);
  filter = DlBlurImageFilter::Make(3, 0, DlTileMode::kMirror);
  ASSERT_NE(filter, nullptr);
  filter = DlBlurImageFilter::Make(0, 3, DlTileMode::kMirror);
  ASSERT_NE(filter, nullptr);
}

TEST(DisplayListImageFilter, DilateConstructor) {
  DlDilateImageFilter filter(5.0, 6.0);
}

TEST(DisplayListImageFilter, DilateShared) {
  DlDilateImageFilter filter(5.0, 6.0);

  ASSERT_NE(filter.shared().get(), &filter);
  ASSERT_EQ(*filter.shared(), filter);
}

TEST(DisplayListImageFilter, DilateAsDilate) {
  DlDilateImageFilter filter(5.0, 6.0);

  ASSERT_NE(filter.asDilate(), nullptr);
  ASSERT_EQ(filter.asDilate(), &filter);
}

TEST(DisplayListImageFilter, DilateContents) {
  DlDilateImageFilter filter(5.0, 6.0);

  ASSERT_EQ(filter.radius_x(), 5.0);
  ASSERT_EQ(filter.radius_y(), 6.0);
}

TEST(DisplayListImageFilter, DilateEquals) {
  DlDilateImageFilter filter1(5.0, 6.0);
  DlDilateImageFilter filter2(5.0, 6.0);

  TestEquals(filter1, filter2);
}

TEST(DisplayListImageFilter, DilateWithLocalMatrixEquals) {
  DlDilateImageFilter filter1(5.0, 6.0);
  DlDilateImageFilter filter2(5.0, 6.0);

  SkMatrix local_matrix = SkMatrix::Translate(10, 10);
  TestEquals(*filter1.makeWithLocalMatrix(local_matrix),
             *filter2.makeWithLocalMatrix(local_matrix));
}

TEST(DisplayListImageFilter, DilateNotEquals) {
  DlDilateImageFilter filter1(5.0, 6.0);
  DlDilateImageFilter filter2(7.0, 6.0);
  DlDilateImageFilter filter3(5.0, 8.0);

  TestNotEquals(filter1, filter2, "Radius X differs");
  TestNotEquals(filter1, filter3, "Radius Y differs");
}

TEST(DisplayListImageFilter, DilateBounds) {
  DlDilateImageFilter filter = DlDilateImageFilter(5, 10);
  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  SkRect expected_output_bounds = input_bounds.makeOutset(5, 10);
  TestBounds(filter, input_bounds, expected_output_bounds);
}

TEST(DisplayListImageFilter, ErodeConstructor) {
  DlErodeImageFilter filter(5.0, 6.0);
}

TEST(DisplayListImageFilter, ErodeShared) {
  DlErodeImageFilter filter(5.0, 6.0);

  ASSERT_NE(filter.shared().get(), &filter);
  ASSERT_EQ(*filter.shared(), filter);
}

TEST(DisplayListImageFilter, ErodeAsErode) {
  DlErodeImageFilter filter(5.0, 6.0);

  ASSERT_NE(filter.asErode(), nullptr);
  ASSERT_EQ(filter.asErode(), &filter);
}

TEST(DisplayListImageFilter, ErodeContents) {
  DlErodeImageFilter filter(5.0, 6.0);

  ASSERT_EQ(filter.radius_x(), 5.0);
  ASSERT_EQ(filter.radius_y(), 6.0);
}

TEST(DisplayListImageFilter, ErodeEquals) {
  DlErodeImageFilter filter1(5.0, 6.0);
  DlErodeImageFilter filter2(5.0, 6.0);

  TestEquals(filter1, filter2);
}

TEST(DisplayListImageFilter, ErodeWithLocalMatrixEquals) {
  DlErodeImageFilter filter1(5.0, 6.0);
  DlErodeImageFilter filter2(5.0, 6.0);

  SkMatrix local_matrix = SkMatrix::Translate(10, 10);
  TestEquals(*filter1.makeWithLocalMatrix(local_matrix),
             *filter2.makeWithLocalMatrix(local_matrix));
}

TEST(DisplayListImageFilter, ErodeNotEquals) {
  DlErodeImageFilter filter1(5.0, 6.0);
  DlErodeImageFilter filter2(7.0, 6.0);
  DlErodeImageFilter filter3(5.0, 8.0);

  TestNotEquals(filter1, filter2, "Radius X differs");
  TestNotEquals(filter1, filter3, "Radius Y differs");
}

TEST(DisplayListImageFilter, ErodeBounds) {
  DlErodeImageFilter filter = DlErodeImageFilter(5, 10);
  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  SkRect expected_output_bounds = input_bounds.makeInset(5, 10);
  TestBounds(filter, input_bounds, expected_output_bounds);
}

TEST(DisplayListImageFilter, MatrixConstructor) {
  DlMatrixImageFilter filter(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                               0.5, 3.0, 15,  //
                                               0.0, 0.0, 1),
                             DlImageSampling::kLinear);
}

TEST(DisplayListImageFilter, MatrixShared) {
  DlMatrixImageFilter filter(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                               0.5, 3.0, 15,  //
                                               0.0, 0.0, 1),
                             DlImageSampling::kLinear);

  ASSERT_NE(filter.shared().get(), &filter);
  ASSERT_EQ(*filter.shared(), filter);
}

TEST(DisplayListImageFilter, MatrixAsMatrix) {
  DlMatrixImageFilter filter(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                               0.5, 3.0, 15,  //
                                               0.0, 0.0, 1),
                             DlImageSampling::kLinear);

  ASSERT_NE(filter.asMatrix(), nullptr);
  ASSERT_EQ(filter.asMatrix(), &filter);
}

TEST(DisplayListImageFilter, MatrixContents) {
  SkMatrix matrix = SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                      0.5, 3.0, 15,  //
                                      0.0, 0.0, 1);
  DlMatrixImageFilter filter(matrix, DlImageSampling::kLinear);

  ASSERT_EQ(filter.matrix(), matrix);
  ASSERT_EQ(filter.sampling(), DlImageSampling::kLinear);
}

TEST(DisplayListImageFilter, MatrixEquals) {
  SkMatrix matrix = SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                      0.5, 3.0, 15,  //
                                      0.0, 0.0, 1);
  DlMatrixImageFilter filter1(matrix, DlImageSampling::kLinear);
  DlMatrixImageFilter filter2(matrix, DlImageSampling::kLinear);

  TestEquals(filter1, filter2);
}

TEST(DisplayListImageFilter, MatrixWithLocalMatrixEquals) {
  SkMatrix matrix = SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                      0.5, 3.0, 15,  //
                                      0.0, 0.0, 1);
  DlMatrixImageFilter filter1(matrix, DlImageSampling::kLinear);
  DlMatrixImageFilter filter2(matrix, DlImageSampling::kLinear);

  SkMatrix local_matrix = SkMatrix::Translate(10, 10);
  TestEquals(*filter1.makeWithLocalMatrix(local_matrix),
             *filter2.makeWithLocalMatrix(local_matrix));
}

TEST(DisplayListImageFilter, MatrixNotEquals) {
  SkMatrix matrix1 = SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                       0.5, 3.0, 15,  //
                                       0.0, 0.0, 1);
  SkMatrix matrix2 = SkMatrix::MakeAll(5.0, 0.0, 10,  //
                                       0.5, 3.0, 15,  //
                                       0.0, 0.0, 1);
  DlMatrixImageFilter filter1(matrix1, DlImageSampling::kLinear);
  DlMatrixImageFilter filter2(matrix2, DlImageSampling::kLinear);
  DlMatrixImageFilter filter3(matrix1, DlImageSampling::kNearestNeighbor);

  TestNotEquals(filter1, filter2, "Matrix differs");
  TestNotEquals(filter1, filter3, "Sampling differs");
}

TEST(DisplayListImageFilter, MatrixBounds) {
  SkMatrix matrix = SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                      0.5, 3.0, 7,   //
                                      0.0, 0.0, 1);
  SkMatrix inverse;
  ASSERT_TRUE(matrix.invert(&inverse));
  DlMatrixImageFilter filter(matrix, DlImageSampling::kLinear);
  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  SkPoint expectedOutputQuad[4] = {
      {50, 77},    // (20,20) => (20*2 + 10, 20/2 + 20*3 + 7) == (50, 77)
      {50, 257},   // (20,80) => (20*2 + 10, 20/2 + 80*3 + 7) == (50, 257)
      {170, 287},  // (80,80) => (80*2 + 10, 80/2 + 80*3 + 7) == (170, 287)
      {170, 107},  // (80,20) => (80*2 + 10, 80/2 + 20*3 + 7) == (170, 107)
  };
  TestBounds(filter, input_bounds, expectedOutputQuad);
}

TEST(DisplayListImageFilter, ComposeConstructor) {
  DlMatrixImageFilter outer(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                              0.5, 3.0, 15,  //
                                              0.0, 0.0, 1),
                            DlImageSampling::kLinear);
  DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror);
  DlComposeImageFilter filter(outer, inner);
}

TEST(DisplayListImageFilter, ComposeShared) {
  DlMatrixImageFilter outer(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                              0.5, 3.0, 15,  //
                                              0.0, 0.0, 1),
                            DlImageSampling::kLinear);
  DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror);
  DlComposeImageFilter filter(outer, inner);

  ASSERT_NE(filter.shared().get(), &filter);
  ASSERT_EQ(*filter.shared(), filter);
}

TEST(DisplayListImageFilter, ComposeAsCompose) {
  DlMatrixImageFilter outer(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                              0.5, 3.0, 15,  //
                                              0.0, 0.0, 1),
                            DlImageSampling::kLinear);
  DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror);
  DlComposeImageFilter filter(outer, inner);

  ASSERT_NE(filter.asCompose(), nullptr);
  ASSERT_EQ(filter.asCompose(), &filter);
}

TEST(DisplayListImageFilter, ComposeContents) {
  DlMatrixImageFilter outer(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                              0.5, 3.0, 15,  //
                                              0.0, 0.0, 1),
                            DlImageSampling::kLinear);
  DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror);
  DlComposeImageFilter filter(outer, inner);

  ASSERT_EQ(*filter.outer().get(), outer);
  ASSERT_EQ(*filter.inner().get(), inner);
}

TEST(DisplayListImageFilter, ComposeEquals) {
  DlMatrixImageFilter outer1(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                               0.5, 3.0, 15,  //
                                               0.0, 0.0, 1),
                             DlImageSampling::kLinear);
  DlBlurImageFilter inner1(5.0, 6.0, DlTileMode::kMirror);
  DlComposeImageFilter filter1(outer1, inner1);

  DlMatrixImageFilter outer2(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                               0.5, 3.0, 15,  //
                                               0.0, 0.0, 1),
                             DlImageSampling::kLinear);
  DlBlurImageFilter inner2(5.0, 6.0, DlTileMode::kMirror);
  DlComposeImageFilter filter2(outer1, inner1);

  TestEquals(filter1, filter2);
}

TEST(DisplayListImageFilter, ComposeWithLocalMatrixEquals) {
  DlMatrixImageFilter outer1(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                               0.5, 3.0, 15,  //
                                               0.0, 0.0, 1),
                             DlImageSampling::kLinear);
  DlBlurImageFilter inner1(5.0, 6.0, DlTileMode::kMirror);
  DlComposeImageFilter filter1(outer1, inner1);

  DlMatrixImageFilter outer2(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                               0.5, 3.0, 15,  //
                                               0.0, 0.0, 1),
                             DlImageSampling::kLinear);
  DlBlurImageFilter inner2(5.0, 6.0, DlTileMode::kMirror);
  DlComposeImageFilter filter2(outer1, inner1);

  SkMatrix local_matrix = SkMatrix::Translate(10, 10);
  TestEquals(*filter1.makeWithLocalMatrix(local_matrix),
             *filter2.makeWithLocalMatrix(local_matrix));
}

TEST(DisplayListImageFilter, ComposeNotEquals) {
  DlMatrixImageFilter outer1(SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                               0.5, 3.0, 15,  //
                                               0.0, 0.0, 1),
                             DlImageSampling::kLinear);
  DlBlurImageFilter inner1(5.0, 6.0, DlTileMode::kMirror);

  DlMatrixImageFilter outer2(SkMatrix::MakeAll(5.0, 0.0, 10,  //
                                               0.5, 3.0, 15,  //
                                               0.0, 0.0, 1),
                             DlImageSampling::kLinear);
  DlBlurImageFilter inner2(7.0, 6.0, DlTileMode::kMirror);

  DlComposeImageFilter filter1(outer1, inner1);
  DlComposeImageFilter filter2(outer2, inner1);
  DlComposeImageFilter filter3(outer1, inner2);

  TestNotEquals(filter1, filter2, "Outer differs");
  TestNotEquals(filter1, filter3, "Inner differs");
}

TEST(DisplayListImageFilter, ComposeBounds) {
  DlDilateImageFilter outer = DlDilateImageFilter(5, 10);
  DlBlurImageFilter inner = DlBlurImageFilter(12, 5, DlTileMode::kDecal);
  DlComposeImageFilter filter = DlComposeImageFilter(outer, inner);
  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  SkRect expected_output_bounds =
      input_bounds.makeOutset(36, 15).makeOutset(5, 10);
  TestBounds(filter, input_bounds, expected_output_bounds);
}

static void TestUnboundedBounds(DlImageFilter& filter,
                                const SkRect& sourceBounds,
                                const SkRect& expectedOutputBounds,
                                const SkRect& expectedInputBounds) {
  SkRect bounds;
  EXPECT_EQ(filter.map_local_bounds(sourceBounds, bounds), nullptr);
  EXPECT_EQ(bounds, expectedOutputBounds);

  SkIRect ibounds;
  EXPECT_EQ(
      filter.map_device_bounds(sourceBounds.roundOut(), SkMatrix::I(), ibounds),
      nullptr);
  EXPECT_EQ(ibounds, expectedOutputBounds.roundOut());

  EXPECT_EQ(filter.get_input_device_bounds(sourceBounds.roundOut(),
                                           SkMatrix::I(), ibounds),
            nullptr);
  EXPECT_EQ(ibounds, expectedInputBounds.roundOut());
}

TEST(DisplayListImageFilter, ComposeBoundsWithUnboundedInner) {
  auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  auto expected_bounds = SkRect::MakeLTRB(5, 2, 95, 98);

  DlBlendColorFilter color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);
  auto outer = DlBlurImageFilter(5.0, 6.0, DlTileMode::kRepeat);
  auto inner = DlColorFilterImageFilter(color_filter.shared());
  auto composed = DlComposeImageFilter(outer.shared(), inner.shared());

  TestUnboundedBounds(composed, input_bounds, expected_bounds, expected_bounds);
}

TEST(DisplayListImageFilter, ComposeBoundsWithUnboundedOuter) {
  auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  auto expected_bounds = SkRect::MakeLTRB(5, 2, 95, 98);

  DlBlendColorFilter color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);
  auto outer = DlColorFilterImageFilter(color_filter.shared());
  auto inner = DlBlurImageFilter(5.0, 6.0, DlTileMode::kRepeat);
  auto composed = DlComposeImageFilter(outer.shared(), inner.shared());

  TestUnboundedBounds(composed, input_bounds, expected_bounds, expected_bounds);
}

TEST(DisplayListImageFilter, ComposeBoundsWithUnboundedInnerAndOuter) {
  auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  auto expected_bounds = input_bounds;

  DlBlendColorFilter color_filter1(DlColor::kRed(), DlBlendMode::kSrcOver);
  DlBlendColorFilter color_filter2(DlColor::kBlue(), DlBlendMode::kSrcOver);
  auto outer = DlColorFilterImageFilter(color_filter1.shared());
  auto inner = DlColorFilterImageFilter(color_filter2.shared());
  auto composed = DlComposeImageFilter(outer.shared(), inner.shared());

  TestUnboundedBounds(composed, input_bounds, expected_bounds, expected_bounds);
}

// See https://github.com/flutter/flutter/issues/108433
TEST(DisplayListImageFilter, Issue108433) {
  auto input_bounds = SkIRect::MakeLTRB(20, 20, 80, 80);
  auto expected_bounds = SkIRect::MakeLTRB(5, 2, 95, 98);

  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);
  auto dl_outer = DlBlurImageFilter(5.0, 6.0, DlTileMode::kRepeat);
  auto dl_inner = DlColorFilterImageFilter(dl_color_filter.shared());
  auto dl_compose = DlComposeImageFilter(dl_outer, dl_inner);

  SkIRect dl_bounds;
  ASSERT_EQ(
      dl_compose.map_device_bounds(input_bounds, SkMatrix::I(), dl_bounds),
      nullptr);
  ASSERT_EQ(dl_bounds, expected_bounds);
}

TEST(DisplayListImageFilter, ColorFilterConstructor) {
  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter(dl_color_filter);
}

TEST(DisplayListImageFilter, ColorFilterShared) {
  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter(dl_color_filter);

  ASSERT_EQ(*filter.shared(), filter);
}

TEST(DisplayListImageFilter, ColorFilterAsColorFilter) {
  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter(dl_color_filter);

  ASSERT_NE(filter.asColorFilter(), nullptr);
  ASSERT_EQ(filter.asColorFilter(), &filter);
}

TEST(DisplayListImageFilter, ColorFilterContents) {
  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter(dl_color_filter);

  ASSERT_EQ(*filter.color_filter().get(), dl_color_filter);
}

TEST(DisplayListImageFilter, ColorFilterEquals) {
  DlBlendColorFilter dl_color_filter1(DlColor::kRed(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter1(dl_color_filter1);

  DlBlendColorFilter dl_color_filter2(DlColor::kRed(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter2(dl_color_filter2);

  TestEquals(filter1, filter2);
}

TEST(DisplayListImageFilter, ColorFilterWithLocalMatrixEquals) {
  DlBlendColorFilter dl_color_filter1(DlColor::kRed(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter1(dl_color_filter1);

  DlBlendColorFilter dl_color_filter2(DlColor::kRed(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter2(dl_color_filter2);

  SkMatrix local_matrix = SkMatrix::Translate(10, 10);
  TestEquals(*filter1.makeWithLocalMatrix(local_matrix),
             *filter2.makeWithLocalMatrix(local_matrix));
}

TEST(DisplayListImageFilter, ColorFilterNotEquals) {
  DlBlendColorFilter dl_color_filter1(DlColor::kRed(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter1(dl_color_filter1);

  DlBlendColorFilter dl_color_filter2(DlColor::kBlue(), DlBlendMode::kLighten);
  DlColorFilterImageFilter filter2(dl_color_filter2);

  DlBlendColorFilter dl_color_filter3(DlColor::kRed(), DlBlendMode::kDarken);
  DlColorFilterImageFilter filter3(dl_color_filter3);

  TestNotEquals(filter1, filter2, "Color differs");
  TestNotEquals(filter1, filter3, "Blend Mode differs");
}

TEST(DisplayListImageFilter, ColorFilterBounds) {
  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcIn);
  DlColorFilterImageFilter filter(dl_color_filter);
  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  TestBounds(filter, input_bounds, input_bounds);
}

TEST(DisplayListImageFilter, ColorFilterModifiesTransparencyBounds) {
  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);
  DlColorFilterImageFilter filter(dl_color_filter);
  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);
  TestInvalidBounds(filter, SkMatrix::I(), input_bounds);
}

TEST(DisplayListImageFilter, LocalImageFilterBounds) {
  auto filter_matrix = SkMatrix::MakeAll(2.0, 0.0, 10,  //
                                         0.5, 3.0, 15,  //
                                         0.0, 0.0, 1);
  std::vector<sk_sp<SkImageFilter>> sk_filters{
      SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr),
      SkImageFilters::ColorFilter(
          SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcOver), nullptr),
      SkImageFilters::Dilate(5.0, 10.0, nullptr),
      SkImageFilters::MatrixTransform(
          filter_matrix, SkSamplingOptions(SkFilterMode::kLinear), nullptr),
      SkImageFilters::Compose(
          SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr),
          SkImageFilters::ColorFilter(
              SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcOver),
              nullptr))};

  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);
  std::vector<std::shared_ptr<DlImageFilter>> dl_filters{
      std::make_shared<DlBlurImageFilter>(5.0, 6.0, DlTileMode::kRepeat),
      std::make_shared<DlColorFilterImageFilter>(dl_color_filter.shared()),
      std::make_shared<DlDilateImageFilter>(5, 10),
      std::make_shared<DlMatrixImageFilter>(filter_matrix,
                                            DlImageSampling::kLinear),
      std::make_shared<DlComposeImageFilter>(
          std::make_shared<DlBlurImageFilter>(5.0, 6.0, DlTileMode::kRepeat),
          std::make_shared<DlColorFilterImageFilter>(
              dl_color_filter.shared()))};

  auto persp = SkMatrix::I();
  persp.setPerspY(0.001);
  std::vector<SkMatrix> matrices = {
      SkMatrix::Translate(10.0, 10.0),
      SkMatrix::Scale(2.0, 2.0).preTranslate(10.0, 10.0),
      SkMatrix::RotateDeg(45).preTranslate(5.0, 5.0), persp};
  std::vector<SkMatrix> bounds_matrices{SkMatrix::Translate(5.0, 10.0),
                                        SkMatrix::Scale(2.0, 2.0)};

  for (unsigned i = 0; i < sk_filters.size(); i++) {
    for (unsigned j = 0; j < matrices.size(); j++) {
      for (unsigned k = 0; k < bounds_matrices.size(); k++) {
        auto desc = "filter " + std::to_string(i + 1)             //
                    + ", filter matrix " + std::to_string(j + 1)  //
                    + ", bounds matrix " + std::to_string(k + 1);
        auto& m = matrices[j];
        auto& bounds_matrix = bounds_matrices[k];
        auto sk_local_filter = sk_filters[i]->makeWithLocalMatrix(m);
        auto dl_local_filter = dl_filters[i]->makeWithLocalMatrix(m);
        if (!sk_local_filter || !dl_local_filter) {
          // Temporarily relax the equivalence testing to allow Skia to expand
          // their behavior. Once the Skia fixes are rolled in, the
          // DlImageFilter should adapt  to the new rules.
          // See https://github.com/flutter/flutter/issues/114723
          ASSERT_TRUE(sk_local_filter || !dl_local_filter) << desc;
          continue;
        }
        {
          auto input_bounds = SkIRect::MakeLTRB(20, 20, 80, 80);
          SkIRect sk_rect, dl_rect;
          sk_rect = sk_local_filter->filterBounds(
              input_bounds, bounds_matrix,
              SkImageFilter::MapDirection::kForward_MapDirection);
          if (dl_local_filter->map_device_bounds(input_bounds, bounds_matrix,
                                                 dl_rect)) {
            ASSERT_EQ(sk_rect, dl_rect) << desc;
          } else {
            ASSERT_TRUE(dl_local_filter->modifies_transparent_black()) << desc;
            ASSERT_FALSE(sk_local_filter->canComputeFastBounds()) << desc;
          }
        }
        {
          // Test for: Know the outset bounds to get the inset bounds
          // Skia have some bounds calculate error of DilateFilter and
          // MatrixFilter
          // Skia issue: https://bugs.chromium.org/p/skia/issues/detail?id=13444
          // flutter issue: https://github.com/flutter/flutter/issues/108693
          if (i == 2 || i == 3) {
            continue;
          }
          auto outset_bounds = SkIRect::MakeLTRB(20, 20, 80, 80);
          SkIRect sk_rect, dl_rect;
          sk_rect = sk_local_filter->filterBounds(
              outset_bounds, bounds_matrix,
              SkImageFilter::MapDirection::kReverse_MapDirection);
          if (dl_local_filter->get_input_device_bounds(
                  outset_bounds, bounds_matrix, dl_rect)) {
            ASSERT_EQ(sk_rect, dl_rect) << desc;
          } else {
            ASSERT_TRUE(dl_local_filter->modifies_transparent_black());
            ASSERT_FALSE(sk_local_filter->canComputeFastBounds());
          }
        }
      }
    }
  }
}

}  // namespace testing
}  // namespace flutter
