1 // Copyright 2013 The Flutter 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 "flutter/lib/ui/painting/image_decoder.h"
6
7 #include "flutter/fml/make_copyable.h"
8 #include "flutter/fml/trace_event.h"
9 #include "third_party/skia/include/codec/SkCodec.h"
10
11 namespace flutter {
12
ImageDecoder(TaskRunners runners,std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,fml::WeakPtr<IOManager> io_manager)13 ImageDecoder::ImageDecoder(
14 TaskRunners runners,
15 std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner,
16 fml::WeakPtr<IOManager> io_manager)
17 : runners_(std::move(runners)),
18 concurrent_task_runner_(std::move(concurrent_task_runner)),
19 io_manager_(std::move(io_manager)),
20 weak_factory_(this) {
21 FML_DCHECK(runners_.IsValid());
22 FML_DCHECK(runners_.GetUITaskRunner()->RunsTasksOnCurrentThread())
23 << "The image decoder must be created & collected on the UI thread.";
24 }
25
26 ImageDecoder::~ImageDecoder() = default;
27
28 // Get the updated dimensions of the image. If both dimensions are specified,
29 // use them. If one of them is specified, respect the one that is and use the
30 // aspect ratio to calculate the other. If neither dimension is specified, use
31 // intrinsic dimensions of the image.
GetResizedDimensions(SkISize current_size,std::optional<uint32_t> target_width,std::optional<uint32_t> target_height)32 static SkISize GetResizedDimensions(SkISize current_size,
33 std::optional<uint32_t> target_width,
34 std::optional<uint32_t> target_height) {
35 if (current_size.isEmpty()) {
36 return SkISize::MakeEmpty();
37 }
38
39 if (target_width && target_height) {
40 return SkISize::Make(target_width.value(), target_height.value());
41 }
42
43 const auto aspect_ratio =
44 static_cast<double>(current_size.width()) / current_size.height();
45
46 if (target_width) {
47 return SkISize::Make(target_width.value(),
48 target_width.value() / aspect_ratio);
49 }
50
51 if (target_height) {
52 return SkISize::Make(target_height.value() * aspect_ratio,
53 target_height.value());
54 }
55
56 return current_size;
57 }
58
ResizeRasterImage(sk_sp<SkImage> image,std::optional<uint32_t> target_width,std::optional<uint32_t> target_height,const fml::tracing::TraceFlow & flow)59 static sk_sp<SkImage> ResizeRasterImage(sk_sp<SkImage> image,
60 std::optional<uint32_t> target_width,
61 std::optional<uint32_t> target_height,
62 const fml::tracing::TraceFlow& flow) {
63 FML_DCHECK(!image->isTextureBacked());
64
65 const auto resized_dimensions =
66 GetResizedDimensions(image->dimensions(), target_width, target_height);
67
68 if (resized_dimensions.isEmpty()) {
69 FML_LOG(ERROR) << "Could not resize to empty dimensions.";
70 return nullptr;
71 }
72
73 if (resized_dimensions == image->dimensions()) {
74 // The resized dimesions are the same as the intrinsic dimensions of the
75 // image. There is nothing to do.
76 return image;
77 }
78
79 TRACE_EVENT0("flutter", __FUNCTION__);
80 flow.Step(__FUNCTION__);
81
82 const auto scaled_image_info = image->imageInfo().makeWH(
83 resized_dimensions.width(), resized_dimensions.height());
84
85 SkBitmap scaled_bitmap;
86 if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) {
87 FML_LOG(ERROR) << "Could not allocate bitmap when attempting to scale.";
88 return nullptr;
89 }
90
91 if (!image->scalePixels(scaled_bitmap.pixmap(), kLow_SkFilterQuality,
92 SkImage::kDisallow_CachingHint)) {
93 FML_LOG(ERROR) << "Could not scale pixels";
94 return nullptr;
95 }
96
97 // Marking this as immutable makes the MakeFromBitmap call share the pixels
98 // instead of copying.
99 scaled_bitmap.setImmutable();
100
101 auto scaled_image = SkImage::MakeFromBitmap(scaled_bitmap);
102 if (!scaled_image) {
103 FML_LOG(ERROR) << "Could not create a scaled image from a scaled bitmap.";
104 return nullptr;
105 }
106
107 return scaled_image;
108 }
109
ImageFromDecompressedData(sk_sp<SkData> data,ImageDecoder::ImageInfo info,std::optional<uint32_t> target_width,std::optional<uint32_t> target_height,const fml::tracing::TraceFlow & flow)110 static sk_sp<SkImage> ImageFromDecompressedData(
111 sk_sp<SkData> data,
112 ImageDecoder::ImageInfo info,
113 std::optional<uint32_t> target_width,
114 std::optional<uint32_t> target_height,
115 const fml::tracing::TraceFlow& flow) {
116 TRACE_EVENT0("flutter", __FUNCTION__);
117 flow.Step(__FUNCTION__);
118 auto image = SkImage::MakeRasterData(info.sk_info, data, info.row_bytes);
119
120 if (!image) {
121 FML_LOG(ERROR) << "Could not create image from decompressed bytes.";
122 return nullptr;
123 }
124
125 return ResizeRasterImage(std::move(image), target_width, target_height, flow);
126 }
127
ImageFromCompressedData(sk_sp<SkData> data,std::optional<uint32_t> target_width,std::optional<uint32_t> target_height,const fml::tracing::TraceFlow & flow)128 static sk_sp<SkImage> ImageFromCompressedData(
129 sk_sp<SkData> data,
130 std::optional<uint32_t> target_width,
131 std::optional<uint32_t> target_height,
132 const fml::tracing::TraceFlow& flow) {
133 TRACE_EVENT0("flutter", __FUNCTION__);
134 flow.Step(__FUNCTION__);
135
136 auto decoded_image = SkImage::MakeFromEncoded(data);
137
138 if (!decoded_image) {
139 return nullptr;
140 }
141
142 // Make sure to resolve all lazy images.
143 decoded_image = decoded_image->makeRasterImage();
144
145 if (!decoded_image) {
146 return nullptr;
147 }
148
149 return ResizeRasterImage(decoded_image, target_width, target_height, flow);
150 }
151
UploadRasterImage(sk_sp<SkImage> image,fml::WeakPtr<GrContext> context,fml::RefPtr<flutter::SkiaUnrefQueue> queue,const fml::tracing::TraceFlow & flow)152 static SkiaGPUObject<SkImage> UploadRasterImage(
153 sk_sp<SkImage> image,
154 fml::WeakPtr<GrContext> context,
155 fml::RefPtr<flutter::SkiaUnrefQueue> queue,
156 const fml::tracing::TraceFlow& flow) {
157 TRACE_EVENT0("flutter", __FUNCTION__);
158 flow.Step(__FUNCTION__);
159
160 // Should not already be a texture image because that is the entire point of
161 // the this method.
162 FML_DCHECK(!image->isTextureBacked());
163
164 if (!context || !queue) {
165 FML_LOG(ERROR)
166 << "Could not acquire context of release queue for texture upload.";
167 return {};
168 }
169
170 SkPixmap pixmap;
171 if (!image->peekPixels(&pixmap)) {
172 FML_LOG(ERROR) << "Could not peek pixels of image for texture upload.";
173 return {};
174 }
175
176 auto texture_image =
177 SkImage::MakeCrossContextFromPixmap(context.get(), // context
178 pixmap, // pixmap
179 true, // buildMips,
180 true // limitToMaxTextureSize
181 );
182
183 if (!texture_image) {
184 FML_LOG(ERROR) << "Could not make x-context image.";
185 return {};
186 }
187
188 return {texture_image, queue};
189 }
190
Decode(ImageDescriptor descriptor,ImageResult callback)191 void ImageDecoder::Decode(ImageDescriptor descriptor, ImageResult callback) {
192 TRACE_EVENT0("flutter", __FUNCTION__);
193 fml::tracing::TraceFlow flow(__FUNCTION__);
194
195 FML_DCHECK(callback);
196 FML_DCHECK(runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
197
198 // Always service the callback on the UI thread.
199 auto result = [callback, ui_runner = runners_.GetUITaskRunner()](
200 SkiaGPUObject<SkImage> image,
201 fml::tracing::TraceFlow flow) {
202 ui_runner->PostTask(fml::MakeCopyable(
203 [callback, image = std::move(image), flow = std::move(flow)]() mutable {
204 // We are going to terminate the trace flow here. Flows cannot
205 // terminate without a base trace. Add one explicitly.
206 TRACE_EVENT0("flutter", "ImageDecodeCallback");
207 flow.End();
208 callback(std::move(image));
209 }));
210 };
211
212 if (!descriptor.data || descriptor.data->size() == 0) {
213 result({}, std::move(flow));
214 return;
215 }
216
217 concurrent_task_runner_->PostTask(
218 fml::MakeCopyable([descriptor, //
219 io_manager = io_manager_, //
220 io_runner = runners_.GetIOTaskRunner(), //
221 result, //
222 flow = std::move(flow) //
223 ]() mutable {
224 // Step 1: Decompress the image.
225 // On Worker.
226
227 auto decompressed =
228 descriptor.decompressed_image_info
229 ? ImageFromDecompressedData(
230 std::move(descriptor.data), //
231 descriptor.decompressed_image_info.value(), //
232 descriptor.target_width, //
233 descriptor.target_height, //
234 flow //
235 )
236 : ImageFromCompressedData(std::move(descriptor.data), //
237 descriptor.target_width, //
238 descriptor.target_height, //
239 flow);
240
241 if (!decompressed) {
242 FML_LOG(ERROR) << "Could not decompress image.";
243 result({}, std::move(flow));
244 return;
245 }
246
247 // Step 2: Update the image to the GPU.
248 // On IO Thread.
249
250 io_runner->PostTask(fml::MakeCopyable([io_manager, decompressed, result,
251 flow =
252 std::move(flow)]() mutable {
253 if (!io_manager) {
254 FML_LOG(ERROR) << "Could not acquire IO manager.";
255 return result({}, std::move(flow));
256 }
257
258 // If the IO manager does not have a resource context, the caller
259 // might not have set one or a software backend could be in use.
260 // Either way, just return the image as-is.
261 if (!io_manager->GetResourceContext()) {
262 result({std::move(decompressed), io_manager->GetSkiaUnrefQueue()},
263 std::move(flow));
264 return;
265 }
266
267 auto uploaded = UploadRasterImage(
268 std::move(decompressed), io_manager->GetResourceContext(),
269 io_manager->GetSkiaUnrefQueue(), flow);
270
271 if (!uploaded.get()) {
272 FML_LOG(ERROR) << "Could not upload image to the GPU.";
273 result({}, std::move(flow));
274 return;
275 }
276
277 // Finally, all done.
278 result(std::move(uploaded), std::move(flow));
279 }));
280 }));
281 }
282
GetWeakPtr() const283 fml::WeakPtr<ImageDecoder> ImageDecoder::GetWeakPtr() const {
284 return weak_factory_.GetWeakPtr();
285 }
286
287 } // namespace flutter
288