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