Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
flutter::APNGImageGenerator Class Reference

#include <image_generator_apng.h>

Inheritance diagram for flutter::APNGImageGenerator:
flutter::ImageGenerator

Public Member Functions

 ~APNGImageGenerator ()
 
const SkImageInfo & GetInfo () override
 Returns basic information about the contents of the encoded image. This information can almost always be collected by just interpreting the header of a decoded image.
 
unsigned int GetFrameCount () const override
 Get the number of frames that the encoded image stores. This method is always expected to be called before GetFrameInfo, as the underlying image decoder may interpret frame information that is then used when calling GetFrameInfo.
 
unsigned int GetPlayCount () const override
 The number of times an animated image should play through before playback stops.
 
const ImageGenerator::FrameInfo GetFrameInfo (unsigned int frame_index) override
 Get information about a single frame in the context of a multi-frame image, useful for animation and frame blending. This method should only ever be called after GetFrameCount has been called. This information is nonsensical for single-frame images.
 
SkISize GetScaledDimensions (float desired_scale) override
 Given a scale value, find the closest image size that can be used for efficiently decoding the image. If subpixel image decoding is not supported by the decoder, this method should just return the original image size.
 
bool GetPixels (const SkImageInfo &info, void *pixels, size_t row_bytes, unsigned int frame_index, std::optional< unsigned int > prior_frame) override
 Decode the image into a given buffer. This method is currently always used for sub-pixel image decoding. For full-sized still images, GetImage is always attempted first.
 
- Public Member Functions inherited from flutter::ImageGenerator
virtual ~ImageGenerator ()
 
sk_sp< SkImage > GetImage ()
 Creates an SkImage based on the current ImageInfo of this ImageGenerator.
 

Static Public Member Functions

static std::unique_ptr< ImageGeneratorMakeFromData (sk_sp< SkData > data)
 
static uint32_t ComputeCrc32 (const uint8_t *data, size_t length)
 Computes the CRC of the data in a PNG chunk.
 

Additional Inherited Members

- Static Public Attributes inherited from flutter::ImageGenerator
static const unsigned int kInfinitePlayCount
 Frame count value to denote infinite looping.
 

Detailed Description

Definition at line 27 of file image_generator_apng.h.

Constructor & Destructor Documentation

◆ ~APNGImageGenerator()

flutter::APNGImageGenerator::~APNGImageGenerator ( )
default

Member Function Documentation

◆ ComputeCrc32()

uint32_t flutter::APNGImageGenerator::ComputeCrc32 ( const uint8_t *  data,
size_t  length 
)
static

Computes the CRC of the data in a PNG chunk.

Definition at line 665 of file image_generator_apng.cc.

665 {
666 uint32_t crc = 0;
667 const uint8_t* data_p = data;
668
669 // zlib's crc32 can only take 16 bits at a time for the length, but PNG
670 // supports a 32 bit chunk length, so looping is necessary here.
671 // Note that crc32 is always called at least once, even if the chunk has an
672 // empty data section.
673 do {
674 uint16_t length16 = length;
675 if (length16 == 0 && length > 0) {
676 length16 = std::numeric_limits<uint16_t>::max();
677 }
678
679 crc = crc32(crc, data_p, length16);
680 length -= length16;
681 data_p += length16;
682 } while (length > 0);
683
684 return crc;
685}
size_t length
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switch_defs.h:36

References flutter::data, and length.

◆ GetFrameCount()

unsigned int flutter::APNGImageGenerator::GetFrameCount ( ) const
overridevirtual

Get the number of frames that the encoded image stores. This method is always expected to be called before GetFrameInfo, as the underlying image decoder may interpret frame information that is then used when calling GetFrameInfo.

Returns
The number of frames that the encoded image stores. This will always be 1 for single-frame images.

Implements flutter::ImageGenerator.

Definition at line 44 of file image_generator_apng.cc.

44 {
45 return frame_count_;
46}

◆ GetFrameInfo()

const ImageGenerator::FrameInfo flutter::APNGImageGenerator::GetFrameInfo ( unsigned int  frame_index)
overridevirtual

Get information about a single frame in the context of a multi-frame image, useful for animation and frame blending. This method should only ever be called after GetFrameCount has been called. This information is nonsensical for single-frame images.

Parameters
[in]frame_indexThe index of the frame to get information about.
Returns
Information about the given frame. If the image is single-frame, a default result is returned.
See also
GetFrameCount

Implements flutter::ImageGenerator.

Definition at line 52 of file image_generator_apng.cc.

53 {
54 unsigned int image_index = first_frame_index_ + frame_index;
55 if (!DemuxToImageIndex(image_index)) {
56 return {};
57 }
58
59 auto frame_info = images_[image_index].frame_info;
60 if (frame_info.has_value()) {
61 return frame_info.value();
62 }
63 return {};
64}

◆ GetInfo()

const SkImageInfo & flutter::APNGImageGenerator::GetInfo ( )
overridevirtual

Returns basic information about the contents of the encoded image. This information can almost always be collected by just interpreting the header of a decoded image.

Returns
Size and color information describing the image.
Note
This method is executed on the UI thread and used for layout purposes by the framework, and so this method should not perform long synchronous tasks.

Implements flutter::ImageGenerator.

Definition at line 40 of file image_generator_apng.cc.

40 {
41 return image_info_;
42}

◆ GetPixels()

bool flutter::APNGImageGenerator::GetPixels ( const SkImageInfo &  info,
void *  pixels,
size_t  row_bytes,
unsigned int  frame_index,
std::optional< unsigned int >  prior_frame 
)
overridevirtual

Decode the image into a given buffer. This method is currently always used for sub-pixel image decoding. For full-sized still images, GetImage is always attempted first.

Parameters
[in]infoThe desired size and color info of the decoded image to be returned. The implementation of GetScaledDimensions determines which sizes are supported by the image decoder.
[in]pixelsThe location where the raw decoded image data should be written.
[in]row_bytesThe total number of bytes that should make up a single row of decoded image data (i.e. width * bytes_per_pixel).
[in]frame_indexWhich frame to decode. This is only useful for multi-frame images.
[in]prior_frameOptional frame index parameter for multi-frame images which specifies the previous frame that should be use for blending. This hints to the decoder that it should use a previously cached frame instead of decoding dependency frame(s). If an empty value is supplied, the decoder should decode any necessary frames first.
Returns
True if the image was successfully decoded.
Note
This method performs potentially long synchronous work, and so it should never be executed on the UI thread. Image decoders do not require GPU acceleration, and so threads without a GPU context may also be used.
See also
GetScaledDimensions
  1. Demux the frame from the APNG stream.
  1. Decode the frame.
  1. Composite the frame onto the canvas.

Implements flutter::ImageGenerator.

Definition at line 70 of file image_generator_apng.cc.

74 {
75 FML_DCHECK(images_.size() > 0);
76 unsigned int image_index = first_frame_index_ + frame_index;
77
78 //----------------------------------------------------------------------------
79 /// 1. Demux the frame from the APNG stream.
80 ///
81
82 if (!DemuxToImageIndex(image_index)) {
83 FML_DLOG(ERROR) << "Couldn't demux image at index " << image_index
84 << " (frame index: " << frame_index
85 << ") from APNG stream.";
86 return RenderDefaultImage(info, pixels, row_bytes);
87 }
88
89 //----------------------------------------------------------------------------
90 /// 2. Decode the frame.
91 ///
92
93 APNGImage& frame = images_[image_index];
94 SkImageInfo frame_info = frame.codec->getInfo();
95
96 fml::SafeMath safe;
97 size_t frame_row_bytes =
98 safe.mul(frame_info.bytesPerPixel(), frame_info.width());
99 if (safe.overflow_detected()) {
100 FML_DLOG(ERROR) << "Failed to decode image at index " << image_index
101 << " (frame index: " << frame_index
102 << ") of APNG due to frame row bytes overflow.";
103 return false;
104 }
105
106 if (frame.pixels.empty()) {
107 size_t pixels_bytes = safe.mul(frame_row_bytes, frame_info.height());
108 if (safe.overflow_detected()) {
109 FML_DLOG(ERROR) << "Failed to decode image at index " << image_index
110 << " (frame index: " << frame_index
111 << ") of APNG due to pixel buffer size overflow.";
112 return false;
113 }
114 frame.pixels.resize(pixels_bytes);
115 SkCodec::Result result = frame.codec->getPixels(
116 frame.codec->getInfo(), frame.pixels.data(), frame_row_bytes);
117 if (result != SkCodec::kSuccess) {
118 FML_DLOG(ERROR) << "Failed to decode image at index " << image_index
119 << " (frame index: " << frame_index
120 << ") of APNG. SkCodec::Result: " << result;
121 return RenderDefaultImage(info, pixels, row_bytes);
122 }
123 }
124 if (!frame.frame_info.has_value()) {
125 FML_DLOG(ERROR) << "Failed to decode image at index " << image_index
126 << " (frame index: " << frame_index
127 << ") of APNG due to the frame missing data (frame_info).";
128 return false;
129 }
130 if (
131 // Check for unsigned integer wrapping for
132 // frame.{x|y}_offset + frame_info.{width|height}().
133 frame.x_offset >
134 std::numeric_limits<uint32_t>::max() - frame_info.width() ||
135 frame.y_offset >
136 std::numeric_limits<uint32_t>::max() - frame_info.height() ||
137
138 frame.x_offset + frame_info.width() >
139 static_cast<unsigned int>(info.width()) ||
140 frame.y_offset + frame_info.height() >
141 static_cast<unsigned int>(info.height())) {
142 FML_DLOG(ERROR)
143 << "Decoded image at index " << image_index
144 << " (frame index: " << frame_index
145 << ") rejected because the destination region (x: " << frame.x_offset
146 << ", y: " << frame.y_offset << ", width: " << frame_info.width()
147 << ", height: " << frame_info.height()
148 << ") is not entirely within the destination surface (width: "
149 << info.width() << ", height: " << info.height() << ").";
150 return false;
151 }
152
153 //----------------------------------------------------------------------------
154 /// 3. Composite the frame onto the canvas.
155 ///
156
157 if (info.colorType() != kN32_SkColorType) {
158 FML_DLOG(ERROR) << "Failed to composite image at index " << image_index
159 << " (frame index: " << frame_index
160 << ") of APNG due to the destination surface having an "
161 "unsupported color type.";
162 return false;
163 }
164 if (frame_info.colorType() != kN32_SkColorType) {
165 FML_DLOG(ERROR)
166 << "Failed to composite image at index " << image_index
167 << " (frame index: " << frame_index
168 << ") of APNG due to the frame having an unsupported color type.";
169 return false;
170 }
171
172 // Regardless of the byte order (RGBA vs BGRA), the blending operations are
173 // the same.
174 struct Pixel {
175 uint8_t channel[4];
176
177 uint8_t GetAlpha() { return channel[3]; }
178
179 void Premultiply() {
180 for (int i = 0; i < 3; i++) {
181 channel[i] = channel[i] * GetAlpha() / 0xFF;
182 }
183 }
184
185 void Unpremultiply() {
186 if (GetAlpha() == 0) {
187 channel[0] = channel[1] = channel[2] = 0;
188 return;
189 }
190 for (int i = 0; i < 3; i++) {
191 channel[i] = channel[i] * 0xFF / GetAlpha();
192 }
193 }
194 };
195
196 FML_DCHECK(frame_info.bytesPerPixel() == sizeof(Pixel));
197
198 bool result = true;
199
200 if (frame.frame_info->blend_mode == SkCodecAnimation::Blend::kSrc) {
201 SkPixmap src_pixmap(frame_info, frame.pixels.data(), frame_row_bytes);
202 uint8_t* dst_pixels = static_cast<uint8_t*>(pixels) +
203 frame.y_offset * row_bytes +
204 frame.x_offset * frame_info.bytesPerPixel();
205 result = src_pixmap.readPixels(info, dst_pixels, row_bytes);
206 if (!result) {
207 FML_DLOG(ERROR) << "Failed to copy pixels at index " << image_index
208 << " (frame index: " << frame_index << ") of APNG.";
209 }
210 } else if (frame.frame_info->blend_mode ==
211 SkCodecAnimation::Blend::kSrcOver) {
212 for (int y = 0; y < frame_info.height(); y++) {
213 auto src_row = frame.pixels.data() + y * frame_row_bytes;
214 auto dst_row = static_cast<uint8_t*>(pixels) +
215 (y + frame.y_offset) * row_bytes +
216 frame.x_offset * frame_info.bytesPerPixel();
217
218 for (int x = 0; x < frame_info.width(); x++) {
219 auto x_offset_bytes = x * frame_info.bytesPerPixel();
220
221 Pixel src = *reinterpret_cast<Pixel*>(src_row + x_offset_bytes);
222 Pixel* dst_p = reinterpret_cast<Pixel*>(dst_row + x_offset_bytes);
223 Pixel dst = *dst_p;
224
225 // Ensure both colors are premultiplied for the blending operation.
226 if (info.alphaType() == kUnpremul_SkAlphaType) {
227 dst.Premultiply();
228 }
229 if (frame_info.alphaType() == kUnpremul_SkAlphaType) {
230 src.Premultiply();
231 }
232
233 for (int i = 0; i < 4; i++) {
234 dst.channel[i] =
235 src.channel[i] + dst.channel[i] * (0xFF - src.GetAlpha()) / 0xFF;
236 }
237
238 // The final color is premultiplied. Unpremultiply to match the
239 // backdrop surface if necessary.
240 if (info.alphaType() == kUnpremul_SkAlphaType) {
241 dst.Unpremultiply();
242 }
243
244 *dst_p = dst;
245 }
246 }
247 }
248
249 return result;
250}
size_t mul(size_t x, size_t y)
Definition safe_math.cc:12
bool overflow_detected() const
Definition safe_math.h:17
int32_t x
const gchar * channel
#define FML_DLOG(severity)
Definition logging.h:121
#define FML_DCHECK(condition)
Definition logging.h:122
double y

References channel, FML_DCHECK, FML_DLOG, i, fml::SafeMath::mul(), fml::SafeMath::overflow_detected(), x, and y.

◆ GetPlayCount()

unsigned int flutter::APNGImageGenerator::GetPlayCount ( ) const
overridevirtual

The number of times an animated image should play through before playback stops.

Returns
If this image is animated, the number of times the animation should play through is returned, otherwise it'll just return 1. If the animation should loop forever, kInfinitePlayCount is returned.

Implements flutter::ImageGenerator.

Definition at line 48 of file image_generator_apng.cc.

48 {
49 return frame_count_ > 1 ? play_count_ : 1;
50}

◆ GetScaledDimensions()

SkISize flutter::APNGImageGenerator::GetScaledDimensions ( float  scale)
overridevirtual

Given a scale value, find the closest image size that can be used for efficiently decoding the image. If subpixel image decoding is not supported by the decoder, this method should just return the original image size.

Parameters
[in]scaleThe desired scale factor of the image for decoding.
Returns
The closest image size that can be used for efficiently decoding the image.
Note
This method is called prior to GetPixels in order to query for supported sizes.
See also
GetPixels

Implements flutter::ImageGenerator.

Definition at line 66 of file image_generator_apng.cc.

66 {
67 return image_info_.dimensions();
68}

◆ MakeFromData()

std::unique_ptr< ImageGenerator > flutter::APNGImageGenerator::MakeFromData ( sk_sp< SkData >  data)
static

Definition at line 252 of file image_generator_apng.cc.

253 {
254 // Ensure the buffer is large enough to at least contain the PNG signature
255 // and a chunk header.
256 if (data->size() < sizeof(kPngSignature) + sizeof(ChunkHeader)) {
257 return nullptr;
258 }
259 // Validate the full PNG signature.
260 const uint8_t* data_p = static_cast<const uint8_t*>(data.get()->data());
261 if (memcmp(data_p, kPngSignature, sizeof(kPngSignature))) {
262 return nullptr;
263 }
264
265 // Validate the header chunk.
266 const ChunkHeader* chunk = reinterpret_cast<const ChunkHeader*>(data_p + 8);
267 if (!IsValidChunkHeader(data_p, data->size(), chunk) ||
268 chunk->get_data_length() != sizeof(ImageHeaderChunkData) ||
269 chunk->get_type() != kImageHeaderChunkType) {
270 return nullptr;
271 }
272
273 // Walk the chunks to find the "animation control" chunk. If an "image data"
274 // chunk is found first, this PNG is not animated.
275 while (true) {
276 chunk = GetNextChunk(data_p, data->size(), chunk);
277
278 if (chunk == nullptr) {
279 return nullptr;
280 }
281 if (chunk->get_type() == kImageDataChunkType) {
282 return nullptr;
283 }
284 if (chunk->get_type() == kAnimationControlChunkType) {
285 break;
286 }
287 }
288
289 if (chunk->get_data_length() < sizeof(AnimationControlChunkData)) {
290 return nullptr;
291 }
292 const AnimationControlChunkData* animation_data =
293 CastChunkData<AnimationControlChunkData>(chunk);
294
295 // Extract the header signature and chunks to prepend when demuxing images.
296 std::optional<std::vector<uint8_t>> header;
297 const void* first_chunk_p;
298 std::tie(header, first_chunk_p) = ExtractHeader(data_p, data->size());
299 if (!header.has_value()) {
300 return nullptr;
301 }
302
303 // Demux the first image in the APNG chunk stream in order to interpret
304 // extent and blending info immediately.
305 std::optional<APNGImage> default_image;
306 const void* next_chunk_p;
307 std::tie(default_image, next_chunk_p) =
308 DemuxNextImage(data_p, data->size(), header.value(), first_chunk_p);
309 if (!default_image.has_value()) {
310 return nullptr;
311 }
312
313 unsigned int play_count = animation_data->get_num_plays();
314 if (play_count == 0) {
315 play_count = kInfinitePlayCount;
316 }
317
318 SkImageInfo image_info = default_image.value().codec->getInfo();
319 return std::unique_ptr<APNGImageGenerator>(
320 new APNGImageGenerator(data, image_info, std::move(default_image.value()),
321 animation_data->get_num_frames(), play_count,
322 next_chunk_p, header.value()));
323}
static const unsigned int kInfinitePlayCount
Frame count value to denote infinite looping.

References flutter::data.

Referenced by flutter::ImageGeneratorRegistry::ImageGeneratorRegistry(), and flutter::testing::TEST().


The documentation for this class was generated from the following files: