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