// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/lens/lens_overlay_image_helper.h"

#include <array>

#include "base/functional/callback.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/lens/lens_overlay_colors.h"
#include "components/lens/lens_features.h"
#include "lens_overlay_image_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/lens_server_proto/lens_overlay_image_crop.pb.h"
#include "third_party/lens_server_proto/lens_overlay_image_data.pb.h"
#include "third_party/lens_server_proto/lens_overlay_polygon.pb.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/webp_codec.h"
#include "ui/gfx/color_analysis.h"
#include "ui/gfx/geometry/rect.h"

namespace lens {

constexpr int kImageCompressionQuality = 30;
constexpr int kImageMaxArea = 1000000;
constexpr int kImageMaxHeight = 1000;
constexpr int kImageMaxWidth = 1000;
constexpr int kImageMaxAreaTier3 = 3000000;
constexpr int kImageMaxHeightTier3 = 3000;
constexpr int kImageMaxWidthTier3 = 3000;
constexpr int kImageMaxAreaTier2 = 2000000;
constexpr int kImageMaxHeightTier2 = 1500;
constexpr int kImageMaxWidthTier2 = 1500;
constexpr int kImageMaxAreaTier1 = 400000;
constexpr int kImageMaxHeightTier1 = 500;
constexpr int kImageMaxWidthTier1 = 500;
constexpr int kImageDownscaleUIScalingFactor = 2;

class LensOverlayImageHelperTest : public testing::Test {
 public:
  void SetUp() override {
    // Set all the feature params here to keep the test consistent if future
    // default values are changed.
    feature_list_.InitAndEnableFeatureWithParameters(
        lens::features::kLensOverlay,
        {{"enable-tiered-downscaling", "false"},
         {"image-compression-quality",
          base::StringPrintf("%d", kImageCompressionQuality)},
         {"image-dimensions-max-area", base::StringPrintf("%d", kImageMaxArea)},
         {"image-dimensions-max-height",
          base::StringPrintf("%d", kImageMaxHeight)},
         {"image-dimensions-max-width",
          base::StringPrintf("%d", kImageMaxWidth)}});
  }

  const SkBitmap CreateNonEmptyBitmap(int width, int height) {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(width, height);
    bitmap.eraseColor(SK_ColorGREEN);
    return bitmap;
  }

  std::string GetJpegBytesForBitmap(const SkBitmap& bitmap) {
    std::optional<std::vector<uint8_t>> data =
        gfx::JPEGCodec::Encode(bitmap, kImageCompressionQuality);
    return std::string(base::as_string_view(data.value()));
  }

  std::string GetWebpBytesForBitmap(const SkBitmap& bitmap) {
    std::optional<std::vector<uint8_t>> data =
        gfx::WebpCodec::Encode(bitmap, kImageCompressionQuality);
    return std::string(base::as_string_view(data.value()));
  }

  // Helper to create a region search region for the given rect.
  lens::mojom::CenterRotatedBoxPtr CenterBoxForRegion(const gfx::Rect& region) {
    auto box = lens::mojom::CenterRotatedBox::New();
    box->box = gfx::RectF(region.CenterPoint().x(), region.CenterPoint().y(),
                          region.width(), region.height());
    box->coordinate_type = lens::mojom::CenterRotatedBox_CoordinateType::kImage;
    return box;
  }

  void EnableTieredDownscaling() {
    feature_list_.Reset();
    feature_list_.InitAndEnableFeatureWithParameters(
        lens::features::kLensOverlay,
        {{"enable-tiered-downscaling", "true"},
         {"image-compression-quality",
          base::StringPrintf("%d", kImageCompressionQuality)},
         {"image-dimensions-max-area", base::StringPrintf("%d", kImageMaxArea)},
         {"image-dimensions-max-height",
          base::StringPrintf("%d", kImageMaxHeight)},
         {"image-dimensions-max-width",
          base::StringPrintf("%d", kImageMaxWidth)},
         {"image-dimensions-max-area-tier-3",
          base::StringPrintf("%d", kImageMaxAreaTier3)},
         {"image-dimensions-max-height-tier-3",
          base::StringPrintf("%d", kImageMaxHeightTier3)},
         {"image-dimensions-max-width-tier-3",
          base::StringPrintf("%d", kImageMaxWidthTier3)},
         {"image-dimensions-max-area-tier-2",
          base::StringPrintf("%d", kImageMaxAreaTier2)},
         {"image-dimensions-max-height-tier-2",
          base::StringPrintf("%d", kImageMaxHeightTier2)},
         {"image-dimensions-max-width-tier-2",
          base::StringPrintf("%d", kImageMaxWidthTier2)},
         {"image-dimensions-max-area-tier-1",
          base::StringPrintf("%d", kImageMaxAreaTier1)},
         {"image-dimensions-max-height-tier-1",
          base::StringPrintf("%d", kImageMaxHeightTier1)},
         {"image-dimensions-max-width-tier-1",
          base::StringPrintf("%d", kImageMaxWidthTier1)},
         {"image-downscale-ui-scaling-factor",
          base::StringPrintf("%d", kImageDownscaleUIScalingFactor)}});
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
};

TEST_F(LensOverlayImageHelperTest, DownscaleAndEncodeBitmapMaxSize) {
  const SkBitmap bitmap = CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  lens::ImageData image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, 2, ref_counted_logs);
  std::string expected_output = GetJpegBytesForBitmap(bitmap);

  ASSERT_EQ(kImageMaxWidth, image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeight, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      1,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(6239, ref_counted_logs->client_logs()
                      .phase_latencies_metadata()
                      .phase(0)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest, DownscaleAndEncodeBitmapSmallSize) {
  const SkBitmap bitmap = CreateNonEmptyBitmap(/*width=*/100, /*height=*/100);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  lens::ImageData image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, 2, ref_counted_logs);
  std::string expected_output = GetJpegBytesForBitmap(bitmap);

  ASSERT_EQ(bitmap.width(), image_data.image_metadata().width());
  ASSERT_EQ(bitmap.height(), image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      1,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(359, ref_counted_logs->client_logs()
                     .phase_latencies_metadata()
                     .phase(0)
                     .image_encode_data()
                     .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest, DownscaleAndEncodeBitmapLargeSize) {
  const int scale = 2;
  const SkBitmap bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth * scale, kImageMaxHeight * scale);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  lens::ImageData image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, 2, ref_counted_logs);

  const SkBitmap expected_bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight);
  std::string expected_output = GetJpegBytesForBitmap(expected_bitmap);

  // The image should have been resized and scaled down.
  ASSERT_EQ(kImageMaxWidth, image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeight, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      2,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight * scale * scale,
            ref_counted_logs->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .original_image_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight, ref_counted_logs->client_logs()
                                                  .phase_latencies_metadata()
                                                  .phase(0)
                                                  .image_downscale_data()
                                                  .downscaled_image_size());
  ASSERT_EQ(6239, ref_counted_logs->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest, DownscaleAndEncodeBitmapHeightTooLarge) {
  const int scale = 2;
  const SkBitmap bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight * scale);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  lens::ImageData image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, 2, ref_counted_logs);

  const SkBitmap expected_bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth / scale, kImageMaxHeight);
  std::string expected_output = GetJpegBytesForBitmap(expected_bitmap);

  // The image should have been resized and scaled down.
  ASSERT_EQ(kImageMaxWidth / scale, image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeight, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
}

TEST_F(LensOverlayImageHelperTest, DownscaleAndEncodeBitmapWidthTooLarge) {
  const int scale = 2;
  const SkBitmap bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth * scale, kImageMaxHeight);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  lens::ImageData image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, 2, ref_counted_logs);

  const SkBitmap expected_bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight / scale);
  std::string expected_output = GetJpegBytesForBitmap(expected_bitmap);

  // The image should have been resized and scaled down.
  ASSERT_EQ(kImageMaxWidth, image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeight / scale, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      2,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight * scale,
            ref_counted_logs->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .original_image_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight / scale,
            ref_counted_logs->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .downscaled_image_size());
  ASSERT_EQ(3309, ref_counted_logs->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest, AddSignificantRegions) {
  lens::ImageData image_data;
  std::vector<lens::mojom::CenterRotatedBoxPtr> significant_region_boxes;
  gfx::Rect view_bounds(100, 150, 200, 100);
  gfx::Rect image_bounds(125, 25, 50, 50);
  significant_region_boxes.emplace_back(
      GetCenterRotatedBoxFromTabViewAndImageBounds(view_bounds, view_bounds,
                                                   image_bounds));

  AddSignificantRegions(image_data, std::move(significant_region_boxes));

  ASSERT_EQ(1, image_data.significant_regions_size());
  ASSERT_EQ(0.75, image_data.significant_regions(0).bounding_box().center_x());
  ASSERT_EQ(0.5, image_data.significant_regions(0).bounding_box().center_y());
  ASSERT_EQ(0.25, image_data.significant_regions(0).bounding_box().width());
  ASSERT_EQ(0.5, image_data.significant_regions(0).bounding_box().height());
  ASSERT_EQ(lens::CoordinateType::NORMALIZED,
            image_data.significant_regions(0).bounding_box().coordinate_type());
}

TEST_F(LensOverlayImageHelperTest, CropBitmapToRegion) {
  const SkBitmap bitmap = CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight);
  gfx::Rect region(10, 10, 50, 50);
  std::string expected_output =
      GetJpegBytesForBitmap(CreateNonEmptyBitmap(50, 50));

  const SkBitmap cropped =
      lens::CropBitmapToRegion(bitmap, CenterBoxForRegion(region));

  ASSERT_EQ(expected_output, GetJpegBytesForBitmap(cropped));
}

TEST_F(LensOverlayImageHelperTest,
       DownscaleAndEncodeBitmapRegionNonRegionRequest) {
  const SkBitmap bitmap = CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight);
  lens::mojom::CenterRotatedBoxPtr region;
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          bitmap, std::move(region), std::nullopt, ref_counted_logs);
  ASSERT_FALSE(image_crop_and_bitmap.has_value());
  ASSERT_EQ(
      0,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
}

TEST_F(LensOverlayImageHelperTest,
       DownscaleAndEncodeBitmapRegionPreservesZoomedCropMetadata) {
  const SkBitmap bitmap = CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight);
  gfx::Rect region(10, 10, 50, 50);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();

  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          bitmap, CenterBoxForRegion(region), std::nullopt, ref_counted_logs);

  ASSERT_TRUE(image_crop_and_bitmap.has_value());
  const auto& image_crop = image_crop_and_bitmap->image_crop;

  // Verify spatial context contract is fulfilled.
  EXPECT_TRUE(image_crop.has_zoomed_crop());
  EXPECT_EQ(kImageMaxWidth, image_crop.zoomed_crop().parent_width());
  EXPECT_EQ(kImageMaxHeight, image_crop.zoomed_crop().parent_height());
  EXPECT_GT(image_crop.zoomed_crop().zoom(), 0.0);

  // Verify normalized region coordinates are populated.
  EXPECT_TRUE(image_crop.zoomed_crop().has_crop());
  EXPECT_GT(image_crop.zoomed_crop().crop().width(), 0.0);
  EXPECT_GT(image_crop.zoomed_crop().crop().height(), 0.0);
  EXPECT_EQ(lens::CoordinateType::NORMALIZED,
            image_crop.zoomed_crop().crop().coordinate_type());
}

TEST_F(LensOverlayImageHelperTest,
       DownscaleAndEncodeBitmapRegionPreservesMetadataOnEncodingFailure) {
  // Create a bitmap with valid dimensions but without allocating pixels.
  // This causes encoding to fail gracefully while satisfying spatial
  // preconditions.
  SkBitmap unallocated_bitmap;
  unallocated_bitmap.setInfo(SkImageInfo::MakeN32Premul(100, 100));

  gfx::Rect region(10, 10, 50, 50);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();

  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          unallocated_bitmap, CenterBoxForRegion(region), std::nullopt,
          ref_counted_logs);

  ASSERT_TRUE(image_crop_and_bitmap.has_value());
  const auto& image_crop = image_crop_and_bitmap->image_crop;

  // Verify spatial context contract is still perfectly fulfilled.
  EXPECT_TRUE(image_crop.has_zoomed_crop());
  EXPECT_EQ(100, image_crop.zoomed_crop().parent_width());
  EXPECT_EQ(100, image_crop.zoomed_crop().parent_height());
  EXPECT_GE(image_crop.zoomed_crop().zoom(), 0.0);

  // Verify normalized region coordinates are populated.
  EXPECT_TRUE(image_crop.zoomed_crop().has_crop());
  EXPECT_GT(image_crop.zoomed_crop().crop().width(), 0.0);
  EXPECT_GT(image_crop.zoomed_crop().crop().height(), 0.0);

  // Verify that the image payload wrapper remains unset due to encoding
  // failure.
  EXPECT_FALSE(image_crop.has_image());
}

TEST_F(LensOverlayImageHelperTest, DownscaleAndEncodeBitmapRegionMaxSize) {
  const SkBitmap bitmap = CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight);
  gfx::Rect region(0, 0, kImageMaxWidth, kImageMaxHeight);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          bitmap, CenterBoxForRegion(region), std::nullopt, ref_counted_logs);
  std::string expected_output = GetJpegBytesForBitmap(bitmap);

  const auto& image_crop = image_crop_and_bitmap->image_crop;
  ASSERT_EQ(kImageMaxWidth, image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight, image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(1, image_crop.zoomed_crop().zoom());
  ASSERT_EQ(kImageMaxWidth * .5, image_crop.zoomed_crop().crop().center_x() *
                                     image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight * .5,
            image_crop.zoomed_crop().crop().center_y() *
                image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(kImageMaxWidth, image_crop.zoomed_crop().crop().width() *
                                image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight, image_crop.zoomed_crop().crop().height() *
                                 image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(0, image_crop.zoomed_crop().crop().rotation_z());
  ASSERT_EQ(lens::CoordinateType::NORMALIZED,
            image_crop.zoomed_crop().crop().coordinate_type());
  ASSERT_EQ(expected_output, image_crop.image().image_content());
  ASSERT_EQ(
      2,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight, ref_counted_logs->client_logs()
                                                  .phase_latencies_metadata()
                                                  .phase(0)
                                                  .image_downscale_data()
                                                  .original_image_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight, ref_counted_logs->client_logs()
                                                  .phase_latencies_metadata()
                                                  .phase(0)
                                                  .image_downscale_data()
                                                  .downscaled_image_size());
  ASSERT_EQ(6239, ref_counted_logs->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest, DownscaleAndEncodeBitmapRegionSmallSize) {
  const SkBitmap bitmap = CreateNonEmptyBitmap(/*width=*/100, /*height=*/100);
  gfx::Rect region(10, 10, 50, 50);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          bitmap, CenterBoxForRegion(region), std::nullopt, ref_counted_logs);

  const SkBitmap region_bitmap =
      CreateNonEmptyBitmap(/*width=*/50, /*height=*/50);
  std::string expected_output = GetJpegBytesForBitmap(region_bitmap);
  const auto& image_crop = image_crop_and_bitmap->image_crop;

  ASSERT_EQ(100, image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(100, image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(1, image_crop.zoomed_crop().zoom());
  ASSERT_EQ(35, image_crop.zoomed_crop().crop().center_x() *
                    image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(35, image_crop.zoomed_crop().crop().center_y() *
                    image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(50, image_crop.zoomed_crop().crop().width() *
                    image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(50, image_crop.zoomed_crop().crop().height() *
                    image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(0, image_crop.zoomed_crop().crop().rotation_z());
  ASSERT_EQ(lens::CoordinateType::NORMALIZED,
            image_crop.zoomed_crop().crop().coordinate_type());
  ASSERT_EQ(expected_output, image_crop.image().image_content());
  ASSERT_EQ(
      2,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(10000, ref_counted_logs->client_logs()
                       .phase_latencies_metadata()
                       .phase(0)
                       .image_downscale_data()
                       .original_image_size());
  ASSERT_EQ(2500, ref_counted_logs->client_logs()
                      .phase_latencies_metadata()
                      .phase(0)
                      .image_downscale_data()
                      .downscaled_image_size());
  ASSERT_EQ(309, ref_counted_logs->client_logs()
                     .phase_latencies_metadata()
                     .phase(1)
                     .image_encode_data()
                     .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest,
       DownscaleAndEncodeBitmapRegionLargeFullImageSize) {
  const int scale = 2;
  const SkBitmap bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth * scale, kImageMaxHeight * scale);

  gfx::Rect region(10, 10, 50, 50);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          bitmap, CenterBoxForRegion(region), std::nullopt, ref_counted_logs);

  const SkBitmap region_bitmap =
      CreateNonEmptyBitmap(/*width=*/50, /*height=*/50);
  std::string expected_output = GetJpegBytesForBitmap(region_bitmap);
  const auto& image_crop = image_crop_and_bitmap->image_crop;

  ASSERT_EQ(kImageMaxWidth * scale, image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight * scale, image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(1, image_crop.zoomed_crop().zoom());
  ASSERT_EQ(35, image_crop.zoomed_crop().crop().center_x() *
                    image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(35, image_crop.zoomed_crop().crop().center_y() *
                    image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(50, image_crop.zoomed_crop().crop().width() *
                    image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(50, image_crop.zoomed_crop().crop().height() *
                    image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(0, image_crop.zoomed_crop().crop().rotation_z());
  ASSERT_EQ(lens::CoordinateType::NORMALIZED,
            image_crop.zoomed_crop().crop().coordinate_type());
  ASSERT_EQ(expected_output, image_crop.image().image_content());
  ASSERT_EQ(
      2,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight * scale * scale,
            ref_counted_logs->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .original_image_size());
  ASSERT_EQ(2500, ref_counted_logs->client_logs()
                      .phase_latencies_metadata()
                      .phase(0)
                      .image_downscale_data()
                      .downscaled_image_size());
  ASSERT_EQ(309, ref_counted_logs->client_logs()
                     .phase_latencies_metadata()
                     .phase(1)
                     .image_encode_data()
                     .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest,
       DownscaleAndEncodeBitmapRegionLargeRegionSize) {
  const int full_image_scale = 3;
  const SkBitmap bitmap = CreateNonEmptyBitmap(
      kImageMaxWidth * full_image_scale, kImageMaxHeight * full_image_scale);

  const int region_scale = 2;
  gfx::Rect region(10, 10, kImageMaxWidth * region_scale,
                   kImageMaxHeight * region_scale);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          bitmap, CenterBoxForRegion(region), std::nullopt, ref_counted_logs);

  const SkBitmap region_bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight);
  std::string expected_output = GetJpegBytesForBitmap(region_bitmap);
  const auto& image_crop = image_crop_and_bitmap->image_crop;

  ASSERT_EQ(kImageMaxWidth * full_image_scale,
            image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight * full_image_scale,
            image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(.5, image_crop.zoomed_crop().zoom());
  ASSERT_EQ(10 + kImageMaxWidth, image_crop.zoomed_crop().crop().center_x() *
                                     image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(10 + kImageMaxHeight,
            image_crop.zoomed_crop().crop().center_y() *
                image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(kImageMaxWidth * region_scale,
            image_crop.zoomed_crop().crop().width() *
                image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight * region_scale,
            image_crop.zoomed_crop().crop().height() *
                image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(0, image_crop.zoomed_crop().crop().rotation_z());
  ASSERT_EQ(lens::CoordinateType::NORMALIZED,
            image_crop.zoomed_crop().crop().coordinate_type());
  ASSERT_EQ(expected_output, image_crop.image().image_content());
  ASSERT_EQ(
      2,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(
      kImageMaxWidth * kImageMaxHeight * full_image_scale * full_image_scale,
      ref_counted_logs->client_logs()
          .phase_latencies_metadata()
          .phase(0)
          .image_downscale_data()
          .original_image_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight, ref_counted_logs->client_logs()
                                                  .phase_latencies_metadata()
                                                  .phase(0)
                                                  .image_downscale_data()
                                                  .downscaled_image_size());
  ASSERT_EQ(6239, ref_counted_logs->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest,
       DownscaleAndEncodeBitmapRegionWidthTooLarge) {
  const int full_image_scale = 3;
  const SkBitmap bitmap = CreateNonEmptyBitmap(
      kImageMaxWidth * full_image_scale, kImageMaxHeight * full_image_scale);

  const int region_scale = 2;
  gfx::Rect region(10, 10, kImageMaxWidth * region_scale, kImageMaxHeight);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          bitmap, CenterBoxForRegion(region), std::nullopt, ref_counted_logs);

  const SkBitmap region_bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight / region_scale);
  std::string expected_output = GetJpegBytesForBitmap(region_bitmap);
  const auto& image_crop = image_crop_and_bitmap->image_crop;

  ASSERT_EQ(kImageMaxWidth * full_image_scale,
            image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight * full_image_scale,
            image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(.5, image_crop.zoomed_crop().zoom());
  ASSERT_EQ(10 + kImageMaxWidth, image_crop.zoomed_crop().crop().center_x() *
                                     image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(10 + kImageMaxHeight / 2,
            image_crop.zoomed_crop().crop().center_y() *
                image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(kImageMaxWidth * region_scale,
            image_crop.zoomed_crop().crop().width() *
                image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight, image_crop.zoomed_crop().crop().height() *
                                 image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(0, image_crop.zoomed_crop().crop().rotation_z());
  ASSERT_EQ(lens::CoordinateType::NORMALIZED,
            image_crop.zoomed_crop().crop().coordinate_type());
  ASSERT_EQ(expected_output, image_crop.image().image_content());
  ASSERT_EQ(
      2,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(
      kImageMaxWidth * kImageMaxHeight * full_image_scale * full_image_scale,
      ref_counted_logs->client_logs()
          .phase_latencies_metadata()
          .phase(0)
          .image_downscale_data()
          .original_image_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight / region_scale,
            ref_counted_logs->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .downscaled_image_size());
  ASSERT_EQ(3309, ref_counted_logs->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest,
       DownscaleAndEncodeBitmapRegionHeightTooLarge) {
  const int full_image_scale = 3;
  const SkBitmap bitmap = CreateNonEmptyBitmap(
      kImageMaxWidth * full_image_scale, kImageMaxHeight * full_image_scale);

  const int region_scale = 2;
  gfx::Rect region(10, 10, kImageMaxWidth, kImageMaxHeight * region_scale);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          bitmap, CenterBoxForRegion(region), std::nullopt, ref_counted_logs);

  const SkBitmap region_bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth / region_scale, kImageMaxHeight);
  std::string expected_output = GetJpegBytesForBitmap(region_bitmap);
  const auto& image_crop = image_crop_and_bitmap->image_crop;

  ASSERT_EQ(kImageMaxWidth * full_image_scale,
            image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight * full_image_scale,
            image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(.5, image_crop.zoomed_crop().zoom());
  ASSERT_EQ(10 + kImageMaxWidth / 2,
            image_crop.zoomed_crop().crop().center_x() *
                image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(10 + kImageMaxHeight,
            image_crop.zoomed_crop().crop().center_y() *
                image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(kImageMaxWidth, image_crop.zoomed_crop().crop().width() *
                                image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(kImageMaxHeight * region_scale,
            image_crop.zoomed_crop().crop().height() *
                image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(0, image_crop.zoomed_crop().crop().rotation_z());
  ASSERT_EQ(lens::CoordinateType::NORMALIZED,
            image_crop.zoomed_crop().crop().coordinate_type());
  ASSERT_EQ(expected_output, image_crop.image().image_content());
  ASSERT_EQ(
      2,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(
      kImageMaxWidth * kImageMaxHeight * full_image_scale * full_image_scale,
      ref_counted_logs->client_logs()
          .phase_latencies_metadata()
          .phase(0)
          .image_downscale_data()
          .original_image_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight / region_scale,
            ref_counted_logs->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .downscaled_image_size());
  ASSERT_EQ(3309, ref_counted_logs->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest,
       DownscaleAndEncodeBitmapWithOpaqueRegionBytes) {
  const SkBitmap image_bitmap = CreateNonEmptyBitmap(1000, 1000);
  SkBitmap region_bitmap = CreateNonEmptyBitmap(300, 300);
  region_bitmap.setAlphaType(kOpaque_SkAlphaType);
  gfx::Rect region(0, 0, 100, 100);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          image_bitmap, CenterBoxForRegion(region),
          std::make_optional<SkBitmap>(region_bitmap), ref_counted_logs);
  std::string expected_output = GetJpegBytesForBitmap(region_bitmap);
  const auto& image_crop = image_crop_and_bitmap->image_crop;

  ASSERT_EQ(1000, image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(1000, image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(3, image_crop.zoomed_crop().zoom());
  ASSERT_EQ(50, image_crop.zoomed_crop().crop().center_x() *
                    image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(50, image_crop.zoomed_crop().crop().center_y() *
                    image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(100, image_crop.zoomed_crop().crop().width() *
                     image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(100, image_crop.zoomed_crop().crop().height() *
                     image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(0, image_crop.zoomed_crop().crop().rotation_z());
  ASSERT_EQ(lens::CoordinateType::NORMALIZED,
            image_crop.zoomed_crop().crop().coordinate_type());
  ASSERT_EQ(expected_output, image_crop.image().image_content());
  ASSERT_EQ(
      1,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(827, ref_counted_logs->client_logs()
                     .phase_latencies_metadata()
                     .phase(0)
                     .image_encode_data()
                     .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest,
       DownscaleAndEncodeBitmapWithTransparentRegionBytes) {
  const SkBitmap image_bitmap = CreateNonEmptyBitmap(1000, 1000);
  const SkBitmap region_bitmap = CreateNonEmptyBitmap(300, 300);
  gfx::Rect region(0, 0, 100, 100);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  std::optional<lens::ImageCropAndBitmap> image_crop_and_bitmap =
      lens::DownscaleAndEncodeBitmapRegionIfNeeded(
          image_bitmap, CenterBoxForRegion(region),
          std::make_optional<SkBitmap>(region_bitmap), ref_counted_logs);
  std::string expected_output = GetWebpBytesForBitmap(region_bitmap);
  const auto& image_crop = image_crop_and_bitmap->image_crop;

  ASSERT_EQ(1000, image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(1000, image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(3, image_crop.zoomed_crop().zoom());
  ASSERT_EQ(50, image_crop.zoomed_crop().crop().center_x() *
                    image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(50, image_crop.zoomed_crop().crop().center_y() *
                    image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(100, image_crop.zoomed_crop().crop().width() *
                     image_crop.zoomed_crop().parent_width());
  ASSERT_EQ(100, image_crop.zoomed_crop().crop().height() *
                     image_crop.zoomed_crop().parent_height());
  ASSERT_EQ(0, image_crop.zoomed_crop().crop().rotation_z());
  ASSERT_EQ(lens::CoordinateType::NORMALIZED,
            image_crop.zoomed_crop().crop().coordinate_type());
  ASSERT_EQ(expected_output, image_crop.image().image_content());
  ASSERT_EQ(
      1,
      ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(280, ref_counted_logs->client_logs()
                     .phase_latencies_metadata()
                     .phase(0)
                     .image_encode_data()
                     .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest,
       GetCenterRotatedBoxFromTabViewAndImageBounds) {
  gfx::Rect tab_bounds(50, 50, 400, 200);
  gfx::Rect view_bounds(100, 150, 200, 100);
  gfx::Rect image_bounds(125, 25, 50, 50);

  auto result = GetCenterRotatedBoxFromTabViewAndImageBounds(
      tab_bounds, view_bounds, image_bounds);

  ASSERT_EQ(0.5, result->box.x());
  ASSERT_EQ(0.75, result->box.y());
  ASSERT_EQ(0.125, result->box.width());
  ASSERT_EQ(0.25, result->box.height());
  ASSERT_EQ(lens::mojom::CenterRotatedBox_CoordinateType::kNormalized,
            result->coordinate_type);
}

TEST_F(LensOverlayImageHelperTest,
       GetCenterRotatedBoxFromTabViewAndImageBoundsClipsWhenImageOutOfView) {
  gfx::Rect tab_bounds(0, 0, 400, 200);
  gfx::Rect view_bounds(0, 0, 200, 300);
  gfx::Rect image_bounds(100, 100, 200, 200);

  auto result = GetCenterRotatedBoxFromTabViewAndImageBounds(
      tab_bounds, view_bounds, image_bounds);

  ASSERT_EQ(0.375, result->box.x());
  ASSERT_EQ(0.75, result->box.y());
  ASSERT_EQ(0.25, result->box.width());
  ASSERT_EQ(0.5, result->box.height());
  ASSERT_EQ(lens::mojom::CenterRotatedBox_CoordinateType::kNormalized,
            result->coordinate_type);
}


TEST_F(LensOverlayImageHelperTest, TieredDownscalingTier3) {
  EnableTieredDownscaling();

  int image_scale = 2;
  int ui_scale = 1;
  const SkBitmap bitmap = CreateNonEmptyBitmap(
      kImageMaxWidthTier3 * image_scale, kImageMaxHeightTier3);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_logs_scale1 =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  lens::ImageData image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, ui_scale, ref_logs_scale1);

  SkBitmap expected_bitmap = CreateNonEmptyBitmap(
      kImageMaxWidthTier3, kImageMaxHeightTier3 / image_scale);
  std::string expected_output = GetJpegBytesForBitmap(expected_bitmap);

  // Downscales to Tier 3 when UI scale is less than finch defined UI scaling
  // factor threshold (kImageDownscaleUIScalingFactor).
  ASSERT_EQ(kImageMaxWidthTier3, image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeightTier3 / image_scale,
            image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      2,
      ref_logs_scale1->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidthTier3 * kImageMaxHeightTier3 * image_scale,
            ref_logs_scale1->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .original_image_size());
  ASSERT_EQ(kImageMaxWidthTier3 * kImageMaxHeightTier3 / image_scale,
            ref_logs_scale1->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .downscaled_image_size());
  ASSERT_EQ(26793, ref_logs_scale1->client_logs()
                       .phase_latencies_metadata()
                       .phase(1)
                       .image_encode_data()
                       .encoded_image_size_bytes());

  ui_scale = 2;
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_logs_scale2 =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, ui_scale, ref_logs_scale2);

  expected_bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth, kImageMaxHeight / image_scale);
  expected_output = GetJpegBytesForBitmap(expected_bitmap);

  // Downscales to Tier 1.5 when UI scale is more than finch defined UI scaling
  // factor threshold (kImageDownscaleUIScalingFactor).
  ASSERT_EQ(kImageMaxWidth, image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeight / image_scale,
            image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      2,
      ref_logs_scale2->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidthTier3 * kImageMaxHeightTier3 * image_scale,
            ref_logs_scale2->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .original_image_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight / image_scale,
            ref_logs_scale2->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .downscaled_image_size());
  ASSERT_EQ(3309, ref_logs_scale2->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest, TieredDownscalingTier2) {
  EnableTieredDownscaling();

  int image_scale = 2;
  int ui_scale = 1;
  const SkBitmap bitmap = CreateNonEmptyBitmap(
      kImageMaxWidthTier2, kImageMaxHeightTier2 * image_scale);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_logs_scale1 =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  lens::ImageData image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, ui_scale, ref_logs_scale1);

  SkBitmap expected_bitmap = CreateNonEmptyBitmap(
      kImageMaxWidthTier2 / image_scale, kImageMaxHeightTier2);
  std::string expected_output = GetJpegBytesForBitmap(expected_bitmap);

  // Downscales to Tier 2 when UI scale is less than finch defined UI scaling
  // factor threshold (kImageDownscaleUIScalingFactor).
  ASSERT_EQ(kImageMaxWidthTier2 / image_scale,
            image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeightTier2, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      2,
      ref_logs_scale1->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidthTier2 * kImageMaxHeightTier2 * image_scale,
            ref_logs_scale1->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .original_image_size());
  ASSERT_EQ(kImageMaxWidthTier2 * kImageMaxHeightTier2 / image_scale,
            ref_logs_scale1->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .downscaled_image_size());
  ASSERT_EQ(6912, ref_logs_scale1->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());

  ui_scale = 2;
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_logs_scale2 =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, ui_scale, ref_logs_scale2);

  expected_bitmap =
      CreateNonEmptyBitmap(kImageMaxWidth / image_scale, kImageMaxHeight);
  expected_output = GetJpegBytesForBitmap(expected_bitmap);

  // Downscales to Tier 1.5 when UI scale is more than finch defined UI scaling
  // factor threshold (kImageDownscaleUIScalingFactor).
  ASSERT_EQ(kImageMaxWidth / image_scale, image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeight, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      2,
      ref_logs_scale2->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidthTier2 * kImageMaxHeightTier2 * image_scale,
            ref_logs_scale2->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .original_image_size());
  ASSERT_EQ(kImageMaxWidth * kImageMaxHeight / image_scale,
            ref_logs_scale2->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .downscaled_image_size());
  ASSERT_EQ(3309, ref_logs_scale2->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest, TieredDownscalingTier1) {
  EnableTieredDownscaling();

  int image_scale = 2;
  int ui_scale = 1;
  const SkBitmap bitmap = CreateNonEmptyBitmap(
      kImageMaxWidthTier1, kImageMaxHeightTier1 * image_scale);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_logs_scale1 =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  lens::ImageData image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, ui_scale, ref_logs_scale1);

  SkBitmap expected_bitmap = CreateNonEmptyBitmap(
      kImageMaxWidthTier1 / image_scale, kImageMaxHeightTier1);
  std::string expected_output = GetJpegBytesForBitmap(expected_bitmap);

  // Downscales to Tier 1 when UI scale is less than finch defined UI scaling
  // factor threshold (kImageDownscaleUIScalingFactor).
  ASSERT_EQ(kImageMaxWidthTier1 / image_scale,
            image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeightTier1, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      2,
      ref_logs_scale1->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidthTier1 * kImageMaxHeightTier1 * image_scale,
            ref_logs_scale1->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .original_image_size());
  ASSERT_EQ(kImageMaxWidthTier1 * kImageMaxHeightTier1 / image_scale,
            ref_logs_scale1->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .downscaled_image_size());
  ASSERT_EQ(1053, ref_logs_scale1->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());

  ui_scale = 2;
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_logs_scale2 =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, ui_scale, ref_logs_scale2);

  // Downscales to Tier 1 when UI scale is less than finch defined UI scaling
  // factor threshold (kImageDownscaleUIScalingFactor). Essentially verify that
  // ui_scale is ignored at Tier 1.
  ASSERT_EQ(kImageMaxWidthTier1 / image_scale,
            image_data.image_metadata().width());
  ASSERT_EQ(kImageMaxHeightTier1, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      2,
      ref_logs_scale2->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(kImageMaxWidthTier1 * kImageMaxHeightTier1 * image_scale,
            ref_logs_scale2->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .original_image_size());
  ASSERT_EQ(kImageMaxWidthTier1 * kImageMaxHeightTier1 / image_scale,
            ref_logs_scale2->client_logs()
                .phase_latencies_metadata()
                .phase(0)
                .image_downscale_data()
                .downscaled_image_size());
  ASSERT_EQ(1053, ref_logs_scale2->client_logs()
                      .phase_latencies_metadata()
                      .phase(1)
                      .image_encode_data()
                      .encoded_image_size_bytes());
}

TEST_F(LensOverlayImageHelperTest, TieredDownscalingNoCompression) {
  EnableTieredDownscaling();

  int ui_scale = 1;
  const SkBitmap bitmap = CreateNonEmptyBitmap(/*width=*/100, /*height=*/100);
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_logs_scale1 =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  lens::ImageData image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, ui_scale, ref_logs_scale1);

  std::string expected_output = GetJpegBytesForBitmap(bitmap);

  // No downscaling when image is less than Tier 1.
  ASSERT_EQ(100, image_data.image_metadata().width());
  ASSERT_EQ(100, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      1,
      ref_logs_scale1->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(359, ref_logs_scale1->client_logs()
                     .phase_latencies_metadata()
                     .phase(0)
                     .image_encode_data()
                     .encoded_image_size_bytes());

  ui_scale = 2;
  scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_logs_scale2 =
      base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
  image_data =
      lens::DownscaleAndEncodeBitmap(bitmap, ui_scale, ref_logs_scale2);

  // Verify that UI Scale does not change no compression flow
  ASSERT_EQ(100, image_data.image_metadata().width());
  ASSERT_EQ(100, image_data.image_metadata().height());
  ASSERT_EQ(expected_output, image_data.payload().image_bytes());
  ASSERT_EQ(
      1,
      ref_logs_scale2->client_logs().phase_latencies_metadata().phase_size());
  ASSERT_EQ(359, ref_logs_scale2->client_logs()
                     .phase_latencies_metadata()
                     .phase(0)
                     .image_encode_data()
                     .encoded_image_size_bytes());
}
}  // namespace lens
