// Copyright 2023 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/thumbnail/cc/etc1_thumbnail_helper.h"
#include <cstring>
#include <memory>
#include <utility>
#include <vector>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/thumbnail/cc/thumbnail.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/android_opengl/etc1/etc1.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkMallocPixelRef.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkScalar.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
#include "ui/display/screen.h"
#include "ui/display/screen_base.h"
namespace thumbnail {
namespace {
constexpr int kDimension = 16;
constexpr int kKiB = 1024;
constexpr int kWidth = kDimension * kKiB;
constexpr int kHeight = kDimension;
} // namespace
class Etc1ThumbnailHelperTest : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
interface_ = std::make_unique<thumbnail::Etc1ThumbnailHelper>(
temp_dir_.GetPath(),
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}));
if (screen_.GetNumDisplays() == 0) {
screen_.display_list().AddDisplay({1, gfx::Rect(kWidth, kHeight)},
display::DisplayList::Type::PRIMARY);
}
display::Screen::SetScreenInstance(&screen_);
}
void TearDown() override { display::Screen::SetScreenInstance(nullptr); }
thumbnail::Etc1ThumbnailHelper& GetInterface() { return *interface_; }
SkPaint SetupPaint() {
SkColor colors[] = {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE};
SkScalar pos[] = {0, SK_Scalar1 / 2, SK_Scalar1};
SkPaint paint;
paint.setShader(SkGradientShader::MakeSweep(256, 256, colors, pos, 3));
return paint;
}
base::FilePath GetFile(int tab_id) { return interface_->GetFilePath(tab_id); }
content::BrowserTaskEnvironment task_environment_;
private:
std::unique_ptr<thumbnail::Etc1ThumbnailHelper> interface_;
base::ScopedTempDir temp_dir_;
display::ScreenBase screen_;
};
TEST_F(Etc1ThumbnailHelperTest, CompressAndDecompressThumbnail) {
// Create a bitmap
SkBitmap image;
ASSERT_TRUE(image.tryAllocN32Pixels(kWidth, kHeight));
SkCanvas canvas(image);
canvas.drawPaint(SetupPaint());
image.setImmutable();
sk_sp<SkPixelRef> compressed_data_copy;
gfx::Size data_size_copy;
// Compress the bitmap
base::RunLoop loop1;
base::OnceCallback<void(sk_sp<SkPixelRef>, const gfx::Size&)> compress_once =
base::BindOnce(
[](sk_sp<SkPixelRef>* compressed_data_copy, gfx::Size* data_size_copy,
sk_sp<SkPixelRef> compressed_data, const gfx::Size& data_size) {
EXPECT_GT(compressed_data->width(), 0);
EXPECT_GT(compressed_data->height(), 0);
EXPECT_GT(data_size.width(), 0);
EXPECT_GT(data_size.height(), 0);
gfx::Size buffer_size =
gfx::Size(compressed_data->width(), compressed_data->height());
EXPECT_EQ(data_size, buffer_size);
SkBitmap raw_data;
raw_data.allocPixels(SkImageInfo::MakeN32(buffer_size.width(),
buffer_size.height(),
kOpaque_SkAlphaType));
bool success = etc1_decode_image(
reinterpret_cast<unsigned char*>(compressed_data->pixels()),
reinterpret_cast<unsigned char*>(raw_data.getPixels()),
buffer_size.width(), buffer_size.height(),
raw_data.bytesPerPixel(), raw_data.rowBytes());
EXPECT_TRUE(success);
*compressed_data_copy = compressed_data;
*data_size_copy = data_size;
},
&compressed_data_copy, &data_size_copy)
.Then(loop1.QuitClosure());
GetInterface().Compress(image, true, std::move(compress_once));
loop1.Run();
// Decompress the image
base::RunLoop loop2;
base::OnceCallback<void(bool, const SkBitmap&)> decompress_once =
base::BindOnce(
[](SkBitmap* image, bool success, const SkBitmap& decompressed_data) {
EXPECT_TRUE(success);
EXPECT_FALSE(decompressed_data.empty());
EXPECT_EQ(decompressed_data.width(), image->width());
EXPECT_EQ(decompressed_data.height(), image->height());
},
&image)
.Then(loop2.QuitClosure());
GetInterface().Decompress(std::move(decompress_once), compressed_data_copy,
1.f, data_size_copy);
loop2.Run();
}
TEST_F(Etc1ThumbnailHelperTest, WriteReadAndDeleteThumbnail) {
int tab_id = 0;
// Create a bitmap
SkBitmap image;
ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension));
SkCanvas canvas(image);
canvas.drawPaint(SetupPaint());
image.setImmutable();
sk_sp<SkPixelRef> compressed_data_copy;
gfx::Size data_size_copy;
// Compress the bitmap
base::RunLoop loop1;
base::OnceCallback<void(sk_sp<SkPixelRef>, const gfx::Size&)> compress_once =
base::BindOnce(
[](sk_sp<SkPixelRef>* compressed_data_copy, gfx::Size* data_size_copy,
sk_sp<SkPixelRef> compressed_data, const gfx::Size& data_size) {
EXPECT_GT(compressed_data->width(), 0);
EXPECT_GT(compressed_data->height(), 0);
EXPECT_GT(data_size.width(), 0);
EXPECT_GT(data_size.height(), 0);
gfx::Size buffer_size =
gfx::Size(compressed_data->width(), compressed_data->height());
EXPECT_EQ(data_size, buffer_size);
SkBitmap raw_data;
raw_data.allocPixels(SkImageInfo::MakeN32(buffer_size.width(),
buffer_size.height(),
kOpaque_SkAlphaType));
bool success = etc1_decode_image(
reinterpret_cast<unsigned char*>(compressed_data->pixels()),
reinterpret_cast<unsigned char*>(raw_data.getPixels()),
buffer_size.width(), buffer_size.height(),
raw_data.bytesPerPixel(), raw_data.rowBytes());
EXPECT_TRUE(success);
*compressed_data_copy = compressed_data;
*data_size_copy = data_size;
},
&compressed_data_copy, &data_size_copy)
.Then(loop1.QuitClosure());
GetInterface().Compress(image, true, std::move(compress_once));
loop1.Run();
// Write the image
base::RunLoop loop2;
GetInterface().Write(tab_id, compressed_data_copy, 1.f, data_size_copy,
loop2.QuitClosure());
loop2.Run();
base::FilePath file_path_post_write = GetFile(tab_id);
EXPECT_TRUE(base::PathExists(file_path_post_write));
sk_sp<SkPixelRef> read_compressed_data;
gfx::Size read_data_size;
// Read the image
base::RunLoop loop3;
base::OnceCallback<void(sk_sp<SkPixelRef>, float, const gfx::Size&)>
read_once =
base::BindOnce(
[](SkBitmap* image, sk_sp<SkPixelRef>* read_compressed_data,
gfx::Size* read_data_size, sk_sp<SkPixelRef> compressed_data,
float scale, const gfx::Size& data_size) {
gfx::Size buffer_size = gfx::Size(kWidth, kHeight);
EXPECT_GT(compressed_data->width(), 0);
EXPECT_GT(compressed_data->height(), 0);
EXPECT_GT(data_size.width(), 0);
EXPECT_GT(data_size.height(), 0);
SkBitmap raw_data;
raw_data.allocPixels(SkImageInfo::MakeN32(buffer_size.width(),
buffer_size.height(),
kOpaque_SkAlphaType));
bool success = etc1_decode_image(
reinterpret_cast<unsigned char*>(compressed_data->pixels()),
reinterpret_cast<unsigned char*>(image->getPixels()),
buffer_size.width(), buffer_size.height(),
raw_data.bytesPerPixel(), raw_data.rowBytes());
EXPECT_TRUE(success);
EXPECT_EQ(scale, 1.f);
*read_compressed_data = compressed_data;
*read_data_size = data_size;
},
&image, &read_compressed_data, &read_data_size)
.Then(loop3.QuitClosure());
GetInterface().Read(
tab_id, base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
std::move(read_once)));
loop3.Run();
EXPECT_EQ(compressed_data_copy->width(), read_compressed_data->width());
EXPECT_EQ(compressed_data_copy->height(), read_compressed_data->height());
EXPECT_EQ(data_size_copy.width(), read_data_size.width());
EXPECT_EQ(data_size_copy.height(), read_data_size.height());
EXPECT_EQ(
0, memcmp(compressed_data_copy->pixels(), read_compressed_data->pixels(),
compressed_data_copy->rowBytes()));
base::FilePath file_path_post_read = GetFile(tab_id);
EXPECT_TRUE(base::PathExists(file_path_post_read));
// Delete the image
GetInterface().Delete(tab_id);
task_environment_.RunUntilIdle();
// Check deletion
base::FilePath post_delete_file_path = GetFile(tab_id);
EXPECT_FALSE(base::PathExists(post_delete_file_path));
}
} // namespace thumbnail