/*
 * Copyright (c) 2013, Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"

#include "base/test/null_task_runner.h"
#include "build/build_config.h"
#include "components/viz/test/test_raster_interface.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
#include "third_party/blink/renderer/core/html/canvas/image_data.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
#include "third_party/blink/renderer/platform/graphics/test/fake_gles2_interface.h"
#include "third_party/blink/renderer/platform/graphics/test/fake_web_graphics_context_3d_provider.h"
#include "third_party/blink/renderer/platform/graphics/test/gpu_test_utils.h"
#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPixelRef.h"
#include "third_party/skia/include/core/SkSurface.h"

namespace blink {

class ExceptionState;

class ImageBitmapTest : public testing::Test {
 protected:
  ImageBitmapTest()
      : scoped_memory_cache_(MakeGarbageCollected<MemoryCache>(
            blink::scheduler::GetSingleThreadTaskRunnerForTesting())) {}

  void SetUp() override {
    sk_sp<SkSurface> surface =
        SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10));
    surface->getCanvas()->clear(0xFFFFFFFF);
    image_ = surface->makeImageSnapshot();

    sk_sp<SkSurface> surface2 =
        SkSurfaces::Raster(SkImageInfo::MakeN32Premul(5, 5));
    surface2->getCanvas()->clear(0xAAAAAAAA);
    image2_ = surface2->makeImageSnapshot();

    test_context_provider_ = viz::TestContextProvider::CreateRaster();
    InitializeSharedGpuContext(test_context_provider_.get());
  }

  void TearDown() override {
    // Garbage collection is required prior to switching out the
    // test's memory cache; image resources are released, evicting
    // them from the cache.
    ThreadState::Current()->CollectAllGarbageForTesting(
        ThreadState::StackState::kNoHeapPointers);

    SharedGpuContext::Reset();
  }

 protected:
  test::TaskEnvironment task_environment_;
  scoped_refptr<viz::TestContextProvider> test_context_provider_;
  sk_sp<SkImage> image_, image2_;
  ScopedMemoryCacheForTesting scoped_memory_cache_;
};

TEST_F(ImageBitmapTest, ImageResourceConsistency) {
  const ImageBitmapOptions* default_options = ImageBitmapOptions::Create();
  auto dummy = std::make_unique<DummyPageHolder>(gfx::Size(800, 600));
  auto* image_element =
      MakeGarbageCollected<HTMLImageElement>(dummy->GetDocument());
  sk_sp<SkColorSpace> src_rgb_color_space = SkColorSpace::MakeSRGB();
  SkImageInfo raster_image_info =
      SkImageInfo::MakeN32Premul(5, 5, src_rgb_color_space);
  sk_sp<SkSurface> surface(SkSurfaces::Raster(raster_image_info));
  sk_sp<SkImage> image = surface->makeImageSnapshot();
  ImageResourceContent* original_image_content =
      ImageResourceContent::CreateLoaded(
          UnacceleratedStaticBitmapImage::Create(image).get());
  image_element->SetImageForTest(original_image_content);

  std::optional<gfx::Rect> crop_rect =
      gfx::Rect(0, 0, image_element->width(), image_element->height());
  auto* image_bitmap_no_crop = MakeGarbageCollected<ImageBitmap>(
      image_element, crop_rect, default_options);
  ASSERT_TRUE(image_bitmap_no_crop);
  crop_rect =
      gfx::Rect(image_element->width() / 2, image_element->height() / 2,
                image_element->width() / 2, image_element->height() / 2);
  auto* image_bitmap_interior_crop = MakeGarbageCollected<ImageBitmap>(
      image_element, crop_rect, default_options);
  ASSERT_TRUE(image_bitmap_interior_crop);
  crop_rect =
      gfx::Rect(-image_element->width() / 2, -image_element->height() / 2,
                image_element->width(), image_element->height());
  auto* image_bitmap_exterior_crop = MakeGarbageCollected<ImageBitmap>(
      image_element, crop_rect, default_options);
  ASSERT_TRUE(image_bitmap_exterior_crop);
  crop_rect = gfx::Rect(-image_element->width(), -image_element->height(),
                        image_element->width(), image_element->height());
  auto* image_bitmap_outside_crop = MakeGarbageCollected<ImageBitmap>(
      image_element, crop_rect, default_options);
  ASSERT_TRUE(image_bitmap_outside_crop);

  ASSERT_EQ(image_bitmap_no_crop->BitmapImage()
                ->PaintImageForCurrentFrame()
                .GetSwSkImage(),
            image_element->CachedImage()
                ->GetImage()
                ->PaintImageForCurrentFrame()
                .GetSwSkImage());
  ASSERT_NE(image_bitmap_interior_crop->BitmapImage()
                ->PaintImageForCurrentFrame()
                .GetSwSkImage(),
            image_element->CachedImage()
                ->GetImage()
                ->PaintImageForCurrentFrame()
                .GetSwSkImage());
  ASSERT_NE(image_bitmap_exterior_crop->BitmapImage()
                ->PaintImageForCurrentFrame()
                .GetSwSkImage(),
            image_element->CachedImage()
                ->GetImage()
                ->PaintImageForCurrentFrame()
                .GetSwSkImage());

  scoped_refptr<StaticBitmapImage> empty_image =
      image_bitmap_outside_crop->BitmapImage();
  ASSERT_NE(empty_image->PaintImageForCurrentFrame().GetSwSkImage(),
            image_element->CachedImage()
                ->GetImage()
                ->PaintImageForCurrentFrame()
                .GetSwSkImage());
}

// Verifies that ImageBitmaps constructed from HTMLImageElements hold a
// reference to the original Image if the HTMLImageElement src is changed.
TEST_F(ImageBitmapTest, ImageBitmapSourceChanged) {
  auto dummy = std::make_unique<DummyPageHolder>(gfx::Size(800, 600));
  auto* image = MakeGarbageCollected<HTMLImageElement>(dummy->GetDocument());
  sk_sp<SkColorSpace> src_rgb_color_space = SkColorSpace::MakeSRGB();
  SkImageInfo raster_image_info =
      SkImageInfo::MakeN32Premul(5, 5, src_rgb_color_space);
  sk_sp<SkSurface> raster_surface(SkSurfaces::Raster(raster_image_info));
  sk_sp<SkImage> raster_image = raster_surface->makeImageSnapshot();
  ImageResourceContent* original_image_content =
      ImageResourceContent::CreateLoaded(
          UnacceleratedStaticBitmapImage::Create(raster_image).get());
  image->SetImageForTest(original_image_content);

  const ImageBitmapOptions* default_options = ImageBitmapOptions::Create();
  std::optional<gfx::Rect> crop_rect =
      gfx::Rect(0, 0, image->width(), image->height());
  auto* image_bitmap =
      MakeGarbageCollected<ImageBitmap>(image, crop_rect, default_options);
  ASSERT_TRUE(image_bitmap);
  ASSERT_EQ(
      image_bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSwSkImage(),
      original_image_content->GetImage()
          ->PaintImageForCurrentFrame()
          .GetSwSkImage());

  ImageResourceContent* new_image_content = ImageResourceContent::CreateLoaded(
      UnacceleratedStaticBitmapImage::Create(image2_).get());
  image->SetImageForTest(new_image_content);

  {
    ASSERT_EQ(
        image_bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSwSkImage(),
        original_image_content->GetImage()
            ->PaintImageForCurrentFrame()
            .GetSwSkImage());
    SkImage* image1 = image_bitmap->BitmapImage()
                          ->PaintImageForCurrentFrame()
                          .GetSwSkImage()
                          .get();
    ASSERT_NE(image1, nullptr);
    SkImage* image2 = original_image_content->GetImage()
                          ->PaintImageForCurrentFrame()
                          .GetSwSkImage()
                          .get();
    ASSERT_NE(image2, nullptr);
    ASSERT_EQ(image1, image2);
  }

  {
    ASSERT_NE(
        image_bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSwSkImage(),
        new_image_content->GetImage()
            ->PaintImageForCurrentFrame()
            .GetSwSkImage());
    SkImage* image1 = image_bitmap->BitmapImage()
                          ->PaintImageForCurrentFrame()
                          .GetSwSkImage()
                          .get();
    ASSERT_NE(image1, nullptr);
    SkImage* image2 = new_image_content->GetImage()
                          ->PaintImageForCurrentFrame()
                          .GetSwSkImage()
                          .get();
    ASSERT_NE(image2, nullptr);
    ASSERT_NE(image1, image2);
  }
}

static void TestImageBitmapTextureBacked(
    scoped_refptr<StaticBitmapImage> bitmap,
    gfx::Rect& rect,
    ImageBitmapOptions* options,
    bool is_texture_backed) {
  auto* image_bitmap = MakeGarbageCollected<ImageBitmap>(bitmap, rect, options);
  EXPECT_TRUE(image_bitmap);
  EXPECT_EQ(image_bitmap->BitmapImage()->IsTextureBacked(), is_texture_backed);
}

TEST_F(ImageBitmapTest, AvoidGPUReadback) {
  auto client_si = gpu::ClientSharedImage::CreateForTesting(
      gpu::SHARED_IMAGE_USAGE_RASTER_READ);
  gpu::SyncToken token;
  token.Set(gpu::CommandBufferNamespace::GPU_IO,
            gpu::CommandBufferId::FromUnsafeValue(64), 100);

  scoped_refptr<StaticBitmapImage> bitmap =
      AcceleratedStaticBitmapImage::CreateFromCanvasSharedImage(
          std::move(client_si), token, kPremul_SkAlphaType, gfx::HDRMetadata(),
          SharedGpuContext::ContextProviderWrapper(),
          base::PlatformThread::CurrentRef(),
          base::MakeRefCounted<base::NullTaskRunner>(), base::DoNothing());
  ASSERT_TRUE(bitmap->IsTextureBacked());

  auto* image_bitmap = MakeGarbageCollected<ImageBitmap>(bitmap);
  EXPECT_TRUE(image_bitmap);
  EXPECT_TRUE(image_bitmap->BitmapImage()->IsTextureBacked());

  gfx::Rect image_bitmap_rect(25, 25, 50, 50);
  {
    ImageBitmapOptions* image_bitmap_options = ImageBitmapOptions::Create();
    TestImageBitmapTextureBacked(bitmap, image_bitmap_rect,
                                 image_bitmap_options, true);
  }

  std::list<V8ImageOrientation::Enum> image_orientations = {
      V8ImageOrientation::Enum::kNone, V8ImageOrientation::Enum::kFlipY};
  std::list<V8PremultiplyAlpha::Enum> premultiply_alphas = {
      V8PremultiplyAlpha::Enum::kNone, V8PremultiplyAlpha::Enum::kPremultiply,
      V8PremultiplyAlpha::Enum::kDefault};
  std::list<V8ColorSpaceConversion::Enum> color_space_conversions = {
      V8ColorSpaceConversion::Enum::kNone,
      V8ColorSpaceConversion::Enum::kDefault};
  std::list<int> resize_widths = {25, 50, 75};
  std::list<int> resize_heights = {25, 50, 75};
  std::list<V8ResizeQuality::Enum> resize_qualities = {
      V8ResizeQuality::Enum::kPixelated, V8ResizeQuality::Enum::kLow,
      V8ResizeQuality::Enum::kMedium, V8ResizeQuality::Enum::kHigh};

  for (auto image_orientation : image_orientations) {
    for (auto premultiply_alpha : premultiply_alphas) {
      for (auto color_space_conversion : color_space_conversions) {
        for (auto resize_width : resize_widths) {
          for (auto resize_height : resize_heights) {
            for (auto resize_quality : resize_qualities) {
              ImageBitmapOptions* image_bitmap_options =
                  ImageBitmapOptions::Create();
              image_bitmap_options->setImageOrientation(image_orientation);
              image_bitmap_options->setPremultiplyAlpha(premultiply_alpha);
              image_bitmap_options->setColorSpaceConversion(
                  color_space_conversion);
              image_bitmap_options->setResizeWidth(resize_width);
              image_bitmap_options->setResizeHeight(resize_height);
              image_bitmap_options->setResizeQuality(resize_quality);
              // Setting premuliply_alpha to none will cause a read back.
              // Otherwise, we expect to avoid GPU readback when creaing an
              // ImageBitmap from a texture-backed source.
              TestImageBitmapTextureBacked(
                  bitmap, image_bitmap_rect, image_bitmap_options,
                  premultiply_alpha != V8PremultiplyAlpha::Enum::kNone);
            }
          }
        }
      }
    }
  }
}

// This test is failing on asan-clang-phone because memory allocation is
// declined. See <http://crbug.com/782286>.
// This test is failing on fuchsia because memory allocation is
// declined.  <http://crbug.com/1090252>.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
#define MAYBE_CreateImageBitmapFromTooBigImageDataDoesNotCrash \
  DISABLED_CreateImageBitmapFromTooBigImageDataDoesNotCrash
#else
#define MAYBE_CreateImageBitmapFromTooBigImageDataDoesNotCrash \
  CreateImageBitmapFromTooBigImageDataDoesNotCrash
#endif

// This test verifies if requesting a large ImageData and creating an
// ImageBitmap from that does not crash. crbug.com/780358
TEST_F(ImageBitmapTest,
       MAYBE_CreateImageBitmapFromTooBigImageDataDoesNotCrash) {
  constexpr int kWidth = 1 << 28;  // 256M pixels width, resulting in 1GB data.
  ImageData* image_data = ImageData::CreateForTest(gfx::Size(kWidth, 1));
  DCHECK(image_data);
  ImageBitmapOptions* options = ImageBitmapOptions::Create();
  options->setColorSpaceConversion(V8ColorSpaceConversion::Enum::kDefault);
  auto* image_bitmap = MakeGarbageCollected<ImageBitmap>(
      image_data, gfx::Rect(image_data->Size()), options);
  DCHECK(image_bitmap);
}

TEST_F(ImageBitmapTest, ImageAlphaState) {
  auto dummy = std::make_unique<DummyPageHolder>(gfx::Size(800, 600));
  auto* image_element =
      MakeGarbageCollected<HTMLImageElement>(dummy->GetDocument());

  // Load a 2x2 png file which has pixels (255, 102, 153, 0). It is a fully
  // transparent image.
  ResourceRequest resource_request(
      "data:image/"
      "png;base64,"
      "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAEUlEQVR42mP8nzaTAQQYYQwA"
      "LssD/5ca+r8AAAAASUVORK5CYII=");

  FetchParameters params =
      FetchParameters::CreateForTest(std::move(resource_request));

  ImageResourceContent* resource_content =
      ImageResourceContent::Fetch(params, dummy->GetDocument().Fetcher());

  image_element->SetImageForTest(resource_content);

  ImageBitmapOptions* options = ImageBitmapOptions::Create();
  // ImageBitmap created from unpremul source image result.
  options->setPremultiplyAlpha(V8PremultiplyAlpha::Enum::kNone);

  // Additional operation shouldn't affect alpha op.
  options->setImageOrientation(V8ImageOrientation::Enum::kFlipY);

  std::optional<gfx::Rect> crop_rect =
      gfx::Rect(0, 0, image_element->width(), image_element->height());
  auto* image_bitmap =
      MakeGarbageCollected<ImageBitmap>(image_element, crop_rect, options);
  ASSERT_TRUE(image_bitmap);

  // Read 1 pixel
  sk_sp<SkImage> result =
      image_bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSwSkImage();
  SkPixmap pixmap;
  ASSERT_TRUE(result->peekPixels(&pixmap));
  const uint32_t* pixels = pixmap.addr32();

  SkColorType result_color_type = result->colorType();
  SkColor expected = SkColorSetARGB(0, 0, 0, 0);

  switch (result_color_type) {
    case SkColorType::kRGBA_8888_SkColorType:
      // Set ABGR value as reverse of RGBA
      expected =
          SkColorSetARGB(/* a */ 0, /* b */ 153, /* g */ 102, /* r */ 255);
      break;
    case SkColorType::kBGRA_8888_SkColorType:
      // Set ARGB value as reverse of BGRA
      expected =
          SkColorSetARGB(/* a */ 0, /* r */ 255, /* g */ 102, /* b */ 153);
      break;
    default:
      NOTREACHED();
  }

  ASSERT_EQ(pixels[0], expected);
}

}  // namespace blink
