| // Copyright 2018 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/android/explore_sites/image_helper.h" |
| |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "chrome/browser/android/explore_sites/explore_sites_types.h" |
| #include "content/public/common/service_manager_connection.h" |
| #include "services/data_decoder/public/cpp/decode_image.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "third_party/skia/include/core/SkImageInfo.h" |
| #include "third_party/skia/include/core/SkPixmap.h" |
| #include "third_party/skia/include/core/SkRect.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace explore_sites { |
| namespace { |
| // Ratio of icon size to the amount of padding between the icons. |
| const int kIconPaddingScale = 8; |
| } // namespace |
| |
| // Class Job is used to manage multiple calls to the ImageHelper. Each request |
| // to the ImageHelper is handled by a single Job, which is then destroyed after |
| // it is finished. |
| class ImageHelper::Job { |
| public: |
| // WARNING: When ImageJobFinishedCallback is called, |this| may be deleted. |
| // So nothing can be called after this callback. |
| Job(ImageJobType job_type, |
| ImageJobFinishedCallback job_finished_callback, |
| BitmapCallback bitmap_callback, |
| EncodedImageList images, |
| int pixel_size, |
| std::unique_ptr<service_manager::Connector> connector); |
| ~Job(); |
| |
| // Start begins the work that a Job performs (decoding and composition). |
| void Start(); |
| |
| void DecodeImageBytes(std::unique_ptr<EncodedImageBytes> image_bytes); |
| void OnDecodeSiteImageDone(const SkBitmap& decoded_image); |
| void OnDecodeCategoryImageDone(const SkBitmap& decoded_image); |
| std::unique_ptr<SkBitmap> CombineImages(); |
| |
| private: |
| // Used to inject connector in tests. |
| void SetupConnector(); |
| |
| const ImageJobType job_type_; |
| ImageJobFinishedCallback job_finished_callback_; |
| BitmapCallback bitmap_callback_; |
| |
| EncodedImageList images_; |
| int num_icons_, pixel_size_; |
| std::vector<SkBitmap> bitmaps_; |
| |
| std::unique_ptr<service_manager::Connector> connector_; |
| |
| base::WeakPtrFactory<Job> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Job); |
| }; |
| |
| ImageHelper::Job::Job(ImageJobType job_type, |
| ImageJobFinishedCallback job_finished_callback, |
| BitmapCallback bitmap_callback, |
| EncodedImageList images, |
| int pixel_size, |
| std::unique_ptr<service_manager::Connector> connector) |
| : job_type_(job_type), |
| job_finished_callback_(std::move(job_finished_callback)), |
| bitmap_callback_(std::move(bitmap_callback)), |
| images_(std::move(images)), |
| pixel_size_(pixel_size), |
| connector_(std::move(connector)), |
| weak_ptr_factory_(this) { |
| num_icons_ = (images_.size() < kFaviconsPerCategoryImage) |
| ? images_.size() |
| : kFaviconsPerCategoryImage; |
| } |
| |
| ImageHelper::Job::~Job() {} |
| |
| void ImageHelper::Job::Start() { |
| for (int i = 0; i < num_icons_; i++) { |
| // TODO(freedjm): preserve order of images. |
| DVLOG(1) << "Decoding image " << i + 1 << " of " << images_.size(); |
| DecodeImageBytes(std::move(images_[i])); |
| } |
| } |
| |
| void ImageHelper::Job::SetupConnector() { |
| service_manager::mojom::ConnectorRequest connector_request; |
| connector_ = service_manager::Connector::Create(&connector_request); |
| content::ServiceManagerConnection::GetForProcess() |
| ->GetConnector() |
| ->BindConnectorRequest(std::move(connector_request)); |
| } |
| |
| void ImageHelper::Job::DecodeImageBytes( |
| std::unique_ptr<EncodedImageBytes> image_bytes) { |
| if (!connector_) { |
| SetupConnector(); |
| } |
| |
| data_decoder::mojom::ImageDecoder::DecodeImageCallback callback; |
| if (job_type_ == ImageJobType::kSiteIcon) { |
| callback = base::BindOnce(&ImageHelper::Job::OnDecodeSiteImageDone, |
| weak_ptr_factory_.GetWeakPtr()); |
| } else { |
| callback = base::BindOnce(&ImageHelper::Job::OnDecodeCategoryImageDone, |
| weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| data_decoder::DecodeImage(connector_.get(), *image_bytes, |
| data_decoder::mojom::ImageCodec::DEFAULT, false, |
| data_decoder::kDefaultMaxSizeInBytes, gfx::Size(), |
| std::move(callback)); |
| } |
| |
| void RecordImageDecodedUMA(bool decoded) { |
| UMA_HISTOGRAM_BOOLEAN("ExploreSites.ImageDecoded", decoded); |
| } |
| |
| void ImageHelper::Job::OnDecodeSiteImageDone(const SkBitmap& decoded_image) { |
| bool decode_success = !decoded_image.isNull(); |
| DVLOG(1) << "Decoded site image, result " |
| << (decode_success ? "non-null" : "null"); |
| RecordImageDecodedUMA(decode_success); |
| |
| if (!decode_success) { |
| std::move(bitmap_callback_).Run(nullptr); |
| } else { |
| std::move(bitmap_callback_).Run(std::make_unique<SkBitmap>(decoded_image)); |
| } |
| std::move(job_finished_callback_).Run(); |
| } |
| |
| void ImageHelper::Job::OnDecodeCategoryImageDone( |
| const SkBitmap& decoded_image) { |
| bool decode_success = !decoded_image.isNull(); |
| DVLOG(1) << "Decoded image for category, result " |
| << (decode_success ? "non-null" : "null"); |
| RecordImageDecodedUMA(decode_success); |
| |
| if (!decode_success) { |
| num_icons_--; |
| } else { |
| bitmaps_.push_back(decoded_image); |
| } |
| |
| if ((int)bitmaps_.size() == num_icons_) { // On last image for category. |
| std::unique_ptr<SkBitmap> category_bitmap = CombineImages(); |
| std::move(bitmap_callback_).Run(std::move(category_bitmap)); |
| std::move(job_finished_callback_).Run(); |
| } |
| } |
| |
| SkBitmap ScaleBitmap(int icon_size, SkBitmap* bitmap) { |
| DCHECK(bitmap); |
| SkBitmap temp_bitmap; |
| SkImageInfo scaledIconInfo = bitmap->info().makeWH(icon_size, icon_size); |
| temp_bitmap.setInfo(scaledIconInfo); |
| temp_bitmap.allocPixels(); |
| bool did_scale = |
| bitmap->pixmap().scalePixels(temp_bitmap.pixmap(), kHigh_SkFilterQuality); |
| if (!did_scale) { |
| DLOG(ERROR) << "Unable to scale icon for category image."; |
| return SkBitmap(); |
| } |
| return temp_bitmap; |
| } |
| |
| std::unique_ptr<SkBitmap> ImageHelper::Job::CombineImages() { |
| DVLOG(1) << "num icons: " << num_icons_; |
| if (num_icons_ == 0) { |
| return nullptr; |
| } |
| |
| DVLOG(1) << "pixel_size_: " << pixel_size_; |
| int icon_padding_pixel_size = pixel_size_ / kIconPaddingScale; |
| |
| // Offset to write icons out of frame due to padding. |
| int icon_write_offset = icon_padding_pixel_size / 2; |
| |
| SkBitmap composite_bitmap; |
| SkImageInfo image_info = bitmaps_[0].info().makeWH(pixel_size_, pixel_size_); |
| composite_bitmap.setInfo(image_info); |
| composite_bitmap.allocPixels(); |
| composite_bitmap.eraseColor(gfx::kGoogleGrey100); // Set background to grey. |
| |
| int icon_size = pixel_size_ / 2; |
| |
| // draw icons in correct areas |
| switch (num_icons_) { |
| case 1: { |
| // Centered. |
| SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[0]); |
| if (scaledBitmap.empty()) { |
| return nullptr; |
| } |
| composite_bitmap.writePixels( |
| scaledBitmap.pixmap(), |
| ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset, |
| ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset); |
| break; |
| } |
| case 2: { |
| // Side by side. |
| for (int i = 0; i < 2; i++) { |
| SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[i]); |
| if (scaledBitmap.empty()) { |
| return nullptr; |
| } |
| composite_bitmap.writePixels( |
| scaledBitmap.pixmap(), |
| (i * (icon_size + icon_padding_pixel_size)) - icon_write_offset, |
| ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset); |
| } |
| break; |
| } |
| case 3: { |
| // Two on top, one on bottom. |
| for (int i = 0; i < 3; i++) { |
| SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[i]); |
| if (scaledBitmap.empty()) { |
| return nullptr; |
| } |
| switch (i) { |
| case 0: |
| composite_bitmap.writePixels( |
| scaledBitmap.pixmap(), -icon_write_offset, -icon_write_offset); |
| break; |
| case 1: |
| composite_bitmap.writePixels( |
| scaledBitmap.pixmap(), |
| (icon_size + icon_padding_pixel_size) - icon_write_offset, |
| -icon_write_offset); |
| break; |
| default: |
| composite_bitmap.writePixels( |
| scaledBitmap.pixmap(), |
| ((icon_size + icon_padding_pixel_size) / 2) - icon_write_offset, |
| (icon_size + icon_padding_pixel_size) - icon_write_offset); |
| break; |
| } |
| } |
| break; |
| } |
| case 4: { |
| // One in each corner. |
| for (int i = 0; i < 2; i++) { |
| for (int j = 0; j < 2; j++) { |
| int index = i + 2 * j; |
| SkBitmap scaledBitmap = ScaleBitmap(icon_size, &bitmaps_[index]); |
| if (scaledBitmap.empty()) { |
| return nullptr; |
| } |
| composite_bitmap.writePixels( |
| scaledBitmap.pixmap(), |
| (j * (icon_size + icon_padding_pixel_size)) - icon_write_offset, |
| (i * (icon_size + icon_padding_pixel_size)) - icon_write_offset); |
| } |
| } |
| break; |
| } |
| default: |
| DLOG(ERROR) << "Invalid number of icons to combine: " << bitmaps_.size(); |
| return nullptr; |
| } |
| |
| return std::make_unique<SkBitmap>(composite_bitmap); |
| } |
| |
| ImageHelper::ImageHelper() : last_used_job_id_(0), weak_factory_(this) {} |
| |
| ImageHelper::~ImageHelper() {} |
| |
| void ImageHelper::NewJob( |
| ImageJobType job_type, |
| ImageJobFinishedCallback job_finished_callback, |
| BitmapCallback bitmap_callback, |
| EncodedImageList images, |
| int pixel_size, |
| std::unique_ptr<service_manager::Connector> connector) { |
| auto job = std::make_unique<Job>( |
| job_type, std::move(job_finished_callback), std::move(bitmap_callback), |
| std::move(images), pixel_size, std::move(connector)); |
| id_to_job_[last_used_job_id_] = std::move(job); |
| id_to_job_[last_used_job_id_]->Start(); |
| } |
| |
| void ImageHelper::OnJobFinished(int job_id) { |
| DVLOG(1) << "Erasing job " << job_id; |
| id_to_job_.erase(job_id); |
| } |
| |
| void ImageHelper::ComposeSiteImage( |
| BitmapCallback callback, |
| EncodedImageList images, |
| std::unique_ptr<service_manager::Connector> connector) { |
| DVLOG(1) << "Requested decoding for site image"; |
| if (images.size() == 0) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| NewJob(ImageJobType::kSiteIcon, |
| base::BindOnce(&ImageHelper::OnJobFinished, weak_factory_.GetWeakPtr(), |
| ++last_used_job_id_), |
| std::move(callback), std::move(images), -1, std::move(connector)); |
| } |
| |
| void ImageHelper::ComposeCategoryImage( |
| BitmapCallback callback, |
| int pixel_size, |
| EncodedImageList images, |
| std::unique_ptr<service_manager::Connector> connector) { |
| DVLOG(1) << "Requested decoding " << images.size() |
| << " images for category image"; |
| |
| if (images.size() == 0) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| NewJob(ImageJobType::kCategoryImage, |
| base::BindOnce(&ImageHelper::OnJobFinished, weak_factory_.GetWeakPtr(), |
| ++last_used_job_id_), |
| std::move(callback), std::move(images), pixel_size, |
| std::move(connector)); |
| } |
| } // namespace explore_sites |