44
55#include " flutter/lib/ui/painting/image_decoder.h"
66
7+ #include < algorithm>
8+
79#include " flutter/fml/make_copyable.h"
8- #include " flutter/fml/trace_event.h"
910#include " third_party/skia/include/codec/SkCodec.h"
11+ #include " third_party/skia/src/codec/SkCodecImageGenerator.h"
1012
1113namespace flutter {
14+ namespace {
15+
16+ constexpr double kAspectRatioChangedThreshold = 0.01 ;
17+
18+ } // namespace
1219
1320ImageDecoder::ImageDecoder (
1421 TaskRunners runners,
@@ -25,6 +32,10 @@ ImageDecoder::ImageDecoder(
2532
2633ImageDecoder::~ImageDecoder () = default ;
2734
35+ static double AspectRatio (const SkISize& size) {
36+ return static_cast <double >(size.width ()) / size.height ();
37+ }
38+
2839// Get the updated dimensions of the image. If both dimensions are specified,
2940// use them. If one of them is specified, respect the one that is and use the
3041// aspect ratio to calculate the other. If neither dimension is specified, use
@@ -40,8 +51,7 @@ static SkISize GetResizedDimensions(SkISize current_size,
4051 return SkISize::Make (target_width.value (), target_height.value ());
4152 }
4253
43- const auto aspect_ratio =
44- static_cast <double >(current_size.width ()) / current_size.height ();
54+ const auto aspect_ratio = AspectRatio (current_size);
4555
4656 if (target_width) {
4757 return SkISize::Make (target_width.value (),
@@ -57,34 +67,52 @@ static SkISize GetResizedDimensions(SkISize current_size,
5767}
5868
5969static sk_sp<SkImage> ResizeRasterImage (sk_sp<SkImage> image,
60- std::optional<uint32_t > target_width,
61- std::optional<uint32_t > target_height,
70+ const SkISize& resized_dimensions,
6271 const fml::tracing::TraceFlow& flow) {
6372 FML_DCHECK (!image->isTextureBacked ());
6473
65- const auto resized_dimensions =
66- GetResizedDimensions (image-> dimensions (), target_width, target_height );
74+ TRACE_EVENT0 ( " flutter " , __FUNCTION__);
75+ flow. Step (__FUNCTION__ );
6776
6877 if (resized_dimensions.isEmpty ()) {
6978 FML_LOG (ERROR ) << " Could not resize to empty dimensions." ;
7079 return nullptr ;
7180 }
7281
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;
82+ if (image->dimensions () == resized_dimensions) {
83+ return image->makeRasterImage ();
7784 }
7885
79- TRACE_EVENT0 (" flutter" , __FUNCTION__);
80- flow.Step (__FUNCTION__);
86+ if (resized_dimensions.width () > image->dimensions ().width () ||
87+ resized_dimensions.height () > image->dimensions ().height ()) {
88+ FML_LOG (WARNING ) << " Image is being upsized from "
89+ << image->dimensions ().width () << " x"
90+ << image->dimensions ().height () << " to "
91+ << resized_dimensions.width () << " x"
92+ << resized_dimensions.height ()
93+ << " . Are cache(Height|Width) used correctly?" ;
94+ // TOOD(48885): consider exiting here, there's no good reason to support
95+ // upsampling in a "caching"-optimization context..
96+ }
8197
82- const auto scaled_image_info = image->imageInfo ().makeWH (
83- resized_dimensions.width (), resized_dimensions.height ());
98+ const bool aspect_ratio_changed =
99+ std::abs (AspectRatio (resized_dimensions) -
100+ AspectRatio (image->dimensions ())) > kAspectRatioChangedThreshold ;
101+ if (aspect_ratio_changed) {
102+ // This is probably a bug. If a user passes dimensions that change the
103+ // aspect ratio in a "caching" context that's probably not working as
104+ // intended and rather a signal that the API is hard to use.
105+ FML_LOG (WARNING )
106+ << " Aspect ratio changes. Are cache(Height|Width) used correctly?" ;
107+ }
108+
109+ const auto scaled_image_info =
110+ image->imageInfo ().makeDimensions (resized_dimensions);
84111
85112 SkBitmap scaled_bitmap;
86113 if (!scaled_bitmap.tryAllocPixels (scaled_image_info)) {
87- FML_LOG (ERROR ) << " Could not allocate bitmap when attempting to scale." ;
114+ FML_LOG (ERROR ) << " Failed to allocate memory for bitmap of size "
115+ << scaled_image_info.computeMinByteSize () << " B" ;
88116 return nullptr ;
89117 }
90118
@@ -122,31 +150,98 @@ static sk_sp<SkImage> ImageFromDecompressedData(
122150 return nullptr ;
123151 }
124152
125- return ResizeRasterImage (std::move (image), target_width, target_height, flow);
153+ if (!target_width && !target_height) {
154+ // No resizing requested. Just rasterize the image.
155+ return image->makeRasterImage ();
156+ }
157+
158+ auto resized_dimensions =
159+ GetResizedDimensions (image->dimensions (), target_width, target_height);
160+
161+ return ResizeRasterImage (std::move (image), resized_dimensions, flow);
126162}
127163
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) {
164+ sk_sp<SkImage> ImageFromCompressedData (sk_sp<SkData> data,
165+ std::optional<uint32_t > target_width,
166+ std::optional<uint32_t > target_height,
167+ const fml::tracing::TraceFlow& flow) {
133168 TRACE_EVENT0 (" flutter" , __FUNCTION__);
134169 flow.Step (__FUNCTION__);
135170
136- auto decoded_image = SkImage::MakeFromEncoded (data);
171+ if (!target_width && !target_height) {
172+ // No resizing requested. Just decode & rasterize the image.
173+ return SkImage::MakeFromEncoded (data)->makeRasterImage ();
174+ }
137175
138- if (!decoded_image) {
176+ auto codec = SkCodec::MakeFromData (data);
177+ if (codec == nullptr ) {
139178 return nullptr ;
140179 }
141180
142- // Make sure to resolve all lazy images.
143- decoded_image = decoded_image->makeRasterImage ();
181+ const auto * codec_ptr = codec.get ();
144182
145- if (!decoded_image) {
183+ // Note that we cannot read the dimensions from the codec since they don't
184+ // respect image orientation provided e.g. in EXIF data.
185+ auto image_generator = SkCodecImageGenerator::MakeFromCodec (std::move (codec));
186+ const auto & source_dimensions = image_generator->getInfo ().dimensions ();
187+
188+ auto resized_dimensions =
189+ GetResizedDimensions (source_dimensions, target_width, target_height);
190+
191+ // No resize needed.
192+ if (resized_dimensions == source_dimensions) {
193+ return SkImage::MakeFromEncoded (data)->makeRasterImage ();
194+ }
195+
196+ auto decode_dimensions = codec_ptr->getScaledDimensions (
197+ std::max (static_cast <double >(resized_dimensions.width ()) /
198+ source_dimensions.width (),
199+ static_cast <double >(resized_dimensions.height ()) /
200+ source_dimensions.height ()));
201+
202+ // If the codec supports efficient sub-pixel decoding, decoded at a resolution
203+ // close to the target resolution before resizing.
204+ if (decode_dimensions != codec_ptr->dimensions ()) {
205+ if (source_dimensions != codec_ptr->dimensions ()) {
206+ decode_dimensions =
207+ SkISize::Make (decode_dimensions.height (), decode_dimensions.width ());
208+ }
209+
210+ auto scaled_image_info =
211+ image_generator->getInfo ().makeDimensions (decode_dimensions);
212+
213+ SkBitmap scaled_bitmap;
214+ if (!scaled_bitmap.tryAllocPixels (scaled_image_info)) {
215+ FML_LOG (ERROR ) << " Failed to allocate memory for bitmap of size "
216+ << scaled_image_info.computeMinByteSize () << " B" ;
217+ return nullptr ;
218+ }
219+
220+ const auto & pixmap = scaled_bitmap.pixmap ();
221+ if (image_generator->getPixels (pixmap.info (), pixmap.writable_addr (),
222+ pixmap.rowBytes ())) {
223+ // Marking this as immutable makes the MakeFromBitmap call share
224+ // the pixels instead of copying.
225+ scaled_bitmap.setImmutable ();
226+
227+ auto decoded_image = SkImage::MakeFromBitmap (scaled_bitmap);
228+ FML_DCHECK (decoded_image);
229+ if (!decoded_image) {
230+ FML_LOG (ERROR )
231+ << " Could not create a scaled image from a scaled bitmap." ;
232+ return nullptr ;
233+ }
234+ return ResizeRasterImage (std::move (decoded_image), resized_dimensions,
235+ flow);
236+ }
237+ }
238+
239+ auto image = SkImage::MakeFromEncoded (data);
240+ if (!image) {
146241 return nullptr ;
147242 }
148243
149- return ResizeRasterImage (decoded_image, target_width, target_height , flow);
244+ return ResizeRasterImage (std::move (image), resized_dimensions , flow);
150245}
151246
152247static SkiaGPUObject<SkImage> UploadRasterImage (
0 commit comments