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