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