| // 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/jpeg_thumbnail_helper.h" |
| |
| #include <cstring> |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/bind.h" |
| #include "base/run_loop.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/task_environment.h" |
| #include "chrome/browser/thumbnail/cc/thumbnail.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.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/SkPaint.h" |
| #include "third_party/skia/include/core/SkScalar.h" |
| #include "third_party/skia/include/effects/SkGradientShader.h" |
| #include "ui/gfx/codec/jpeg_codec.h" |
| |
| namespace thumbnail { |
| namespace { |
| |
| constexpr int kDimension = 16; |
| constexpr int kKiB = 1024; |
| |
| 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; |
| } |
| |
| } // anonymous namespace |
| |
| class JpegThumbnailHelperTest : public ::testing::Test { |
| protected: |
| JpegThumbnailHelperTest() |
| : task_environment_( |
| base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) {} |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| interface_ = std::make_unique<thumbnail::JpegThumbnailHelper>( |
| temp_dir_.GetPath(), |
| base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})); |
| } |
| |
| void TearDown() override {} |
| |
| thumbnail::JpegThumbnailHelper& GetInterface() { return *interface_; } |
| base::FilePath GetFile(int tab_id) { |
| return interface_->GetJpegFilePath(tab_id); |
| } |
| |
| base::test::TaskEnvironment task_environment_; |
| |
| private: |
| std::unique_ptr<thumbnail::JpegThumbnailHelper> interface_; |
| base::ScopedTempDir temp_dir_; |
| }; |
| |
| TEST_F(JpegThumbnailHelperTest, CompressThumbnail) { |
| // Create a bitmap |
| SkBitmap image; |
| ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension)); |
| SkCanvas canvas(image); |
| canvas.drawPaint(SetupPaint()); |
| image.setImmutable(); |
| |
| // Compress the bitmap |
| base::RunLoop loop1; |
| base::OnceCallback<void(std::vector<uint8_t>)> once = |
| base::BindOnce([](std::vector<uint8_t> jpeg_data) { |
| EXPECT_FALSE(jpeg_data.empty()); |
| auto bitmap = |
| gfx::JPEGCodec::Decode(jpeg_data.data(), jpeg_data.size()); |
| EXPECT_TRUE(bitmap); |
| EXPECT_GT(bitmap->width(), 0); |
| EXPECT_GT(bitmap->height(), 0); |
| }).Then(loop1.QuitClosure()); |
| |
| GetInterface().Compress(image, std::move(once)); |
| task_environment_.RunUntilIdle(); |
| loop1.Run(); |
| } |
| |
| TEST_F(JpegThumbnailHelperTest, WriteThumbnail) { |
| int tab_id = 0; |
| |
| // Create a bitmap |
| SkBitmap image; |
| ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension)); |
| SkCanvas canvas(image); |
| canvas.drawPaint(SetupPaint()); |
| image.setImmutable(); |
| |
| constexpr int kCompressionQuality = 97; |
| std::vector<uint8_t> data; |
| gfx::JPEGCodec::Encode(image, kCompressionQuality, &data); |
| |
| // Write the image |
| base::RunLoop loop1; |
| GetInterface().Write(tab_id, data, |
| base::BindOnce( |
| [](base::OnceClosure quit, bool success) { |
| EXPECT_TRUE(success); |
| std::move(quit).Run(); |
| }, |
| loop1.QuitClosure())); |
| task_environment_.RunUntilIdle(); |
| loop1.Run(); |
| |
| base::FilePath file_path = GetFile(tab_id); |
| EXPECT_TRUE(base::PathExists(file_path)); |
| |
| // Compare original data with written data |
| std::optional<std::vector<uint8_t>> read_data = |
| base::ReadFileToBytes(file_path); |
| ASSERT_EQ(data.size(), read_data->size()); |
| EXPECT_EQ(0, memcmp(data.data(), read_data->data(), data.size())); |
| } |
| |
| TEST_F(JpegThumbnailHelperTest, ReadThumbnail) { |
| int tab_id = 0; |
| |
| // Create a bitmap |
| SkBitmap image; |
| ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension)); |
| SkCanvas canvas(image); |
| canvas.drawPaint(SetupPaint()); |
| image.setImmutable(); |
| |
| constexpr int kCompressionQuality = 97; |
| std::vector<uint8_t> data; |
| gfx::JPEGCodec::Encode(image, kCompressionQuality, &data); |
| |
| // Write the image |
| base::FilePath file_path = GetFile(tab_id); |
| base::File file(file_path, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| file.Write(0, reinterpret_cast<const char*>(data.data()), data.size()); |
| |
| // Read the image |
| base::RunLoop loop1; |
| base::OnceCallback<void(std::optional<std::vector<uint8_t>>)> once = |
| base::BindOnce([](std::optional<std::vector<uint8_t>> compressed_data) { |
| EXPECT_TRUE(compressed_data.has_value()); |
| EXPECT_FALSE(compressed_data->empty()); |
| auto bitmap = gfx::JPEGCodec::Decode(compressed_data->data(), |
| compressed_data->size()); |
| EXPECT_TRUE(bitmap); |
| EXPECT_GT(bitmap->width(), 0); |
| EXPECT_GT(bitmap->height(), 0); |
| }).Then(loop1.QuitClosure()); |
| |
| GetInterface().Read(tab_id, std::move(once)); |
| task_environment_.RunUntilIdle(); |
| loop1.Run(); |
| } |
| |
| TEST_F(JpegThumbnailHelperTest, DeleteThumbnail) { |
| int tab_id = 0; |
| |
| // Create a bitmap |
| SkBitmap image; |
| ASSERT_TRUE(image.tryAllocN32Pixels(kDimension * kKiB, kDimension)); |
| SkCanvas canvas(image); |
| canvas.drawPaint(SetupPaint()); |
| image.setImmutable(); |
| |
| constexpr int kCompressionQuality = 97; |
| std::vector<uint8_t> data; |
| gfx::JPEGCodec::Encode(image, kCompressionQuality, &data); |
| |
| // Write the image |
| base::FilePath file_path = GetFile(tab_id); |
| base::File file(file_path, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| file.Write(0, reinterpret_cast<const char*>(data.data()), data.size()); |
| |
| // Delete the image |
| GetInterface().Delete(tab_id); |
| task_environment_.RunUntilIdle(); |
| |
| // Check deletion occurred |
| base::FilePath post_delete_file_path = GetFile(tab_id); |
| EXPECT_FALSE(base::PathExists(post_delete_file_path)); |
| } |
| |
| } // namespace thumbnail |