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