910e62b5创建于 1月15日历史提交
// Copyright 2019 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/ui/thumbnails/thumbnail_image.h"

#include <algorithm>
#include <utility>

#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/skia_conversions.h"

ThumbnailImage::Subscription::Subscription(
    scoped_refptr<ThumbnailImage> thumbnail)
    : thumbnail_(std::move(thumbnail)) {}

ThumbnailImage::Subscription::~Subscription() {
  thumbnail_->HandleSubscriptionDestroyed(this);
}

ThumbnailImage::CaptureReadiness ThumbnailImage::Delegate::GetCaptureReadiness()
    const {
  return CaptureReadiness::kNotReady;
}

ThumbnailImage::Delegate::~Delegate() {
  if (thumbnail_) {
    thumbnail_->delegate_ = nullptr;
  }
}

ThumbnailImage::ThumbnailImage(Delegate* delegate, CompressedThumbnailData data)
    : delegate_(delegate), data_(std::move(data)) {
  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;
  }
}

ThumbnailImage::CaptureReadiness ThumbnailImage::GetCaptureReadiness() const {
  return delegate_ ? delegate_->GetCaptureReadiness()
                   : CaptureReadiness::kNotReady;
}

std::unique_ptr<ThumbnailImage::Subscription> ThumbnailImage::Subscribe() {
  // Use explicit new since Subscription constructor is private.
  auto subscription =
      base::WrapUnique(new Subscription(base::WrapRefCounted(this)));
  subscribers_.insert(subscribers_.end(), subscription.get());

  // Notify |delegate_| if this is the first subscriber.
  if (subscribers_.size() == 1) {
    delegate_->ThumbnailImageBeingObservedChanged(true);
  }

  return subscription;
}

void ThumbnailImage::AssignSkBitmap(SkBitmap bitmap,
                                    std::optional<uint64_t> frame_id) {
  thumbnail_id_ = base::Token::CreateRandom();

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::TaskPriority::USER_VISIBLE,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&ThumbnailImage::CompressBitmap, std::move(bitmap),
                     frame_id),
      base::BindOnce(&ThumbnailImage::AssignJPEGData,
                     weak_ptr_factory_.GetWeakPtr(), thumbnail_id_,
                     base::TimeTicks::Now(), frame_id));
}

void ThumbnailImage::ClearData() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!data_ && thumbnail_id_.is_zero()) {
    return;
  }

  // If there was stored data we should notify observers that it was
  // cleared. Otherwise, a bitmap was assigned but never compressed so
  // the observers still think the thumbnail is blank.
  const bool should_notify = !!data_;

  data_.reset();
  thumbnail_id_ = base::Token();

  // Notify observers of the new, blank thumbnail.
  if (should_notify) {
    NotifyCompressedDataObservers(data_);
    NotifyUncompressedDataObservers(thumbnail_id_, gfx::ImageSkia());
  }
}

void ThumbnailImage::RequestThumbnailImage() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  ConvertJPEGDataToImageSkiaAndNotifyObservers();
}

void ThumbnailImage::RequestCompressedThumbnailData() {
  if (data_) {
    NotifyCompressedDataObservers(data_);
  }
}

size_t ThumbnailImage::GetCompressedDataSizeInBytes() const {
  if (!data_) {
    return 0;
  }
  return data_->data.size();
}

void ThumbnailImage::AssignJPEGData(base::Token thumbnail_id,
                                    base::TimeTicks assign_sk_bitmap_time,
                                    std::optional<uint64_t> frame_id_for_trace,
                                    std::vector<uint8_t> data) {
  // If the image is stale (a new thumbnail was assigned or the
  // thumbnail was cleared after AssignSkBitmap), ignore it.
  if (thumbnail_id != thumbnail_id_) {
    if (async_operation_finished_callback_) {
      async_operation_finished_callback_.Run();
    }
    return;
  }

  data_ = base::MakeRefCounted<base::RefCountedData<std::vector<uint8_t>>>(
      std::move(data));

  // We select a TRACE_EVENT_* macro based on |frame_id|'s presence.
  // Since these are scoped traces, the macro invocation must be in the
  // enclosing scope of these operations. Extract them into a common
  // function.
  auto notify = [&]() {
    NotifyCompressedDataObservers(data_);
    ConvertJPEGDataToImageSkiaAndNotifyObservers();
  };

  if (frame_id_for_trace) {
    TRACE_EVENT_WITH_FLOW0("ui", "Tab.Preview.JPEGReceivedOnUIThreadWithFlow",
                           *frame_id_for_trace, TRACE_EVENT_FLAG_FLOW_IN);
    notify();
  } else {
    TRACE_EVENT0("ui", "Tab.Preview.JPEGReceivedOnUIThread");
    notify();
  }
}

bool ThumbnailImage::ConvertJPEGDataToImageSkiaAndNotifyObservers() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  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(), thumbnail_id_));
}

void ThumbnailImage::NotifyUncompressedDataObservers(base::Token thumbnail_id,
                                                     gfx::ImageSkia image) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (async_operation_finished_callback_) {
    async_operation_finished_callback_.Run();
  }

  // If the image is stale (a new thumbnail was assigned or the
  // thumbnail was cleared after AssignSkBitmap), ignore it.
  if (thumbnail_id != thumbnail_id_) {
    return;
  }

  for (Subscription* subscription : subscribers_) {
    auto size_hint = subscription->size_hint_;
    if (subscription->uncompressed_image_callback_) {
      auto cropped_image = size_hint && !image.isNull()
                               ? CropPreviewImage(image, *size_hint)
                               : image;
      subscription->uncompressed_image_callback_.Run(cropped_image);
    }
  }
}

void ThumbnailImage::NotifyCompressedDataObservers(
    CompressedThumbnailData data) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  for (Subscription* subscription : subscribers_) {
    if (subscription->compressed_image_callback_) {
      subscription->compressed_image_callback_.Run(data);
    }
  }
}

// static
std::vector<uint8_t> ThumbnailImage::CompressBitmap(
    SkBitmap bitmap,
    std::optional<uint64_t> frame_id) {
  constexpr int kCompressionQuality = 97;
  std::optional<std::vector<uint8_t>> data;

  if (frame_id) {
    TRACE_EVENT_WITH_FLOW0(
        "ui", "Tab.Preview.CompressJPEGWithFlow", *frame_id,
        TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
    data = gfx::JPEGCodec::Encode(bitmap, kCompressionQuality);
  } else {
    TRACE_EVENT0("ui", "Tab.Preview.CompressJPEG");
    data = gfx::JPEGCodec::Encode(bitmap, kCompressionQuality);
  }

  return data.value_or(std::vector<uint8_t>());
}

// static
gfx::ImageSkia ThumbnailImage::UncompressImage(
    CompressedThumbnailData compressed) {
  gfx::ImageSkia result;
  SkBitmap bitmap = gfx::JPEGCodec::Decode(compressed->data);
  if (!bitmap.isNull()) {
    result = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
    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 =
      static_cast<float>(minimum_size.width()) / minimum_size.height();
  const float source_aspect =
      static_cast<float>(source_image.width()) / 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);
}

void ThumbnailImage::HandleSubscriptionDestroyed(Subscription* subscription) {
  // The order of |subscribers_| does not matter. We can simply swap
  // |subscription| in |subscribers_| with the last element, then pop it
  // off the back.
  auto it = std::ranges::find(subscribers_, subscription);
  CHECK(it != subscribers_.end());
  std::swap(*it, *(subscribers_.end() - 1));
  subscribers_.pop_back();

  // If that was the last subscriber, tell |delegate_| (if it still exists).
  if (delegate_ && subscribers_.empty()) {
    delegate_->ThumbnailImageBeingObservedChanged(false);
  }
}