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