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 | |
12 | namespace flutter { |
13 | |
14 | ImageDecoder::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 | |
27 | ImageDecoder::~ImageDecoder() = default; |
28 | |
29 | static 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 | |
75 | static 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 | |
99 | sk_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 | |
160 | static 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 | |
216 | void 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 | |
309 | fml::WeakPtr<ImageDecoder> ImageDecoder::GetWeakPtr() const { |
310 | return weak_factory_.GetWeakPtr(); |
311 | } |
312 | |
313 | } // namespace flutter |
314 | |