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