• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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