Flutter Engine
The Flutter Engine
CodecPartialTest.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2016 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
10#include "include/core/SkData.h"
17#include "tests/CodecPriv.h"
18#include "tests/FakeStreams.h"
19#include "tests/Test.h"
20#include "tools/Resources.h"
21
22#include <algorithm>
23#include <cstring>
24#include <initializer_list>
25#include <memory>
26#include <utility>
27#include <vector>
28
30 SkImageInfo defaultInfo = codec->getInfo();
31 // Note: This drops the SkColorSpace, allowing the equality check between two
32 // different codecs created from the same file to have the same SkImageInfo.
33 return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
34}
35
37 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(data)));
38 if (!codec) {
39 return false;
40 }
41
42 const SkImageInfo info = standardize_info(codec.get());
43 dst->allocPixels(info);
44 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
45}
46
47static bool compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
48 const SkImageInfo& info = bm1.info();
49 if (info != bm2.info()) {
50 ERRORF(r, "Bitmaps have different image infos!");
51 return false;
52 }
53 const size_t rowBytes = info.minRowBytes();
54 for (int i = 0; i < info.height(); i++) {
55 if (0 != memcmp(bm1.getAddr(0, i), bm2.getAddr(0, i), rowBytes)) {
56 ERRORF(r, "Bitmaps have different pixels, starting on line %i!", i);
57 return false;
58 }
59 }
60
61 return true;
62}
63
64static void test_partial(skiatest::Reporter* r, const char* name, const sk_sp<SkData>& file,
65 size_t minBytes, size_t increment) {
66 SkBitmap truth;
67 if (!create_truth(file, &truth)) {
68 ERRORF(r, "Failed to decode %s\n", name);
69 return;
70 }
71
72 // Now decode part of the file
73 HaltingStream* stream = new HaltingStream(file, minBytes);
74
75 // Note that we cheat and hold on to a pointer to stream, though it is owned by
76 // partialCodec.
77 auto partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
78 if (!partialCodec) {
79 ERRORF(r, "Failed to create codec for %s with %zu bytes", name, minBytes);
80 return;
81 }
82
83 const SkImageInfo info = standardize_info(partialCodec.get());
84 SkASSERT(info == truth.info());
85 SkBitmap incremental;
86 incremental.allocPixels(info);
87
88 while (true) {
89 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
90 incremental.getPixels(), incremental.rowBytes());
91 if (startResult == SkCodec::kSuccess) {
92 break;
93 }
94
95 if (stream->isAllDataReceived()) {
96 ERRORF(r, "Failed to start incremental decode\n");
97 return;
98 }
99
100 stream->addNewData(increment);
101 }
102
103 while (true) {
104 // This imitates how Chromium calls getFrameCount before resuming a decode.
105 partialCodec->getFrameCount();
106
107 const SkCodec::Result result = partialCodec->incrementalDecode();
108
109 if (result == SkCodec::kSuccess) {
110 break;
111 }
112
114
115 if (stream->isAllDataReceived()) {
116 ERRORF(r, "Failed to completely decode %s", name);
117 return;
118 }
119
120 stream->addNewData(increment);
121 }
122
123 // compare to original
124 compare_bitmaps(r, truth, incremental);
125}
126
127static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
129 if (!file) {
130 SkDebugf("missing resource %s\n", name);
131 return;
132 }
133
134 // This size is arbitrary, but deliberately different from the buffer size used by SkPngCodec.
135 constexpr size_t kIncrement = 1000;
136 test_partial(r, name, file, std::max(file->size() / 2, minBytes), kIncrement);
137}
138
139DEF_TEST(Codec_partial, r) {
140#if 0
141 // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
142 // support incremental decoding.
143 test_partial(r, "images/plane.png");
144 test_partial(r, "images/plane_interlaced.png");
145 test_partial(r, "images/yellow_rose.png");
146 test_partial(r, "images/index8.png");
147 test_partial(r, "images/color_wheel.png");
148 test_partial(r, "images/mandrill_256.png");
149 test_partial(r, "images/mandrill_32.png");
150 test_partial(r, "images/arrow.png");
151 test_partial(r, "images/randPixels.png");
152 test_partial(r, "images/baby_tux.png");
153#endif
154 test_partial(r, "images/box.gif");
155 test_partial(r, "images/randPixels.gif", 215);
156 test_partial(r, "images/color_wheel.gif");
157}
158
159DEF_TEST(Codec_partialWuffs, r) {
160 const char* path = "images/alphabetAnim.gif";
162 if (!file) {
163 ERRORF(r, "missing %s", path);
164 } else {
165 // This is the end of the first frame. SkCodec will treat this as a
166 // single frame gif.
167 file = SkData::MakeSubset(file.get(), 0, 153);
168 // Start with 100 to get a partial decode, then add the rest of the
169 // first frame to decode a full image.
170 test_partial(r, path, file, 100, 53);
171 }
172}
173
174// Verify that when decoding an animated gif byte by byte we report the correct
175// fRequiredFrame as soon as getFrameInfo reports the frame.
176DEF_TEST(Codec_requiredFrame, r) {
177 auto path = "images/colorTables.gif";
179 if (!file) {
180 return;
181 }
182
183 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(file));
184 if (!codec) {
185 ERRORF(r, "Failed to create codec from %s", path);
186 return;
187 }
188
189 auto frameInfo = codec->getFrameInfo();
190 if (frameInfo.size() <= 1) {
191 ERRORF(r, "Test is uninteresting with 0 or 1 frames");
192 return;
193 }
194
195 HaltingStream* stream(nullptr);
196 std::unique_ptr<SkCodec> partialCodec(nullptr);
197 for (size_t i = 0; !partialCodec; i++) {
198 if (file->size() == i) {
199 ERRORF(r, "Should have created a partial codec for %s", path);
200 return;
201 }
202 stream = new HaltingStream(file, i);
203 partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
204 }
205
206 std::vector<SkCodec::FrameInfo> partialInfo;
207 size_t frameToCompare = 0;
208 while (true) {
209 partialInfo = partialCodec->getFrameInfo();
210 for (; frameToCompare < partialInfo.size(); frameToCompare++) {
211 REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame
212 == frameInfo[frameToCompare].fRequiredFrame);
213 }
214
215 if (frameToCompare == frameInfo.size()) {
216 break;
217 }
218
219 if (stream->getLength() == file->size()) {
220 ERRORF(r, "Should have found all frames for %s", path);
221 return;
222 }
223 stream->addNewData(1);
224 }
225}
226
227DEF_TEST(Codec_partialAnim, r) {
228 auto path = "images/test640x479.gif";
230 if (!file) {
231 return;
232 }
233
234 // This stream will be owned by fullCodec, but we hang on to the pointer
235 // to determine frame offsets.
236 std::unique_ptr<SkCodec> fullCodec(SkCodec::MakeFromStream(std::make_unique<SkMemoryStream>(file)));
237 const auto info = standardize_info(fullCodec.get());
238
239 // frameByteCounts stores the number of bytes to decode a particular frame.
240 // - [0] is the number of bytes for the header
241 // - frames[i] requires frameByteCounts[i+1] bytes to decode
242 const std::vector<size_t> frameByteCounts = { 455, 69350, 1344, 1346, 1327 };
243 std::vector<SkBitmap> frames;
244 for (size_t i = 0; true; i++) {
246 frame.allocPixels(info);
247
248 SkCodec::Options opts;
249 opts.fFrameIndex = i;
250 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
251 frame.rowBytes(), &opts);
252
254 // We need to distinguish between a partial frame and no more frames.
255 // getFrameInfo lets us do this, since it tells the number of frames
256 // not considering whether they are complete.
257 // FIXME: Should we use a different Result?
258 if (fullCodec->getFrameInfo().size() > i) {
259 // This is a partial frame.
260 frames.push_back(frame);
261 }
262 break;
263 }
264
265 if (result != SkCodec::kSuccess) {
266 ERRORF(r, "Failed to decode frame %zu from %s", i, path);
267 return;
268 }
269
270 frames.push_back(frame);
271 }
272
273 // Now decode frames partially, then completely, and compare to the original.
274 HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]);
275 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
276 std::unique_ptr<SkStream>(haltingStream)));
277 if (!partialCodec) {
278 ERRORF(r, "Failed to create a partial codec from %s with %zu bytes out of %zu",
279 path, frameByteCounts[0], file->size());
280 return;
281 }
282
283 SkASSERT(frameByteCounts.size() > frames.size());
284 for (size_t i = 0; i < frames.size(); i++) {
285 const size_t fullFrameBytes = frameByteCounts[i + 1];
286 const size_t firstHalf = fullFrameBytes / 2;
287 const size_t secondHalf = fullFrameBytes - firstHalf;
288
289 haltingStream->addNewData(firstHalf);
290 auto frameInfo = partialCodec->getFrameInfo();
291 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
292 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
293
295 frame.allocPixels(info);
296
297 SkCodec::Options opts;
298 opts.fFrameIndex = i;
299 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
300 frame.getPixels(), frame.rowBytes(), &opts);
301 if (result != SkCodec::kSuccess) {
302 ERRORF(r, "Failed to start incremental decode for %s on frame %zu",
303 path, i);
304 return;
305 }
306
307 result = partialCodec->incrementalDecode();
309
310 haltingStream->addNewData(secondHalf);
311 result = partialCodec->incrementalDecode();
313
314 frameInfo = partialCodec->getFrameInfo();
315 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
316 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
317 if (!compare_bitmaps(r, frames[i], frame)) {
318 ERRORF(r, "\tfailure was on frame %zu", i);
319 SkString name = SkStringPrintf("expected_%zu", i);
320 write_bm(name.c_str(), frames[i]);
321
322 name = SkStringPrintf("actual_%zu", i);
323 write_bm(name.c_str(), frame);
324 }
325 }
326}
327
328// Test that calling getPixels when an incremental decode has been
329// started (but not finished) makes the next call to incrementalDecode
330// require a call to startIncrementalDecode.
331static void test_interleaved(skiatest::Reporter* r, const char* name) {
333 if (!file) {
334 return;
335 }
336 const size_t halfSize = file->size() / 2;
337 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
338 std::make_unique<HaltingStream>(std::move(file), halfSize)));
339 if (!partialCodec) {
340 ERRORF(r, "Failed to create codec for %s", name);
341 return;
342 }
343
344 const SkImageInfo info = standardize_info(partialCodec.get());
345 SkBitmap incremental;
346 incremental.allocPixels(info);
347
348 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
349 incremental.getPixels(), incremental.rowBytes());
350 if (startResult != SkCodec::kSuccess) {
351 ERRORF(r, "Failed to start incremental decode\n");
352 return;
353 }
354
355 SkCodec::Result result = partialCodec->incrementalDecode();
357
359 full.allocPixels(info);
360 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
362
363 // Now incremental decode will fail
364 result = partialCodec->incrementalDecode();
366}
367
368DEF_TEST(Codec_rewind, r) {
369 test_interleaved(r, "images/plane.png");
370 test_interleaved(r, "images/plane_interlaced.png");
371 test_interleaved(r, "images/box.gif");
372}
373
374// Modified version of the giflib logo, from
375// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
376// The global color map has been replaced with a local color map.
377static unsigned char gNoGlobalColorMap[] = {
378 // Header
379 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
380
381 // Logical screen descriptor
382 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
383
384 // Image descriptor
385 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
386
387 // Local color table
388 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
389
390 // Image data
391 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
392 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
393
394 // Trailer
395 0x3B,
396};
397
398// Test that a gif file truncated before its local color map behaves as expected.
399DEF_TEST(Codec_GifPreMap, r) {
401 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
402 if (!codec) {
403 ERRORF(r, "failed to create codec");
404 return;
405 }
406
407 SkBitmap truth;
408 auto info = standardize_info(codec.get());
409 truth.allocPixels(info);
410
411 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
413
414 // Truncate to 23 bytes, just before the color map. This should fail to decode.
415 //
416 // See also Codec_GifTruncated2 in GifTest.cpp for this magic 23.
418 REPORTER_ASSERT(r, codec);
419 if (codec) {
420 SkBitmap bm;
421 bm.allocPixels(info);
422 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
423
425 }
426
427 // Again, truncate to 23 bytes, this time for an incremental decode. We
428 // cannot start an incremental decode until we have more data. If we did,
429 // we would be using the wrong color table.
431 codec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
432 REPORTER_ASSERT(r, codec);
433 if (codec) {
434 SkBitmap bm;
435 bm.allocPixels(info);
436 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
437
439
440 // Note that this is incrementalDecode, not startIncrementalDecode.
441 result = codec->incrementalDecode();
443
444 stream->addNewData(data->size());
445
446 result = codec->incrementalDecode();
448 compare_bitmaps(r, truth, bm);
449 }
450}
451
452DEF_TEST(Codec_emptyIDAT, r) {
453 const char* name = "images/baby_tux.png";
455 if (!file) {
456 return;
457 }
458
459 // Truncate to the beginning of the IDAT, immediately after the IDAT tag.
460 file = SkData::MakeSubset(file.get(), 0, 80);
461
462 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(file)));
463 if (!codec) {
464 ERRORF(r, "Failed to create a codec for %s", name);
465 return;
466 }
467
468 SkBitmap bm;
469 const auto info = standardize_info(codec.get());
470 bm.allocPixels(info);
471
472 const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
474}
475
476DEF_TEST(Codec_incomplete, r) {
477 for (const char* name : { "images/baby_tux.png",
478 "images/baby_tux.webp",
479 "images/CMYK.jpg",
480 "images/color_wheel.gif",
481 "images/google_chrome.ico",
482 "images/rle.bmp",
483 "images/mandrill.wbmp",
484 }) {
486 if (!file) {
487 continue;
488 }
489
490 for (size_t len = 14; len <= file->size(); len += 5) {
492 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
493 std::make_unique<SkMemoryStream>(file->data(), len), &result));
494 if (codec) {
495 if (result != SkCodec::kSuccess) {
496 ERRORF(r, "Created an SkCodec for %s with %zu bytes, but "
497 "reported an error %i", name, len, (int)result);
498 }
499 break;
500 }
501
503 ERRORF(r, "Reported error %i for %s with %zu bytes",
504 (int)result, name, len);
505 break;
506 }
507 }
508 }
509}
static bool compare_bitmaps(skiatest::Reporter *r, const SkBitmap &bm1, const SkBitmap &bm2)
static unsigned char gNoGlobalColorMap[]
static SkImageInfo standardize_info(SkCodec *codec)
static bool create_truth(sk_sp< SkData > data, SkBitmap *dst)
static void test_partial(skiatest::Reporter *r, const char *name, const sk_sp< SkData > &file, size_t minBytes, size_t increment)
DEF_TEST(Codec_partial, r)
static void test_interleaved(skiatest::Reporter *r, const char *name)
void write_bm(const char *name, const SkBitmap &bm)
Definition: CodecPriv.h:33
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
sk_sp< SkData > GetResourceAsData(const char *resource)
Definition: Resources.cpp:42
#define SkASSERT(cond)
Definition: SkAssert.h:116
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
#define ERRORF(r,...)
Definition: Test.h:293
void addNewData(size_t extra)
Definition: FakeStreams.h:69
void allocPixels(const SkImageInfo &info, size_t rowBytes)
Definition: SkBitmap.cpp:258
void * getAddr(int x, int y) const
Definition: SkBitmap.cpp:406
size_t rowBytes() const
Definition: SkBitmap.h:238
void * getPixels() const
Definition: SkBitmap.h:283
const SkImageInfo & info() const
Definition: SkBitmap.h:139
static std::unique_ptr< SkCodec > MakeFromStream(std::unique_ptr< SkStream >, SkSpan< const SkCodecs::Decoder > decoders, Result *=nullptr, SkPngChunkReader *=nullptr, SelectionPolicy selectionPolicy=SelectionPolicy::kPreferStillImage)
Definition: SkCodec.cpp:163
static std::unique_ptr< SkCodec > MakeFromData(sk_sp< SkData >, SkSpan< const SkCodecs::Decoder > decoders, SkPngChunkReader *=nullptr)
Definition: SkCodec.cpp:241
Result
Definition: SkCodec.h:76
@ kIncompleteInput
Definition: SkCodec.h:84
@ kInvalidInput
Definition: SkCodec.h:109
@ kInvalidParameters
Definition: SkCodec.h:105
@ kSuccess
Definition: SkCodec.h:80
SkImageInfo getInfo() const
Definition: SkCodec.h:228
static sk_sp< SkData > MakeWithoutCopy(const void *data, size_t length)
Definition: SkData.h:116
static sk_sp< SkData > MakeSubset(const SkData *src, size_t offset, size_t length)
Definition: SkData.cpp:173
double frame
Definition: examples.cpp:31
GAsyncResult * result
static float max(float r, float g, float b)
Definition: hsl.cpp:49
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
Definition: full.py:1
dst
Definition: cp.py:12
static SkImageInfo MakeN32Premul(int width, int height)
int width() const
Definition: SkImageInfo.h:365
int height() const
Definition: SkImageInfo.h:371
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63