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