chromium/chrome/browser/thumbnail/cc/jpeg_thumbnail_helper.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/thumbnail/cc/jpeg_thumbnail_helper.h"

#include <algorithm>

#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "skia/ext/image_operations.h"
#include "ui/gfx/codec/jpeg_codec.h"

namespace thumbnail {
namespace {

SkBitmap ResizeBitmap(const SkBitmap& bitmap) {
  constexpr int kScale = 2;
  int width = bitmap.width() / kScale;
  int height = bitmap.height() / kScale;

  SkIRect dest_subset = {0, 0, width, height};

  SkBitmap output = skia::ImageOperations::Resize(
      bitmap, skia::ImageOperations::RESIZE_BETTER, width, height, dest_subset);
  output.setImmutable();
  return output;
}

void CompressTask(
    const SkBitmap& bitmap,
    base::OnceCallback<void(std::vector<uint8_t>)> post_processing_task) {
  constexpr int kCompressionQuality = 97;
  std::vector<uint8_t> data;
  const bool result =
      gfx::JPEGCodec::Encode(ResizeBitmap(bitmap), kCompressionQuality, &data);
  DCHECK(result);

  std::move(post_processing_task).Run(std::move(data));
}

void WriteTask(base::FilePath file_path,
               std::vector<uint8_t> compressed_data,
               base::OnceCallback<void(bool)> post_write_task) {
  DCHECK(!compressed_data.empty());

  if (!base::WriteFile(file_path, compressed_data)) {
    base::DeleteFile(file_path);
    std::move(post_write_task).Run(false);
    return;
  }

  std::move(post_write_task).Run(true);
}

void ReadTask(base::FilePath file_path,
              base::OnceCallback<void(std::optional<std::vector<uint8_t>>)>
                  post_read_task) {
  std::optional<std::vector<uint8_t>> read_data =
      base::ReadFileToBytes(file_path);

  if (!read_data.has_value()) {
    base::DeleteFile(file_path);
  }

  std::move(post_read_task).Run(std::move(read_data));
}

void DeleteTask(base::FilePath file_path) {
  if (base::PathExists(file_path)) {
    base::DeleteFile(file_path);
  }
}

}  // anonymous namespace

JpegThumbnailHelper::JpegThumbnailHelper(
    const base::FilePath& base_path,
    scoped_refptr<base::SequencedTaskRunner> file_task_runner)
    : base_path_(base_path),
      default_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      file_task_runner_(file_task_runner) {}

JpegThumbnailHelper::~JpegThumbnailHelper() {
  DCHECK(default_task_runner_->RunsTasksInCurrentSequence());
}

void JpegThumbnailHelper::Compress(
    const SkBitmap& bitmap,
    base::OnceCallback<void(std::vector<uint8_t>)> post_processing_task) {
  DCHECK(default_task_runner_->RunsTasksInCurrentSequence());
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&CompressTask, bitmap,
                     base::BindPostTask(default_task_runner_,
                                        std::move(post_processing_task))));
}

void JpegThumbnailHelper::Write(
    TabId tab_id,
    std::vector<uint8_t> compressed_data,
    base::OnceCallback<void(bool)> post_write_task) {
  DCHECK(default_task_runner_->RunsTasksInCurrentSequence());
  base::FilePath file_path = GetJpegFilePath(tab_id);
  file_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&WriteTask, file_path, compressed_data,
                     base::BindPostTask(default_task_runner_,
                                        std::move(post_write_task))));
}

void JpegThumbnailHelper::Read(
    TabId tab_id,
    base::OnceCallback<void(std::optional<std::vector<uint8_t>>)>
        post_read_task) {
  DCHECK(default_task_runner_->RunsTasksInCurrentSequence());
  base::FilePath file_path = GetJpegFilePath(tab_id);
  file_task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&ReadTask, file_path,
                                base::BindPostTask(default_task_runner_,
                                                   std::move(post_read_task))));
}

void JpegThumbnailHelper::Delete(TabId tab_id) {
  DCHECK(default_task_runner_->RunsTasksInCurrentSequence());
  base::FilePath file_path = GetJpegFilePath(tab_id);
  file_task_runner_->PostTask(FROM_HERE,
                              base::BindOnce(&DeleteTask, file_path));
}

base::FilePath JpegThumbnailHelper::GetJpegFilePath(TabId tab_id) {
  base::FilePath file_path = base_path_.Append(base::NumberToString(tab_id));
  return file_path.AddExtension(".jpeg");
}

}  // namespace thumbnail