blob: e40ff8b7143fb58739a570be6887187cb5c9a69b [file] [log] [blame]
scroggo8e6c7ad2016-09-16 08:20:38 -07001/*
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 "SkBitmap.h"
9#include "SkCodec.h"
10#include "SkData.h"
11#include "SkImageInfo.h"
Mike Reedede7bac2017-07-23 15:30:02 -040012#include "SkMakeUnique.h"
scroggo8e6c7ad2016-09-16 08:20:38 -070013#include "SkRWBuffer.h"
14#include "SkString.h"
15
Leon Scroggins III932efed2016-12-16 11:39:51 -050016#include "FakeStreams.h"
scroggo8e6c7ad2016-09-16 08:20:38 -070017#include "Resources.h"
18#include "Test.h"
19
scroggo8e6c7ad2016-09-16 08:20:38 -070020static SkImageInfo standardize_info(SkCodec* codec) {
21 SkImageInfo defaultInfo = codec->getInfo();
22 // Note: This drops the SkColorSpace, allowing the equality check between two
23 // different codecs created from the same file to have the same SkImageInfo.
24 return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
25}
26
27static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
Mike Reedede7bac2017-07-23 15:30:02 -040028 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(data)));
scroggo8e6c7ad2016-09-16 08:20:38 -070029 if (!codec) {
30 return false;
31 }
32
Ben Wagner145dbcd2016-11-03 14:40:50 -040033 const SkImageInfo info = standardize_info(codec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070034 dst->allocPixels(info);
35 return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
36}
37
scroggo19b91532016-10-24 09:03:26 -070038static void compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
39 const SkImageInfo& info = bm1.info();
40 if (info != bm2.info()) {
41 ERRORF(r, "Bitmaps have different image infos!");
42 return;
43 }
44 const size_t rowBytes = info.minRowBytes();
45 for (int i = 0; i < info.height(); i++) {
46 REPORTER_ASSERT(r, !memcmp(bm1.getAddr(0, 0), bm2.getAddr(0, 0), rowBytes));
47 }
48}
49
Leon Scroggins III4993b952016-12-08 11:54:04 -050050static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -050051 sk_sp<SkData> file = GetResourceAsData(name);
scroggo8e6c7ad2016-09-16 08:20:38 -070052 if (!file) {
53 SkDebugf("missing resource %s\n", name);
54 return;
55 }
56
57 SkBitmap truth;
58 if (!create_truth(file, &truth)) {
59 ERRORF(r, "Failed to decode %s\n", name);
60 return;
61 }
62
scroggo8e6c7ad2016-09-16 08:20:38 -070063 // Now decode part of the file
Leon Scroggins III4993b952016-12-08 11:54:04 -050064 HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes));
scroggo8e6c7ad2016-09-16 08:20:38 -070065
66 // Note that we cheat and hold on to a pointer to stream, though it is owned by
67 // partialCodec.
Mike Reedede7bac2017-07-23 15:30:02 -040068 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream)));
scroggo8e6c7ad2016-09-16 08:20:38 -070069 if (!partialCodec) {
70 // Technically, this could be a small file where half the file is not
71 // enough.
72 ERRORF(r, "Failed to create codec for %s", name);
73 return;
74 }
75
Ben Wagner145dbcd2016-11-03 14:40:50 -040076 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070077 SkASSERT(info == truth.info());
78 SkBitmap incremental;
79 incremental.allocPixels(info);
80
scroggo19b91532016-10-24 09:03:26 -070081 while (true) {
82 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
83 incremental.getPixels(), incremental.rowBytes());
84 if (startResult == SkCodec::kSuccess) {
85 break;
86 }
87
88 if (stream->isAllDataReceived()) {
89 ERRORF(r, "Failed to start incremental decode\n");
90 return;
91 }
92
93 // Append some data. The size is arbitrary, but deliberately different from
94 // the buffer size used by SkPngCodec.
95 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -070096 }
97
98 while (true) {
99 const SkCodec::Result result = partialCodec->incrementalDecode();
100
scroggo19b91532016-10-24 09:03:26 -0700101 if (result == SkCodec::kSuccess) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700102 break;
103 }
104
scroggo8e6c7ad2016-09-16 08:20:38 -0700105 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
106
scroggo19b91532016-10-24 09:03:26 -0700107 if (stream->isAllDataReceived()) {
108 ERRORF(r, "Failed to completely decode %s", name);
109 return;
110 }
111
112 // Append some data. The size is arbitrary, but deliberately different from
113 // the buffer size used by SkPngCodec.
114 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -0700115 }
116
117 // compare to original
scroggo19b91532016-10-24 09:03:26 -0700118 compare_bitmaps(r, truth, incremental);
scroggo8e6c7ad2016-09-16 08:20:38 -0700119}
120
121DEF_TEST(Codec_partial, r) {
Leon Scroggins III83239652017-04-21 13:47:12 -0400122#if 0
123 // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
124 // support incremental decoding.
scroggo8e6c7ad2016-09-16 08:20:38 -0700125 test_partial(r, "plane.png");
126 test_partial(r, "plane_interlaced.png");
127 test_partial(r, "yellow_rose.png");
128 test_partial(r, "index8.png");
129 test_partial(r, "color_wheel.png");
130 test_partial(r, "mandrill_256.png");
131 test_partial(r, "mandrill_32.png");
132 test_partial(r, "arrow.png");
133 test_partial(r, "randPixels.png");
134 test_partial(r, "baby_tux.png");
Leon Scroggins III83239652017-04-21 13:47:12 -0400135#endif
scroggo19b91532016-10-24 09:03:26 -0700136 test_partial(r, "box.gif");
Leon Scroggins III4993b952016-12-08 11:54:04 -0500137 test_partial(r, "randPixels.gif", 215);
scroggo19b91532016-10-24 09:03:26 -0700138 test_partial(r, "color_wheel.gif");
139}
140
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500141// Verify that when decoding an animated gif byte by byte we report the correct
142// fRequiredFrame as soon as getFrameInfo reports the frame.
143DEF_TEST(Codec_requiredFrame, r) {
144 auto path = "colorTables.gif";
145 sk_sp<SkData> file = GetResourceAsData(path);
146 if (!file) {
147 return;
148 }
149
Mike Reedede7bac2017-07-23 15:30:02 -0400150 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(file));
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500151 if (!codec) {
152 ERRORF(r, "Failed to create codec from %s", path);
153 return;
154 }
155
156 auto frameInfo = codec->getFrameInfo();
157 if (frameInfo.size() <= 1) {
158 ERRORF(r, "Test is uninteresting with 0 or 1 frames");
159 return;
160 }
161
162 HaltingStream* stream(nullptr);
163 std::unique_ptr<SkCodec> partialCodec(nullptr);
164 for (size_t i = 0; !partialCodec; i++) {
165 if (file->size() == i) {
166 ERRORF(r, "Should have created a partial codec for %s", path);
167 return;
168 }
169 stream = new HaltingStream(file, i);
Mike Reedede7bac2017-07-23 15:30:02 -0400170 partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500171 }
172
173 std::vector<SkCodec::FrameInfo> partialInfo;
174 size_t frameToCompare = 0;
175 for (; stream->getLength() <= file->size(); stream->addNewData(1)) {
176 partialInfo = partialCodec->getFrameInfo();
177 for (; frameToCompare < partialInfo.size(); frameToCompare++) {
178 REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame
179 == frameInfo[frameToCompare].fRequiredFrame);
180 }
181
182 if (frameToCompare == frameInfo.size()) {
183 break;
184 }
185 }
186}
187
scroggo19b91532016-10-24 09:03:26 -0700188DEF_TEST(Codec_partialAnim, r) {
189 auto path = "test640x479.gif";
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500190 sk_sp<SkData> file = GetResourceAsData(path);
scroggo19b91532016-10-24 09:03:26 -0700191 if (!file) {
192 return;
193 }
194
195 // This stream will be owned by fullCodec, but we hang on to the pointer
196 // to determine frame offsets.
Mike Reedede7bac2017-07-23 15:30:02 -0400197 std::unique_ptr<SkCodec> fullCodec(SkCodec::MakeFromStream(skstd::make_unique<SkMemoryStream>(file)));
scroggo19b91532016-10-24 09:03:26 -0700198 const auto info = standardize_info(fullCodec.get());
199
200 // frameByteCounts stores the number of bytes to decode a particular frame.
201 // - [0] is the number of bytes for the header
202 // - frames[i] requires frameByteCounts[i+1] bytes to decode
Leon Scroggins III1f6af6b2017-06-12 16:41:09 -0400203 const std::vector<size_t> frameByteCounts = { 455, 69350, 1344, 1346, 1327 };
scroggo19b91532016-10-24 09:03:26 -0700204 std::vector<SkBitmap> frames;
scroggo19b91532016-10-24 09:03:26 -0700205 for (size_t i = 0; true; i++) {
scroggo19b91532016-10-24 09:03:26 -0700206 SkBitmap frame;
207 frame.allocPixels(info);
208
209 SkCodec::Options opts;
210 opts.fFrameIndex = i;
211 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
Leon Scroggins571b30f2017-07-11 17:35:31 +0000212 frame.rowBytes(), &opts);
scroggo19b91532016-10-24 09:03:26 -0700213
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500214 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
scroggo19b91532016-10-24 09:03:26 -0700215 // We need to distinguish between a partial frame and no more frames.
216 // getFrameInfo lets us do this, since it tells the number of frames
217 // not considering whether they are complete.
218 // FIXME: Should we use a different Result?
219 if (fullCodec->getFrameInfo().size() > i) {
220 // This is a partial frame.
221 frames.push_back(frame);
222 }
223 break;
224 }
225
226 if (result != SkCodec::kSuccess) {
227 ERRORF(r, "Failed to decode frame %i from %s", i, path);
228 return;
229 }
230
231 frames.push_back(frame);
232 }
233
234 // Now decode frames partially, then completely, and compare to the original.
235 HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]);
Mike Reedede7bac2017-07-23 15:30:02 -0400236 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
237 std::unique_ptr<SkStream>(haltingStream)));
scroggo19b91532016-10-24 09:03:26 -0700238 if (!partialCodec) {
239 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of %i",
240 path, frameByteCounts[0], file->size());
241 return;
242 }
243
244 SkASSERT(frameByteCounts.size() > frames.size());
245 for (size_t i = 0; i < frames.size(); i++) {
246 const size_t fullFrameBytes = frameByteCounts[i + 1];
247 const size_t firstHalf = fullFrameBytes / 2;
248 const size_t secondHalf = fullFrameBytes - firstHalf;
249
250 haltingStream->addNewData(firstHalf);
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500251 auto frameInfo = partialCodec->getFrameInfo();
252 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
253 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700254
255 SkBitmap frame;
256 frame.allocPixels(info);
257
258 SkCodec::Options opts;
259 opts.fFrameIndex = i;
260 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
261 frame.getPixels(), frame.rowBytes(), &opts);
262 if (result != SkCodec::kSuccess) {
263 ERRORF(r, "Failed to start incremental decode for %s on frame %i",
264 path, i);
265 return;
266 }
267
268 result = partialCodec->incrementalDecode();
269 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
270
271 haltingStream->addNewData(secondHalf);
272 result = partialCodec->incrementalDecode();
273 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
274
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500275 frameInfo = partialCodec->getFrameInfo();
276 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
277 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700278 compare_bitmaps(r, frames[i], frame);
279 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700280}
281
282// Test that calling getPixels when an incremental decode has been
283// started (but not finished) makes the next call to incrementalDecode
284// require a call to startIncrementalDecode.
285static void test_interleaved(skiatest::Reporter* r, const char* name) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500286 sk_sp<SkData> file = GetResourceAsData(name);
scroggo19b91532016-10-24 09:03:26 -0700287 if (!file) {
288 return;
289 }
290 const size_t halfSize = file->size() / 2;
Mike Reedede7bac2017-07-23 15:30:02 -0400291 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
292 skstd::make_unique<HaltingStream>(std::move(file), halfSize)));
scroggo8e6c7ad2016-09-16 08:20:38 -0700293 if (!partialCodec) {
294 ERRORF(r, "Failed to create codec for %s", name);
295 return;
296 }
297
Ben Wagner145dbcd2016-11-03 14:40:50 -0400298 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700299 SkBitmap incremental;
300 incremental.allocPixels(info);
301
302 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
303 incremental.getPixels(), incremental.rowBytes());
304 if (startResult != SkCodec::kSuccess) {
305 ERRORF(r, "Failed to start incremental decode\n");
306 return;
307 }
308
309 SkCodec::Result result = partialCodec->incrementalDecode();
310 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
311
312 SkBitmap full;
313 full.allocPixels(info);
314 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
315 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
316
317 // Now incremental decode will fail
318 result = partialCodec->incrementalDecode();
319 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
320}
321
322DEF_TEST(Codec_rewind, r) {
323 test_interleaved(r, "plane.png");
324 test_interleaved(r, "plane_interlaced.png");
scroggo19b91532016-10-24 09:03:26 -0700325 test_interleaved(r, "box.gif");
scroggo8e6c7ad2016-09-16 08:20:38 -0700326}
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500327
328// Modified version of the giflib logo, from
329// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
330// The global color map has been replaced with a local color map.
331static unsigned char gNoGlobalColorMap[] = {
332 // Header
333 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
334
335 // Logical screen descriptor
336 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
337
338 // Image descriptor
339 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
340
341 // Local color table
342 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
343
344 // Image data
345 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
346 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
347
348 // Trailer
349 0x3B,
350};
351
352// Test that a gif file truncated before its local color map behaves as expected.
353DEF_TEST(Codec_GifPreMap, r) {
354 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
Mike Reedede7bac2017-07-23 15:30:02 -0400355 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500356 if (!codec) {
357 ERRORF(r, "failed to create codec");
358 return;
359 }
360
361 SkBitmap truth;
362 auto info = standardize_info(codec.get());
363 truth.allocPixels(info);
364
365 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
366 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
367
368 // Truncate to 23 bytes, just before the color map. This should fail to decode.
Mike Reedede7bac2017-07-23 15:30:02 -0400369 codec = SkCodec::MakeFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500370 REPORTER_ASSERT(r, codec);
371 if (codec) {
372 SkBitmap bm;
373 bm.allocPixels(info);
374 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
375 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
376 }
377
378 // Again, truncate to 23 bytes, this time for an incremental decode. We
379 // cannot start an incremental decode until we have more data. If we did,
380 // we would be using the wrong color table.
381 HaltingStream* stream = new HaltingStream(data, 23);
Mike Reedede7bac2017-07-23 15:30:02 -0400382 codec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500383 REPORTER_ASSERT(r, codec);
384 if (codec) {
385 SkBitmap bm;
386 bm.allocPixels(info);
387 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
388 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
389
390 stream->addNewData(data->size());
391 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
392 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
393
394 result = codec->incrementalDecode();
395 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
396 compare_bitmaps(r, truth, bm);
397 }
398}
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400399
400DEF_TEST(Codec_emptyIDAT, r) {
401 const char* name = "baby_tux.png";
402 sk_sp<SkData> file = GetResourceAsData(name);
403 if (!file) {
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400404 return;
405 }
406
407 // Truncate to the beginning of the IDAT, immediately after the IDAT tag.
408 file = SkData::MakeSubset(file.get(), 0, 80);
409
Mike Reedede7bac2017-07-23 15:30:02 -0400410 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(file)));
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400411 if (!codec) {
412 ERRORF(r, "Failed to create a codec for %s", name);
413 return;
414 }
415
416 SkBitmap bm;
417 const auto info = standardize_info(codec.get());
418 bm.allocPixels(info);
419
420 const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
421 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
422}
Leon Scroggins III588fb042017-07-14 16:32:31 -0400423
424DEF_TEST(Codec_incomplete, r) {
425 for (const char* name : { "baby_tux.png",
426 "baby_tux.webp",
427 "CMYK.jpg",
428 "color_wheel.gif",
429 "google_chrome.ico",
430 "rle.bmp",
431 "mandrill.wbmp",
432 }) {
433 sk_sp<SkData> file = GetResourceAsData(name);
434 if (!name) {
435 continue;
436 }
437
438 for (size_t len = 14; len <= file->size(); len += 5) {
439 SkCodec::Result result;
Mike Reedede7bac2017-07-23 15:30:02 -0400440 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
441 skstd::make_unique<SkMemoryStream>(file->data(), len), &result));
Leon Scroggins III588fb042017-07-14 16:32:31 -0400442 if (codec) {
443 if (result != SkCodec::kSuccess) {
444 ERRORF(r, "Created an SkCodec for %s with %lu bytes, but "
445 "reported an error %i", name, len, result);
446 }
447 break;
448 }
449
450 if (SkCodec::kIncompleteInput != result) {
451 ERRORF(r, "Reported error %i for %s with %lu bytes",
452 result, name, len);
453 break;
454 }
455 }
456 }
457}