• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
172 // Verify that when decoding an animated gif byte by byte we report the correct
173 // fRequiredFrame as soon as getFrameInfo reports the frame.
DEF_TEST(Codec_requiredFrame,r)174 DEF_TEST(Codec_requiredFrame, r) {
175     auto path = "images/colorTables.gif";
176     sk_sp<SkData> file = GetResourceAsData(path);
177     if (!file) {
178         return;
179     }
180 
181     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(file));
182     if (!codec) {
183         ERRORF(r, "Failed to create codec from %s", path);
184         return;
185     }
186 
187     auto frameInfo = codec->getFrameInfo();
188     if (frameInfo.size() <= 1) {
189         ERRORF(r, "Test is uninteresting with 0 or 1 frames");
190         return;
191     }
192 
193     HaltingStream* stream(nullptr);
194     std::unique_ptr<SkCodec> partialCodec(nullptr);
195     for (size_t i = 0; !partialCodec; i++) {
196         if (file->size() == i) {
197             ERRORF(r, "Should have created a partial codec for %s", path);
198             return;
199         }
200         stream = new HaltingStream(file, i);
201         partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
202     }
203 
204     std::vector<SkCodec::FrameInfo> partialInfo;
205     size_t frameToCompare = 0;
206     while (true) {
207         partialInfo = partialCodec->getFrameInfo();
208         for (; frameToCompare < partialInfo.size(); frameToCompare++) {
209             REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame
210                                 == frameInfo[frameToCompare].fRequiredFrame);
211         }
212 
213         if (frameToCompare == frameInfo.size()) {
214             break;
215         }
216 
217         if (stream->getLength() == file->size()) {
218             ERRORF(r, "Should have found all frames for %s", path);
219             return;
220         }
221         stream->addNewData(1);
222     }
223 }
224 
DEF_TEST(Codec_partialAnim,r)225 DEF_TEST(Codec_partialAnim, r) {
226     auto path = "images/test640x479.gif";
227     sk_sp<SkData> file = GetResourceAsData(path);
228     if (!file) {
229         return;
230     }
231 
232     // This stream will be owned by fullCodec, but we hang on to the pointer
233     // to determine frame offsets.
234     std::unique_ptr<SkCodec> fullCodec(SkCodec::MakeFromStream(std::make_unique<SkMemoryStream>(file)));
235     const auto info = standardize_info(fullCodec.get());
236 
237     // frameByteCounts stores the number of bytes to decode a particular frame.
238     // - [0] is the number of bytes for the header
239     // - frames[i] requires frameByteCounts[i+1] bytes to decode
240     const std::vector<size_t> frameByteCounts = { 455, 69350, 1344, 1346, 1327 };
241     std::vector<SkBitmap> frames;
242     for (size_t i = 0; true; i++) {
243         SkBitmap frame;
244         frame.allocPixels(info);
245 
246         SkCodec::Options opts;
247         opts.fFrameIndex = i;
248         const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
249                 frame.rowBytes(), &opts);
250 
251         if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
252             // We need to distinguish between a partial frame and no more frames.
253             // getFrameInfo lets us do this, since it tells the number of frames
254             // not considering whether they are complete.
255             // FIXME: Should we use a different Result?
256             if (fullCodec->getFrameInfo().size() > i) {
257                 // This is a partial frame.
258                 frames.push_back(frame);
259             }
260             break;
261         }
262 
263         if (result != SkCodec::kSuccess) {
264             ERRORF(r, "Failed to decode frame %zu from %s", i, path);
265             return;
266         }
267 
268         frames.push_back(frame);
269     }
270 
271     // Now decode frames partially, then completely, and compare to the original.
272     HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]);
273     std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
274                                                       std::unique_ptr<SkStream>(haltingStream)));
275     if (!partialCodec) {
276         ERRORF(r, "Failed to create a partial codec from %s with %zu bytes out of %zu",
277                path, frameByteCounts[0], file->size());
278         return;
279     }
280 
281     SkASSERT(frameByteCounts.size() > frames.size());
282     for (size_t i = 0; i < frames.size(); i++) {
283         const size_t fullFrameBytes = frameByteCounts[i + 1];
284         const size_t firstHalf = fullFrameBytes / 2;
285         const size_t secondHalf = fullFrameBytes - firstHalf;
286 
287         haltingStream->addNewData(firstHalf);
288         auto frameInfo = partialCodec->getFrameInfo();
289         REPORTER_ASSERT(r, frameInfo.size() == i + 1);
290         REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
291 
292         SkBitmap frame;
293         frame.allocPixels(info);
294 
295         SkCodec::Options opts;
296         opts.fFrameIndex = i;
297         SkCodec::Result result = partialCodec->startIncrementalDecode(info,
298                 frame.getPixels(), frame.rowBytes(), &opts);
299         if (result != SkCodec::kSuccess) {
300             ERRORF(r, "Failed to start incremental decode for %s on frame %zu",
301                    path, i);
302             return;
303         }
304 
305         result = partialCodec->incrementalDecode();
306         REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
307 
308         haltingStream->addNewData(secondHalf);
309         result = partialCodec->incrementalDecode();
310         REPORTER_ASSERT(r, SkCodec::kSuccess == result);
311 
312         frameInfo = partialCodec->getFrameInfo();
313         REPORTER_ASSERT(r, frameInfo.size() == i + 1);
314         REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
315         if (!compare_bitmaps(r, frames[i], frame)) {
316             ERRORF(r, "\tfailure was on frame %zu", i);
317             SkString name = SkStringPrintf("expected_%zu", i);
318             write_bm(name.c_str(), frames[i]);
319 
320             name = SkStringPrintf("actual_%zu", i);
321             write_bm(name.c_str(), frame);
322         }
323     }
324 }
325 
326 // Test that calling getPixels when an incremental decode has been
327 // started (but not finished) makes the next call to incrementalDecode
328 // require a call to startIncrementalDecode.
test_interleaved(skiatest::Reporter * r,const char * name)329 static void test_interleaved(skiatest::Reporter* r, const char* name) {
330     sk_sp<SkData> file = GetResourceAsData(name);
331     if (!file) {
332         return;
333     }
334     const size_t halfSize = file->size() / 2;
335     std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
336                                   std::make_unique<HaltingStream>(std::move(file), halfSize)));
337     if (!partialCodec) {
338         ERRORF(r, "Failed to create codec for %s", name);
339         return;
340     }
341 
342     const SkImageInfo info = standardize_info(partialCodec.get());
343     SkBitmap incremental;
344     incremental.allocPixels(info);
345 
346     const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
347             incremental.getPixels(), incremental.rowBytes());
348     if (startResult != SkCodec::kSuccess) {
349         ERRORF(r, "Failed to start incremental decode\n");
350         return;
351     }
352 
353     SkCodec::Result result = partialCodec->incrementalDecode();
354     REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
355 
356     SkBitmap full;
357     full.allocPixels(info);
358     result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
359     REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
360 
361     // Now incremental decode will fail
362     result = partialCodec->incrementalDecode();
363     REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
364 }
365 
DEF_TEST(Codec_rewind,r)366 DEF_TEST(Codec_rewind, r) {
367     test_interleaved(r, "images/plane.png");
368     test_interleaved(r, "images/plane_interlaced.png");
369     test_interleaved(r, "images/box.gif");
370 }
371 
372 // Modified version of the giflib logo, from
373 // http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
374 // The global color map has been replaced with a local color map.
375 static unsigned char gNoGlobalColorMap[] = {
376   // Header
377   0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
378 
379   // Logical screen descriptor
380   0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
381 
382   // Image descriptor
383   0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
384 
385   // Local color table
386   0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
387 
388   // Image data
389   0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
390   0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
391 
392   // Trailer
393   0x3B,
394 };
395 
396 // Test that a gif file truncated before its local color map behaves as expected.
DEF_TEST(Codec_GifPreMap,r)397 DEF_TEST(Codec_GifPreMap, r) {
398     sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
399     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
400     if (!codec) {
401         ERRORF(r, "failed to create codec");
402         return;
403     }
404 
405     SkBitmap truth;
406     auto info = standardize_info(codec.get());
407     truth.allocPixels(info);
408 
409     auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
410     REPORTER_ASSERT(r, result == SkCodec::kSuccess);
411 
412     // Truncate to 23 bytes, just before the color map. This should fail to decode.
413     //
414     // See also Codec_GifTruncated2 in GifTest.cpp for this magic 23.
415     codec = SkCodec::MakeFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23));
416     REPORTER_ASSERT(r, codec);
417     if (codec) {
418         SkBitmap bm;
419         bm.allocPixels(info);
420         result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
421 
422         // See the comments in Codec_GifTruncated2.
423 #ifdef SK_HAS_WUFFS_LIBRARY
424         REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
425 #else
426         REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
427 #endif
428     }
429 
430     // Again, truncate to 23 bytes, this time for an incremental decode. We
431     // cannot start an incremental decode until we have more data. If we did,
432     // we would be using the wrong color table.
433     HaltingStream* stream = new HaltingStream(data, 23);
434     codec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
435     REPORTER_ASSERT(r, codec);
436     if (codec) {
437         SkBitmap bm;
438         bm.allocPixels(info);
439         result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
440 
441         // See the comments in Codec_GifTruncated2.
442 #ifdef SK_HAS_WUFFS_LIBRARY
443         REPORTER_ASSERT(r, result == SkCodec::kSuccess);
444 
445         // Note that this is incrementalDecode, not startIncrementalDecode.
446         result = codec->incrementalDecode();
447         REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
448 
449         stream->addNewData(data->size());
450 #else
451         REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
452 
453         // Note that this is startIncrementalDecode, not incrementalDecode.
454         stream->addNewData(data->size());
455         result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
456         REPORTER_ASSERT(r, result == SkCodec::kSuccess);
457 #endif
458 
459         result = codec->incrementalDecode();
460         REPORTER_ASSERT(r, result == SkCodec::kSuccess);
461         compare_bitmaps(r, truth, bm);
462     }
463 }
464 
DEF_TEST(Codec_emptyIDAT,r)465 DEF_TEST(Codec_emptyIDAT, r) {
466     const char* name = "images/baby_tux.png";
467     sk_sp<SkData> file = GetResourceAsData(name);
468     if (!file) {
469         return;
470     }
471 
472     // Truncate to the beginning of the IDAT, immediately after the IDAT tag.
473     file = SkData::MakeSubset(file.get(), 0, 80);
474 
475     std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(file)));
476     if (!codec) {
477         ERRORF(r, "Failed to create a codec for %s", name);
478         return;
479     }
480 
481     SkBitmap bm;
482     const auto info = standardize_info(codec.get());
483     bm.allocPixels(info);
484 
485     const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
486     REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
487 }
488 
DEF_TEST(Codec_incomplete,r)489 DEF_TEST(Codec_incomplete, r) {
490     for (const char* name : { "images/baby_tux.png",
491                               "images/baby_tux.webp",
492                               "images/CMYK.jpg",
493                               "images/color_wheel.gif",
494                               "images/google_chrome.ico",
495                               "images/rle.bmp",
496                               "images/mandrill.wbmp",
497                               }) {
498         sk_sp<SkData> file = GetResourceAsData(name);
499         if (!file) {
500             continue;
501         }
502 
503         for (size_t len = 14; len <= file->size(); len += 5) {
504             SkCodec::Result result;
505             std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
506                                    std::make_unique<SkMemoryStream>(file->data(), len), &result));
507             if (codec) {
508                 if (result != SkCodec::kSuccess) {
509                     ERRORF(r, "Created an SkCodec for %s with %zu bytes, but "
510                               "reported an error %i", name, len, (int)result);
511                 }
512                 break;
513             }
514 
515             if (SkCodec::kIncompleteInput != result) {
516                 ERRORF(r, "Reported error %i for %s with %zu bytes",
517                        (int)result, name, len);
518                 break;
519             }
520         }
521     }
522 }
523