1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/android/explore_sites/image_helper.h"
6
7 #include "base/bind.h"
8 #include "base/memory/weak_ptr.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "base/time/time.h"
11 #include "chrome/browser/android/compose_bitmaps_helper.h"
12 #include "chrome/browser/android/explore_sites/explore_sites_types.h"
13 #include "services/data_decoder/public/cpp/decode_image.h"
14 #include "third_party/skia/include/core/SkBitmap.h"
15 #include "third_party/skia/include/core/SkImageInfo.h"
16 #include "third_party/skia/include/core/SkPixmap.h"
17 #include "third_party/skia/include/core/SkRect.h"
18 #include "ui/gfx/color_palette.h"
19 #include "ui/gfx/geometry/size.h"
20
21 namespace explore_sites {
22 // Class Job is used to manage multiple calls to the ImageHelper. Each request
23 // to the ImageHelper is handled by a single Job, which is then destroyed after
24 // it is finished.
25 class ImageHelper::Job {
26 public:
27 // WARNING: When ImageJobFinishedCallback is called, |this| may be deleted.
28 // So nothing can be called after this callback.
29 Job(ImageHelper* image_helper,
30 ImageJobType job_type,
31 ImageJobFinishedCallback job_finished_callback,
32 BitmapCallback bitmap_callback,
33 EncodedImageList images,
34 int pixel_size);
35 ~Job();
36
37 // Start begins the work that a Job performs (decoding and composition).
38 void Start();
39
40 void DecodeImageBytes(std::unique_ptr<EncodedImageBytes> image_bytes);
41 void OnDecodeSiteImageDone(const SkBitmap& decoded_image);
42 void OnDecodeCategoryImageDone(const SkBitmap& decoded_image);
43 std::unique_ptr<SkBitmap> CombineImages();
44
45 private:
46 ImageHelper* const image_helper_;
47 const ImageJobType job_type_;
48 ImageJobFinishedCallback job_finished_callback_;
49 BitmapCallback bitmap_callback_;
50
51 EncodedImageList images_;
52 int num_icons_, pixel_size_;
53 std::vector<SkBitmap> bitmaps_;
54
55 base::WeakPtrFactory<Job> weak_ptr_factory_{this};
56
57 DISALLOW_COPY_AND_ASSIGN(Job);
58 };
59
Job(ImageHelper * image_helper,ImageJobType job_type,ImageJobFinishedCallback job_finished_callback,BitmapCallback bitmap_callback,EncodedImageList images,int pixel_size)60 ImageHelper::Job::Job(ImageHelper* image_helper,
61 ImageJobType job_type,
62 ImageJobFinishedCallback job_finished_callback,
63 BitmapCallback bitmap_callback,
64 EncodedImageList images,
65 int pixel_size)
66 : image_helper_(image_helper),
67 job_type_(job_type),
68 job_finished_callback_(std::move(job_finished_callback)),
69 bitmap_callback_(std::move(bitmap_callback)),
70 images_(std::move(images)),
71 pixel_size_(pixel_size) {
72 num_icons_ = (images_.size() < kFaviconsPerCategoryImage)
73 ? images_.size()
74 : kFaviconsPerCategoryImage;
75 }
76
77 ImageHelper::Job::~Job() = default;
78
Start()79 void ImageHelper::Job::Start() {
80 for (int i = 0; i < num_icons_; i++) {
81 // TODO(freedjm): preserve order of images.
82 DVLOG(1) << "Decoding image " << i + 1 << " of " << images_.size();
83 DecodeImageBytes(std::move(images_[i]));
84 }
85 }
86
DecodeImageBytes(std::unique_ptr<EncodedImageBytes> image_bytes)87 void ImageHelper::Job::DecodeImageBytes(
88 std::unique_ptr<EncodedImageBytes> image_bytes) {
89 data_decoder::mojom::ImageDecoder::DecodeImageCallback callback;
90 if (job_type_ == ImageJobType::kSiteIcon) {
91 callback = base::BindOnce(&ImageHelper::Job::OnDecodeSiteImageDone,
92 weak_ptr_factory_.GetWeakPtr());
93 } else {
94 callback = base::BindOnce(&ImageHelper::Job::OnDecodeCategoryImageDone,
95 weak_ptr_factory_.GetWeakPtr());
96 }
97
98 data_decoder::DecodeImage(&image_helper_->data_decoder_, *image_bytes,
99 data_decoder::mojom::ImageCodec::DEFAULT, false,
100 data_decoder::kDefaultMaxSizeInBytes, gfx::Size(),
101 std::move(callback));
102 }
103
RecordImageDecodedUMA(bool decoded)104 void RecordImageDecodedUMA(bool decoded) {
105 UMA_HISTOGRAM_BOOLEAN("ExploreSites.ImageDecoded", decoded);
106 }
107
OnDecodeSiteImageDone(const SkBitmap & decoded_image)108 void ImageHelper::Job::OnDecodeSiteImageDone(const SkBitmap& decoded_image) {
109 bool decode_success = !decoded_image.isNull();
110 DVLOG(1) << "Decoded site image, result "
111 << (decode_success ? "non-null" : "null");
112 RecordImageDecodedUMA(decode_success);
113
114 if (!decode_success) {
115 std::move(bitmap_callback_).Run(nullptr);
116 } else {
117 std::move(bitmap_callback_).Run(std::make_unique<SkBitmap>(decoded_image));
118 }
119 std::move(job_finished_callback_).Run();
120 }
121
OnDecodeCategoryImageDone(const SkBitmap & decoded_image)122 void ImageHelper::Job::OnDecodeCategoryImageDone(
123 const SkBitmap& decoded_image) {
124 bool decode_success = !decoded_image.isNull();
125 DVLOG(1) << "Decoded image for category, result "
126 << (decode_success ? "non-null" : "null");
127 RecordImageDecodedUMA(decode_success);
128
129 if (!decode_success) {
130 num_icons_--;
131 } else {
132 bitmaps_.push_back(decoded_image);
133 }
134
135 if ((int)bitmaps_.size() == num_icons_) { // On last image for category.
136 std::unique_ptr<SkBitmap> category_bitmap = CombineImages();
137 std::move(bitmap_callback_).Run(std::move(category_bitmap));
138 std::move(job_finished_callback_).Run();
139 }
140 }
141
CombineImages()142 std::unique_ptr<SkBitmap> ImageHelper::Job::CombineImages() {
143 return compose_bitmaps_helper::ComposeBitmaps(bitmaps_, pixel_size_);
144 }
145
ImageHelper()146 ImageHelper::ImageHelper() : last_used_job_id_(0) {}
147
~ImageHelper()148 ImageHelper::~ImageHelper() {}
149
NewJob(ImageJobType job_type,ImageJobFinishedCallback job_finished_callback,BitmapCallback bitmap_callback,EncodedImageList images,int pixel_size)150 void ImageHelper::NewJob(ImageJobType job_type,
151 ImageJobFinishedCallback job_finished_callback,
152 BitmapCallback bitmap_callback,
153 EncodedImageList images,
154 int pixel_size) {
155 auto job = std::make_unique<Job>(
156 this, job_type, std::move(job_finished_callback),
157 std::move(bitmap_callback), std::move(images), pixel_size);
158 id_to_job_[last_used_job_id_] = std::move(job);
159 id_to_job_[last_used_job_id_]->Start();
160 }
161
OnJobFinished(int job_id)162 void ImageHelper::OnJobFinished(int job_id) {
163 DVLOG(1) << "Erasing job " << job_id;
164 id_to_job_.erase(job_id);
165 }
166
ComposeSiteImage(BitmapCallback callback,EncodedImageList images)167 void ImageHelper::ComposeSiteImage(BitmapCallback callback,
168 EncodedImageList images) {
169 DVLOG(1) << "Requested decoding for site image";
170 if (images.size() == 0) {
171 std::move(callback).Run(nullptr);
172 return;
173 }
174
175 NewJob(ImageJobType::kSiteIcon,
176 base::BindOnce(&ImageHelper::OnJobFinished, weak_factory_.GetWeakPtr(),
177 ++last_used_job_id_),
178 std::move(callback), std::move(images), -1);
179 }
180
ComposeCategoryImage(BitmapCallback callback,int pixel_size,EncodedImageList images)181 void ImageHelper::ComposeCategoryImage(BitmapCallback callback,
182 int pixel_size,
183 EncodedImageList images) {
184 DVLOG(1) << "Requested decoding " << images.size()
185 << " images for category image";
186
187 if (images.size() == 0) {
188 std::move(callback).Run(nullptr);
189 return;
190 }
191
192 NewJob(ImageJobType::kCategoryImage,
193 base::BindOnce(&ImageHelper::OnJobFinished, weak_factory_.GetWeakPtr(),
194 ++last_used_job_id_),
195 std::move(callback), std::move(images), pixel_size);
196 }
197
198 } // namespace explore_sites
199