1 // Copyright 2014 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/chromeos/login/users/avatar/user_image_loader.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/callback_helpers.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/sequenced_task_runner.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/task_runner_util.h"
18 #include "base/threading/thread_task_runner_handle.h"
19 #include "chrome/browser/chromeos/login/helper.h"
20 #include "components/user_manager/user_image/user_image.h"
21 #include "skia/ext/image_operations.h"
22 #include "third_party/skia/include/core/SkBitmap.h"
23 #include "ui/gfx/codec/png_codec.h"
24 #include "ui/gfx/skbitmap_operations.h"
25 
26 namespace chromeos {
27 namespace user_image_loader {
28 namespace {
29 
30 // Contains attributes we need to know about each image we decode.
31 struct ImageInfo {
ImageInfochromeos::user_image_loader::__anon91eaa7360111::ImageInfo32   ImageInfo(const base::FilePath& file_path,
33             int pixels_per_side,
34             ImageDecoder::ImageCodec image_codec,
35             LoadedCallback loaded_cb)
36       : file_path(file_path),
37         pixels_per_side(pixels_per_side),
38         image_codec(image_codec),
39         loaded_cb(std::move(loaded_cb)) {}
40 
41   ImageInfo(ImageInfo&&) = default;
42   ImageInfo& operator=(ImageInfo&&) = default;
43 
~ImageInfochromeos::user_image_loader::__anon91eaa7360111::ImageInfo44   ~ImageInfo() {}
45 
46   base::FilePath file_path;
47   int pixels_per_side;
48   ImageDecoder::ImageCodec image_codec;
49   LoadedCallback loaded_cb;
50 };
51 
52 // Crops `image` to the square format and downsizes the image to
53 // `target_size` in pixels. On success, returns the bytes representation and
54 // stores the cropped image in `bitmap`, and the format of the bytes
55 // representation in `image_format`. On failure, returns nullptr, and
56 // the contents of `bitmap` and `image_format` are undefined.
CropImage(const SkBitmap & image,int target_size,SkBitmap * bitmap,user_manager::UserImage::ImageFormat * image_format)57 scoped_refptr<base::RefCountedBytes> CropImage(
58     const SkBitmap& image,
59     int target_size,
60     SkBitmap* bitmap,
61     user_manager::UserImage::ImageFormat* image_format) {
62   DCHECK_GT(target_size, 0);
63   DCHECK(image_format);
64 
65   SkBitmap final_image;
66   // Auto crop the image, taking the largest square in the center.
67   int pixels_per_side = std::min(image.width(), image.height());
68   int x = (image.width() - pixels_per_side) / 2;
69   int y = (image.height() - pixels_per_side) / 2;
70   SkBitmap cropped_image = SkBitmapOperations::CreateTiledBitmap(
71       image, x, y, pixels_per_side, pixels_per_side);
72   if (pixels_per_side > target_size) {
73     // Also downsize the image to save space and memory.
74     final_image = skia::ImageOperations::Resize(
75         cropped_image, skia::ImageOperations::RESIZE_LANCZOS3, target_size,
76         target_size);
77   } else {
78     final_image = cropped_image;
79   }
80 
81   // Encode the cropped image to web-compatible bytes representation
82   *image_format = user_manager::UserImage::ChooseImageFormat(final_image);
83   scoped_refptr<base::RefCountedBytes> encoded =
84       user_manager::UserImage::Encode(final_image, *image_format);
85   if (encoded)
86     bitmap->swap(final_image);
87   return encoded;
88 }
89 
90 // Returns the image format for the bytes representation of the user image
91 // from the image codec used for loading the image.
ChooseImageFormatFromCodec(ImageDecoder::ImageCodec image_codec)92 user_manager::UserImage::ImageFormat ChooseImageFormatFromCodec(
93     ImageDecoder::ImageCodec image_codec) {
94   switch (image_codec) {
95     case ImageDecoder::ROBUST_PNG_CODEC:
96       return user_manager::UserImage::FORMAT_PNG;
97     case ImageDecoder::DEFAULT_CODEC:
98       // The default codec can accept many kinds of image formats, hence the
99       // image format of the bytes representation is unknown.
100       return user_manager::UserImage::FORMAT_UNKNOWN;
101   }
102   NOTREACHED();
103   return user_manager::UserImage::FORMAT_UNKNOWN;
104 }
105 
106 // Handles the decoded image returned from ImageDecoder through the
107 // ImageRequest interface.
108 class UserImageRequest : public ImageDecoder::ImageRequest {
109  public:
UserImageRequest(ImageInfo image_info,const std::string & image_data,scoped_refptr<base::SequencedTaskRunner> background_task_runner)110   UserImageRequest(
111       ImageInfo image_info,
112       const std::string& image_data,
113       scoped_refptr<base::SequencedTaskRunner> background_task_runner)
114       : image_info_(std::move(image_info)),
115         // TODO(crbug.com/593251): Remove the data copy here.
116         image_data_(new base::RefCountedBytes(
117             reinterpret_cast<const unsigned char*>(image_data.data()),
118             image_data.size())),
119         background_task_runner_(background_task_runner) {}
~UserImageRequest()120   ~UserImageRequest() override {}
121 
122   // ImageDecoder::ImageRequest implementation.
123   void OnImageDecoded(const SkBitmap& decoded_image) override;
124   void OnDecodeImageFailed() override;
125 
126   // Called after the image is cropped (and downsized) as needed.
127   void OnImageCropped(SkBitmap* bitmap,
128                       user_manager::UserImage::ImageFormat* image_format,
129                       scoped_refptr<base::RefCountedBytes> bytes);
130 
131   // Called after the image is finalized. `image_bytes_regenerated` is true
132   // if `image_bytes` is regenerated from the cropped image.
133   void OnImageFinalized(const SkBitmap& image,
134                         user_manager::UserImage::ImageFormat image_format,
135                         scoped_refptr<base::RefCountedBytes> image_bytes,
136                         bool image_bytes_regenerated);
137 
138  private:
139   ImageInfo image_info_;
140   scoped_refptr<base::RefCountedBytes> image_data_;
141   scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
142 
143   // This should be the last member.
144   base::WeakPtrFactory<UserImageRequest> weak_ptr_factory_{this};
145 };
146 
OnImageDecoded(const SkBitmap & decoded_image)147 void UserImageRequest::OnImageDecoded(const SkBitmap& decoded_image) {
148   int target_size = image_info_.pixels_per_side;
149   if (target_size > 0) {
150     // Cropping an image could be expensive, hence posting to the background
151     // thread.
152     SkBitmap* bitmap = new SkBitmap;
153     auto* image_format = new user_manager::UserImage::ImageFormat(
154         user_manager::UserImage::FORMAT_UNKNOWN);
155     base::PostTaskAndReplyWithResult(
156         background_task_runner_.get(), FROM_HERE,
157         base::BindOnce(&CropImage, decoded_image, target_size, bitmap,
158                        image_format),
159         base::BindOnce(&UserImageRequest::OnImageCropped,
160                        weak_ptr_factory_.GetWeakPtr(), base::Owned(bitmap),
161                        base::Owned(image_format)));
162   } else {
163     const user_manager::UserImage::ImageFormat image_format =
164         ChooseImageFormatFromCodec(image_info_.image_codec);
165     OnImageFinalized(decoded_image, image_format, image_data_,
166                      false /* image_bytes_regenerated */);
167   }
168 }
169 
OnImageCropped(SkBitmap * bitmap,user_manager::UserImage::ImageFormat * image_format,scoped_refptr<base::RefCountedBytes> bytes)170 void UserImageRequest::OnImageCropped(
171     SkBitmap* bitmap,
172     user_manager::UserImage::ImageFormat* image_format,
173     scoped_refptr<base::RefCountedBytes> bytes) {
174   DCHECK_GT(image_info_.pixels_per_side, 0);
175 
176   if (!bytes) {
177     OnDecodeImageFailed();
178     return;
179   }
180   OnImageFinalized(*bitmap, *image_format, bytes,
181                    true /* image_bytes_regenerated */);
182 }
183 
OnImageFinalized(const SkBitmap & image,user_manager::UserImage::ImageFormat image_format,scoped_refptr<base::RefCountedBytes> image_bytes,bool image_bytes_regenerated)184 void UserImageRequest::OnImageFinalized(
185     const SkBitmap& image,
186     user_manager::UserImage::ImageFormat image_format,
187     scoped_refptr<base::RefCountedBytes> image_bytes,
188     bool image_bytes_regenerated) {
189   SkBitmap final_image = image;
190   // Make the SkBitmap immutable as we won't modify it. This is important
191   // because otherwise it gets duplicated during painting, wasting memory.
192   final_image.setImmutable();
193   gfx::ImageSkia final_image_skia =
194       gfx::ImageSkia::CreateFrom1xBitmap(final_image);
195   final_image_skia.MakeThreadSafe();
196   std::unique_ptr<user_manager::UserImage> user_image(
197       new user_manager::UserImage(final_image_skia, image_bytes, image_format));
198   user_image->set_file_path(image_info_.file_path);
199   // The user image is safe if it is decoded using one of the robust image
200   // decoders, or regenerated by Chrome's image encoder.
201   if (image_info_.image_codec == ImageDecoder::ROBUST_PNG_CODEC ||
202       image_bytes_regenerated)
203     user_image->MarkAsSafe();
204   std::move(image_info_.loaded_cb).Run(std::move(user_image));
205   delete this;
206 }
207 
OnDecodeImageFailed()208 void UserImageRequest::OnDecodeImageFailed() {
209   std::move(image_info_.loaded_cb)
210       .Run(base::WrapUnique(new user_manager::UserImage));
211   delete this;
212 }
213 
214 // Starts decoding the image with ImageDecoder for the image `data` if
215 // `data_is_ready` is true.
DecodeImage(ImageInfo image_info,scoped_refptr<base::SequencedTaskRunner> background_task_runner,const std::string * data,bool data_is_ready)216 void DecodeImage(
217     ImageInfo image_info,
218     scoped_refptr<base::SequencedTaskRunner> background_task_runner,
219     const std::string* data,
220     bool data_is_ready) {
221   if (!data_is_ready) {
222     base::ThreadTaskRunnerHandle::Get()->PostTask(
223         FROM_HERE,
224         base::BindOnce(std::move(image_info.loaded_cb),
225                        base::WrapUnique(new user_manager::UserImage)));
226     return;
227   }
228 
229   ImageDecoder::ImageCodec codec = image_info.image_codec;
230   UserImageRequest* image_request = new UserImageRequest(
231       std::move(image_info), *data, background_task_runner);
232   ImageDecoder::StartWithOptions(image_request, *data, codec, false);
233 }
234 
235 }  // namespace
236 
StartWithFilePath(scoped_refptr<base::SequencedTaskRunner> background_task_runner,const base::FilePath & file_path,ImageDecoder::ImageCodec image_codec,int pixels_per_side,LoadedCallback loaded_cb)237 void StartWithFilePath(
238     scoped_refptr<base::SequencedTaskRunner> background_task_runner,
239     const base::FilePath& file_path,
240     ImageDecoder::ImageCodec image_codec,
241     int pixels_per_side,
242     LoadedCallback loaded_cb) {
243   std::string* data = new std::string;
244   base::PostTaskAndReplyWithResult(
245       background_task_runner.get(), FROM_HERE,
246       base::BindOnce(&base::ReadFileToString, file_path, data),
247       base::BindOnce(&DecodeImage,
248                      ImageInfo(file_path, pixels_per_side, image_codec,
249                                std::move(loaded_cb)),
250                      background_task_runner, base::Owned(data)));
251 }
252 
StartWithData(scoped_refptr<base::SequencedTaskRunner> background_task_runner,std::unique_ptr<std::string> data,ImageDecoder::ImageCodec image_codec,int pixels_per_side,LoadedCallback loaded_cb)253 void StartWithData(
254     scoped_refptr<base::SequencedTaskRunner> background_task_runner,
255     std::unique_ptr<std::string> data,
256     ImageDecoder::ImageCodec image_codec,
257     int pixels_per_side,
258     LoadedCallback loaded_cb) {
259   DecodeImage(ImageInfo(base::FilePath(), pixels_per_side, image_codec,
260                         std::move(loaded_cb)),
261               background_task_runner, data.get(), true /* data_is_ready */);
262 }
263 
264 }  // namespace user_image_loader
265 }  // namespace chromeos
266