blob: 8b7af0e149ce5c09bc743f52635e66fb727364a5 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/lens/lens_bitmap_processing.h"
#include <optional>
#include <string>
#include <vector>
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "components/lens/ref_counted_lens_overlay_client_logs.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/lens_server_proto/lens_overlay_client_logs.pb.h"
#include "third_party/lens_server_proto/lens_overlay_phase_latencies_metadata.pb.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/webp_codec.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace lens {
constexpr int kImageCompressionQuality = 30;
constexpr int kImageMaxArea = 1000000;
constexpr int kImageMaxHeight = 1000;
constexpr int kImageMaxWidth = 1000;
class LensBitmapProcessingTest : public testing::Test {
public:
void SetUp() override {}
protected:
const SkBitmap CreateOpaqueBitmap(int width, int height) {
SkBitmap bitmap;
bitmap.allocN32Pixels(width, height);
bitmap.eraseColor(SK_ColorGREEN);
bitmap.setAlphaType(kOpaque_SkAlphaType);
return bitmap;
}
lens::ImageData DownscaleAndEncodeBitmap(
SkBitmap bitmap,
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs,
bool enable_webp_encoding = true) {
ImageEncodingOptions image_options{
.enable_webp_encoding = enable_webp_encoding,
.max_size = kImageMaxArea,
.max_height = kImageMaxHeight,
.max_width = kImageMaxWidth,
.compression_quality = kImageCompressionQuality,
};
return lens::DownscaleAndEncodeBitmap(bitmap, ref_counted_logs,
image_options);
}
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()));
}
};
TEST_F(LensBitmapProcessingTest, ShouldDownscaleSize) {
gfx::Size size(10, 10);
EXPECT_TRUE(lens::ShouldDownscaleSize(size, /*max_area=*/10,
/*max_width=*/100, /*max_height=*/5));
EXPECT_TRUE(lens::ShouldDownscaleSize(size, /*max_area=*/10, /*max_width=*/5,
/*max_height=*/100));
// Area is too great, but width and height are less than max_width and
// max_height respectively.
EXPECT_FALSE(lens::ShouldDownscaleSize(
size, /*max_area=*/10, /*max_width=*/100, /*max_height=*/100));
// Width and height are too great, but area is less than max_area.
EXPECT_FALSE(lens::ShouldDownscaleSize(size, /*max_area=*/1000,
/*max_width=*/5, /*max_height=*/5));
}
TEST_F(LensBitmapProcessingTest, DownscaleImage_TooLarge) {
const SkBitmap bitmap = CreateOpaqueBitmap(100, 100);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
SkBitmap downscaled_bitmap = DownscaleImage(bitmap, 50, 50, ref_counted_logs);
std::string expected_output = GetJpegBytesForBitmap(bitmap);
EXPECT_EQ(50, downscaled_bitmap.width());
EXPECT_EQ(50, downscaled_bitmap.height());
ASSERT_EQ(
1,
ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
ASSERT_EQ(100 * 100, ref_counted_logs->client_logs()
.phase_latencies_metadata()
.phase(0)
.image_downscale_data()
.original_image_size());
ASSERT_EQ(50 * 50, ref_counted_logs->client_logs()
.phase_latencies_metadata()
.phase(0)
.image_downscale_data()
.downscaled_image_size());
}
TEST_F(LensBitmapProcessingTest, DownscaleImage_TooWide) {
const SkBitmap bitmap = CreateOpaqueBitmap(200, 100);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
SkBitmap downscaled_bitmap = DownscaleImage(bitmap, 50, 50, ref_counted_logs);
std::string expected_output = GetJpegBytesForBitmap(bitmap);
EXPECT_EQ(50, downscaled_bitmap.width());
EXPECT_EQ(25, downscaled_bitmap.height());
ASSERT_EQ(
1,
ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
ASSERT_EQ(200 * 100, ref_counted_logs->client_logs()
.phase_latencies_metadata()
.phase(0)
.image_downscale_data()
.original_image_size());
ASSERT_EQ(50 * 25, ref_counted_logs->client_logs()
.phase_latencies_metadata()
.phase(0)
.image_downscale_data()
.downscaled_image_size());
}
TEST_F(LensBitmapProcessingTest, DownscaleImage_TooTall) {
const SkBitmap bitmap = CreateOpaqueBitmap(100, 200);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
SkBitmap downscaled_bitmap = DownscaleImage(bitmap, 50, 50, ref_counted_logs);
std::string expected_output = GetJpegBytesForBitmap(bitmap);
EXPECT_EQ(25, downscaled_bitmap.width());
EXPECT_EQ(50, downscaled_bitmap.height());
ASSERT_EQ(
1,
ref_counted_logs->client_logs().phase_latencies_metadata().phase_size());
ASSERT_EQ(100 * 200, ref_counted_logs->client_logs()
.phase_latencies_metadata()
.phase(0)
.image_downscale_data()
.original_image_size());
ASSERT_EQ(25 * 50, ref_counted_logs->client_logs()
.phase_latencies_metadata()
.phase(0)
.image_downscale_data()
.downscaled_image_size());
}
TEST_F(LensBitmapProcessingTest, EncodeImage_Opaque) {
const SkBitmap bitmap = CreateOpaqueBitmap(100, 100);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
scoped_refptr<base::RefCountedBytes> output =
base::MakeRefCounted<base::RefCountedBytes>();
bool success = EncodeImageMaybeWithTransparency(
bitmap, kImageCompressionQuality, output, ref_counted_logs);
std::string expected_output = GetJpegBytesForBitmap(bitmap);
EXPECT_TRUE(success);
EXPECT_EQ(expected_output,
std::string(base::as_string_view(output->as_vector())));
ASSERT_EQ(359, ref_counted_logs->client_logs()
.phase_latencies_metadata()
.phase(0)
.image_encode_data()
.encoded_image_size_bytes());
}
TEST_F(LensBitmapProcessingTest, EncodeImage_Transparent) {
SkBitmap bitmap;
bitmap.allocN32Pixels(100, 100);
bitmap.eraseColor(SK_ColorGREEN);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
scoped_refptr<base::RefCountedBytes> output =
base::MakeRefCounted<base::RefCountedBytes>();
bool success = EncodeImageMaybeWithTransparency(
bitmap, kImageCompressionQuality, output, ref_counted_logs);
std::string expected_output = GetWebpBytesForBitmap(bitmap);
EXPECT_TRUE(success);
EXPECT_EQ(expected_output,
std::string(base::as_string_view(output->as_vector())));
ASSERT_EQ(116, ref_counted_logs->client_logs()
.phase_latencies_metadata()
.phase(0)
.image_encode_data()
.encoded_image_size_bytes());
}
TEST_F(LensBitmapProcessingTest, DownscaleAndEncodeBitmapMaxSize) {
const SkBitmap bitmap = CreateOpaqueBitmap(kImageMaxWidth, kImageMaxHeight);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
lens::ImageData image_data =
DownscaleAndEncodeBitmap(bitmap, ref_counted_logs);
std::string expected_output = GetJpegBytesForBitmap(bitmap);
EXPECT_EQ(kImageMaxWidth, image_data.image_metadata().width());
EXPECT_EQ(kImageMaxHeight, image_data.image_metadata().height());
EXPECT_EQ(expected_output, image_data.payload().image_bytes());
}
TEST_F(LensBitmapProcessingTest, DownscaleAndEncodeBitmapSmallSize) {
const SkBitmap bitmap = CreateOpaqueBitmap(/*width=*/100, /*height=*/100);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
lens::ImageData image_data =
DownscaleAndEncodeBitmap(bitmap, ref_counted_logs);
std::string expected_output = GetJpegBytesForBitmap(bitmap);
EXPECT_EQ(bitmap.width(), image_data.image_metadata().width());
EXPECT_EQ(bitmap.height(), image_data.image_metadata().height());
EXPECT_EQ(expected_output, image_data.payload().image_bytes());
}
TEST_F(LensBitmapProcessingTest, DownscaleAndEncodeBitmapLargeSize) {
const int scale = 2;
const SkBitmap bitmap =
CreateOpaqueBitmap(kImageMaxWidth * scale, kImageMaxHeight * scale);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
lens::ImageData image_data =
DownscaleAndEncodeBitmap(bitmap, ref_counted_logs);
const SkBitmap expected_bitmap =
CreateOpaqueBitmap(kImageMaxWidth, kImageMaxHeight);
std::string expected_output = GetJpegBytesForBitmap(expected_bitmap);
// The image should have been resized and scaled down.
EXPECT_EQ(kImageMaxWidth, image_data.image_metadata().width());
EXPECT_EQ(kImageMaxHeight, image_data.image_metadata().height());
EXPECT_EQ(expected_output, image_data.payload().image_bytes());
}
TEST_F(LensBitmapProcessingTest, DownscaleAndEncodeBitmapHeightTooLarge) {
const int scale = 2;
const SkBitmap bitmap =
CreateOpaqueBitmap(kImageMaxWidth, kImageMaxHeight * scale);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
lens::ImageData image_data =
DownscaleAndEncodeBitmap(bitmap, ref_counted_logs);
const SkBitmap expected_bitmap =
CreateOpaqueBitmap(kImageMaxWidth / scale, kImageMaxHeight);
std::string expected_output = GetJpegBytesForBitmap(expected_bitmap);
// The image should have been resized and scaled down.
EXPECT_EQ(kImageMaxWidth / scale, image_data.image_metadata().width());
EXPECT_EQ(kImageMaxHeight, image_data.image_metadata().height());
EXPECT_EQ(expected_output, image_data.payload().image_bytes());
}
TEST_F(LensBitmapProcessingTest, DownscaleAndEncodeBitmapWidthTooLarge) {
const int scale = 2;
const SkBitmap bitmap =
CreateOpaqueBitmap(kImageMaxWidth * scale, kImageMaxHeight);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
lens::ImageData image_data =
DownscaleAndEncodeBitmap(bitmap, ref_counted_logs);
const SkBitmap expected_bitmap =
CreateOpaqueBitmap(kImageMaxWidth, kImageMaxHeight / scale);
std::string expected_output = GetJpegBytesForBitmap(expected_bitmap);
// The image should have been resized and scaled down.
EXPECT_EQ(kImageMaxWidth, image_data.image_metadata().width());
EXPECT_EQ(kImageMaxHeight / scale, image_data.image_metadata().height());
EXPECT_EQ(expected_output, image_data.payload().image_bytes());
}
TEST_F(LensBitmapProcessingTest, DownscaleAndEncodeBitmapTransparent) {
// Create a bitmap. Since it isn't marked with kOpaque_SkAlphaType the
// output should be WebP instead of JPEG.
SkBitmap bitmap;
bitmap.allocN32Pixels(/*width=*/100, /*height=*/100);
bitmap.eraseColor(SK_ColorGREEN);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
lens::ImageData image_data =
DownscaleAndEncodeBitmap(bitmap, ref_counted_logs);
std::string expected_output = GetWebpBytesForBitmap(bitmap);
EXPECT_EQ(bitmap.width(), image_data.image_metadata().width());
EXPECT_EQ(bitmap.height(), image_data.image_metadata().height());
EXPECT_EQ(expected_output, image_data.payload().image_bytes());
}
TEST_F(LensBitmapProcessingTest,
DownscaleAndEncodeBitmapTransparentWebpDisabled) {
// Create a bitmap. Since it isn't marked with kOpaque_SkAlphaType the
// output should be WebP instead of JPEG.
SkBitmap bitmap;
bitmap.allocN32Pixels(/*width=*/100, /*height=*/100);
bitmap.eraseColor(SK_ColorGREEN);
scoped_refptr<lens::RefCountedLensOverlayClientLogs> ref_counted_logs =
base::MakeRefCounted<lens::RefCountedLensOverlayClientLogs>();
lens::ImageData image_data = DownscaleAndEncodeBitmap(
bitmap, ref_counted_logs, /*enable_webp_encoding=*/false);
std::string expected_output = GetJpegBytesForBitmap(bitmap);
EXPECT_EQ(bitmap.width(), image_data.image_metadata().width());
EXPECT_EQ(bitmap.height(), image_data.image_metadata().height());
EXPECT_EQ(expected_output, image_data.payload().image_bytes());
}
} // namespace lens