1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
ash / public / cpp / image_util.cc [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/public/cpp/image_util.h"
#include <memory>
#include <string>
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/ranges/algorithm.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "ipc/ipc_channel.h"
#include "services/data_decoder/public/cpp/decode_image.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
namespace ash {
namespace image_util {
namespace {
const int64_t kMaxImageSizeInBytes =
static_cast<int64_t>(IPC::Channel::kMaximumMessageSize);
std::string ReadFileToString(const base::FilePath& path) {
std::string result;
if (!base::ReadFileToString(path, &result)) {
LOG(WARNING) << "Failed reading file";
result.clear();
}
return result;
}
void ToImageSkia(DecodeImageCallback callback, const SkBitmap& bitmap) {
if (bitmap.empty()) {
LOG(WARNING) << "Failed to decode image";
std::move(callback).Run(gfx::ImageSkia());
return;
}
gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
std::move(callback).Run(image);
}
void ToFrames(DecodeAnimationCallback callback,
std::vector<data_decoder::mojom::AnimationFramePtr> raw_frames) {
std::vector<AnimationFrame> frames(raw_frames.size());
base::ranges::transform(
raw_frames, frames.begin(),
[](const data_decoder::mojom::AnimationFramePtr& frame_ptr) {
return AnimationFrame{
gfx::ImageSkia::CreateFrom1xBitmap(frame_ptr->bitmap),
frame_ptr->duration};
});
std::move(callback).Run(std::move(frames));
}
void ScheduleFileRead(
const base::FilePath& file_path,
scoped_refptr<base::SequencedTaskRunner> file_task_runner,
base::OnceCallback<void(const std::string&)> completion_cb) {
if (!file_task_runner) {
file_task_runner = base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
}
file_task_runner->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&ReadFileToString, file_path),
std::move(completion_cb));
}
// EmptyImageSkiaSource --------------------------------------------------------
// An `gfx::ImageSkiaSource` which draws nothing to its `canvas`.
class EmptyImageSkiaSource : public gfx::CanvasImageSource {
public:
explicit EmptyImageSkiaSource(const gfx::Size& size)
: gfx::CanvasImageSource(size) {}
EmptyImageSkiaSource(const EmptyImageSkiaSource&) = delete;
EmptyImageSkiaSource& operator=(const EmptyImageSkiaSource&) = delete;
~EmptyImageSkiaSource() override = default;
private:
// gfx::CanvasImageSource:
void Draw(gfx::Canvas* canvas) override {} // Draw nothing.
};
} // namespace
// Utilities -------------------------------------------------------------------
gfx::ImageSkia CreateEmptyImage(const gfx::Size& size) {
return gfx::ImageSkia(std::make_unique<EmptyImageSkiaSource>(size), size);
}
gfx::ImageSkia ResizeAndCropImage(const gfx::ImageSkia& image_skia,
const gfx::Size& new_size) {
// Calculate the scale factors necessary to make each axis match its
// respective part of `new_size`.
const float scale_x =
new_size.width() / static_cast<float>(image_skia.width());
const float scale_y =
new_size.height() / static_cast<float>(image_skia.height());
// Whichever scale factor is larger is what we want to use, so that we are
// cropping excess instead of leaving empty space.
const float scale = std::max(scale_x, scale_y);
// Scale the image to the size that this scale factor indicates.
auto resized_image_skia = gfx::ImageSkiaOperations::CreateResizedImage(
image_skia, skia::ImageOperations::ResizeMethod::RESIZE_BEST,
gfx::ScaleToCeiledSize(image_skia.size(), scale));
// Crop any excess outside the bounds.
gfx::Rect cropped_bounds(resized_image_skia.size());
cropped_bounds.ClampToCenteredSize(new_size);
return gfx::ImageSkiaOperations::ExtractSubset(resized_image_skia,
cropped_bounds);
}
void DecodeImageFile(
DecodeImageCallback callback,
const base::FilePath& file_path,
data_decoder::mojom::ImageCodec codec,
scoped_refptr<base::SequencedTaskRunner> file_task_runner) {
ScheduleFileRead(
file_path, std::move(file_task_runner),
base::BindOnce(&DecodeImageData, std::move(callback), codec));
}
void DecodeAnimationFile(
DecodeAnimationCallback callback,
const base::FilePath& file_path,
scoped_refptr<base::SequencedTaskRunner> file_task_runner) {
ScheduleFileRead(file_path, std::move(file_task_runner),
base::BindOnce(&DecodeAnimationData, std::move(callback)));
}
void DecodeImageData(DecodeImageCallback callback,
data_decoder::mojom::ImageCodec codec,
const std::string& data) {
if (data.empty()) {
std::move(callback).Run(gfx::ImageSkia());
return;
}
data_decoder::DecodeImageIsolated(
base::as_byte_span(data), codec,
/*shrink_to_fit=*/true, kMaxImageSizeInBytes,
/*desired_image_frame_size=*/gfx::Size(),
base::BindOnce(&ToImageSkia, std::move(callback)));
}
void DecodeAnimationData(DecodeAnimationCallback callback,
const std::string& data) {
if (data.empty()) {
std::move(callback).Run(std::vector<AnimationFrame>());
return;
}
// `shrink_to_fit` is true here so that animations larger than
// `kMaxImageSizeInBytes` will have their resolution downscaled instead of
// simply failing to decode.
data_decoder::DecodeAnimationIsolated(
base::as_byte_span(data), /*shrink_to_fit=*/true, kMaxImageSizeInBytes,
base::BindOnce(&ToFrames, std::move(callback)));
}
} // namespace image_util
} // namespace ash