blob: 9297ca9c7e32bcf091e7b4f11d70df28931bef81 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/thumbnails/thumbnail_image.h"
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/skia_util.h"
void ThumbnailImage::Observer::OnThumbnailImageAvailable(
gfx::ImageSkia thumbnail_image) {}
void ThumbnailImage::Observer::OnCompressedThumbnailDataAvailable(
CompressedThumbnailData thumbnail_data) {}
base::Optional<gfx::Size> ThumbnailImage::Observer::GetThumbnailSizeHint()
const {
return base::nullopt;
}
ThumbnailImage::Delegate::~Delegate() {
if (thumbnail_)
thumbnail_->delegate_ = nullptr;
}
ThumbnailImage::ThumbnailImage(Delegate* delegate) : delegate_(delegate) {
DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK(delegate_);
DCHECK(!delegate_->thumbnail_);
delegate_->thumbnail_ = this;
}
ThumbnailImage::~ThumbnailImage() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (delegate_)
delegate_->thumbnail_ = nullptr;
}
void ThumbnailImage::AddObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(observer);
if (!observers_.HasObserver(observer)) {
const bool is_first_observer = !observers_.might_have_observers();
observers_.AddObserver(observer);
if (is_first_observer && delegate_)
delegate_->ThumbnailImageBeingObservedChanged(true);
}
}
void ThumbnailImage::RemoveObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(observer);
if (observers_.HasObserver(observer)) {
observers_.RemoveObserver(observer);
if (delegate_ && !observers_.might_have_observers())
delegate_->ThumbnailImageBeingObservedChanged(false);
}
}
bool ThumbnailImage::HasObserver(const Observer* observer) const {
return observers_.HasObserver(observer);
}
void ThumbnailImage::AssignSkBitmap(SkBitmap bitmap) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ThumbnailImage::CompressBitmap, std::move(bitmap)),
base::BindOnce(&ThumbnailImage::AssignJPEGData,
weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now()));
}
void ThumbnailImage::RequestThumbnailImage() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ConvertJPEGDataToImageSkiaAndNotifyObservers();
}
void ThumbnailImage::RequestCompressedThumbnailData() {
if (data_)
NotifyCompressedDataObservers(data_);
}
void ThumbnailImage::AssignJPEGData(base::TimeTicks assign_sk_bitmap_time,
std::vector<uint8_t> data) {
data_ = base::MakeRefCounted<base::RefCountedData<std::vector<uint8_t>>>(
std::move(data));
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"Tab.Preview.TimeToNotifyObserversAfterCaptureReceived",
base::TimeTicks::Now() - assign_sk_bitmap_time,
base::TimeDelta::FromMicroseconds(100),
base::TimeDelta::FromMilliseconds(100), 50);
NotifyCompressedDataObservers(data_);
ConvertJPEGDataToImageSkiaAndNotifyObservers();
}
bool ThumbnailImage::ConvertJPEGDataToImageSkiaAndNotifyObservers() {
if (!data_) {
if (async_operation_finished_callback_)
async_operation_finished_callback_.Run();
return false;
}
return base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ThumbnailImage::UncompressImage, data_),
base::BindOnce(&ThumbnailImage::NotifyUncompressedDataObservers,
weak_ptr_factory_.GetWeakPtr()));
}
void ThumbnailImage::NotifyUncompressedDataObservers(gfx::ImageSkia image) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (async_operation_finished_callback_)
async_operation_finished_callback_.Run();
for (auto& observer : observers_) {
auto size_hint = observer.GetThumbnailSizeHint();
observer.OnThumbnailImageAvailable(
size_hint ? CropPreviewImage(image, *size_hint) : image);
}
}
void ThumbnailImage::NotifyCompressedDataObservers(
CompressedThumbnailData data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers_)
observer.OnCompressedThumbnailDataAvailable(data);
}
// static
std::vector<uint8_t> ThumbnailImage::CompressBitmap(SkBitmap bitmap) {
constexpr int kCompressionQuality = 97;
std::vector<uint8_t> data;
const bool result =
gfx::JPEGCodec::Encode(bitmap, kCompressionQuality, &data);
DCHECK(result);
return data;
}
// static
gfx::ImageSkia ThumbnailImage::UncompressImage(
CompressedThumbnailData compressed) {
gfx::ImageSkia result =
gfx::ImageSkia::CreateFrom1xBitmap(*gfx::JPEGCodec::Decode(
compressed->data.data(), compressed->data.size()));
result.MakeThreadSafe();
return result;
}
// static
gfx::ImageSkia ThumbnailImage::CropPreviewImage(
const gfx::ImageSkia& source_image,
const gfx::Size& minimum_size) {
DCHECK(!source_image.isNull());
DCHECK(!source_image.size().IsEmpty());
DCHECK(!minimum_size.IsEmpty());
const float desired_aspect =
float{minimum_size.width()} / minimum_size.height();
const float source_aspect =
float{source_image.width()} / float{source_image.height()};
if (source_aspect == desired_aspect ||
source_image.width() < minimum_size.width() ||
source_image.height() < minimum_size.height()) {
return source_image;
}
gfx::Rect clip_rect;
if (source_aspect > desired_aspect) {
// Wider than tall, clip horizontally: we center the smaller
// thumbnail in the wider screen.
const int new_width = source_image.height() * desired_aspect;
const int x_offset = (source_image.width() - new_width) / 2;
clip_rect = {x_offset, 0, new_width, source_image.height()};
} else {
// Taller than wide; clip vertically.
const int new_height = source_image.width() / desired_aspect;
clip_rect = {0, 0, source_image.width(), new_height};
}
SkBitmap cropped;
source_image.bitmap()->extractSubset(&cropped, gfx::RectToSkIRect(clip_rect));
return gfx::ImageSkia::CreateFrom1xBitmap(cropped);
}