Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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
36static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
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";
161 auto file = GetResourceAsData(path);
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";
178 sk_sp<SkData> file = GetResourceAsData(path);
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";
229 sk_sp<SkData> file = GetResourceAsData(path);
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++) {
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
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.
430 HaltingStream* stream = new HaltingStream(data, 23);
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)
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 static SkString SkStringPrintf()
Definition SkString.h:287
#define DEF_TEST(name, reporter)
Definition Test.h:312
#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
@ 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
const char * name
Definition fuchsia.cc:50
Definition full.py:1
static SkImageInfo MakeN32Premul(int width, int height)
int width() const
int height() const