blob: 35249ac7d201b5ee0ddc38e5f341a8f043aa2793 [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++) {
nagarajan.n0ec0bf02017-10-11 10:41:27 +053046 if (memcmp(bm1.getAddr(0, i), bm2.getAddr(0, i), rowBytes)) {
47 ERRORF(r, "Bitmaps have different pixels, starting on line %i!", i);
48 return;
49 }
scroggo19b91532016-10-24 09:03:26 -070050 }
51}
52
Leon Scroggins III4993b952016-12-08 11:54:04 -050053static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -050054 sk_sp<SkData> file = GetResourceAsData(name);
scroggo8e6c7ad2016-09-16 08:20:38 -070055 if (!file) {
56 SkDebugf("missing resource %s\n", name);
57 return;
58 }
59
60 SkBitmap truth;
61 if (!create_truth(file, &truth)) {
62 ERRORF(r, "Failed to decode %s\n", name);
63 return;
64 }
65
scroggo8e6c7ad2016-09-16 08:20:38 -070066 // Now decode part of the file
Leon Scroggins III4993b952016-12-08 11:54:04 -050067 HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes));
scroggo8e6c7ad2016-09-16 08:20:38 -070068
69 // Note that we cheat and hold on to a pointer to stream, though it is owned by
70 // partialCodec.
Mike Reedede7bac2017-07-23 15:30:02 -040071 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream)));
scroggo8e6c7ad2016-09-16 08:20:38 -070072 if (!partialCodec) {
73 // Technically, this could be a small file where half the file is not
74 // enough.
75 ERRORF(r, "Failed to create codec for %s", name);
76 return;
77 }
78
Ben Wagner145dbcd2016-11-03 14:40:50 -040079 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -070080 SkASSERT(info == truth.info());
81 SkBitmap incremental;
82 incremental.allocPixels(info);
83
scroggo19b91532016-10-24 09:03:26 -070084 while (true) {
85 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
86 incremental.getPixels(), incremental.rowBytes());
87 if (startResult == SkCodec::kSuccess) {
88 break;
89 }
90
91 if (stream->isAllDataReceived()) {
92 ERRORF(r, "Failed to start incremental decode\n");
93 return;
94 }
95
96 // Append some data. The size is arbitrary, but deliberately different from
97 // the buffer size used by SkPngCodec.
98 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -070099 }
100
101 while (true) {
102 const SkCodec::Result result = partialCodec->incrementalDecode();
103
scroggo19b91532016-10-24 09:03:26 -0700104 if (result == SkCodec::kSuccess) {
scroggo8e6c7ad2016-09-16 08:20:38 -0700105 break;
106 }
107
scroggo8e6c7ad2016-09-16 08:20:38 -0700108 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
109
scroggo19b91532016-10-24 09:03:26 -0700110 if (stream->isAllDataReceived()) {
111 ERRORF(r, "Failed to completely decode %s", name);
112 return;
113 }
114
115 // Append some data. The size is arbitrary, but deliberately different from
116 // the buffer size used by SkPngCodec.
117 stream->addNewData(1000);
scroggo8e6c7ad2016-09-16 08:20:38 -0700118 }
119
120 // compare to original
scroggo19b91532016-10-24 09:03:26 -0700121 compare_bitmaps(r, truth, incremental);
scroggo8e6c7ad2016-09-16 08:20:38 -0700122}
123
124DEF_TEST(Codec_partial, r) {
Leon Scroggins III83239652017-04-21 13:47:12 -0400125#if 0
126 // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
127 // support incremental decoding.
scroggo8e6c7ad2016-09-16 08:20:38 -0700128 test_partial(r, "plane.png");
129 test_partial(r, "plane_interlaced.png");
130 test_partial(r, "yellow_rose.png");
131 test_partial(r, "index8.png");
132 test_partial(r, "color_wheel.png");
133 test_partial(r, "mandrill_256.png");
134 test_partial(r, "mandrill_32.png");
135 test_partial(r, "arrow.png");
136 test_partial(r, "randPixels.png");
137 test_partial(r, "baby_tux.png");
Leon Scroggins III83239652017-04-21 13:47:12 -0400138#endif
scroggo19b91532016-10-24 09:03:26 -0700139 test_partial(r, "box.gif");
Leon Scroggins III4993b952016-12-08 11:54:04 -0500140 test_partial(r, "randPixels.gif", 215);
scroggo19b91532016-10-24 09:03:26 -0700141 test_partial(r, "color_wheel.gif");
142}
143
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500144// Verify that when decoding an animated gif byte by byte we report the correct
145// fRequiredFrame as soon as getFrameInfo reports the frame.
146DEF_TEST(Codec_requiredFrame, r) {
147 auto path = "colorTables.gif";
148 sk_sp<SkData> file = GetResourceAsData(path);
149 if (!file) {
150 return;
151 }
152
Mike Reedede7bac2017-07-23 15:30:02 -0400153 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(file));
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500154 if (!codec) {
155 ERRORF(r, "Failed to create codec from %s", path);
156 return;
157 }
158
159 auto frameInfo = codec->getFrameInfo();
160 if (frameInfo.size() <= 1) {
161 ERRORF(r, "Test is uninteresting with 0 or 1 frames");
162 return;
163 }
164
165 HaltingStream* stream(nullptr);
166 std::unique_ptr<SkCodec> partialCodec(nullptr);
167 for (size_t i = 0; !partialCodec; i++) {
168 if (file->size() == i) {
169 ERRORF(r, "Should have created a partial codec for %s", path);
170 return;
171 }
172 stream = new HaltingStream(file, i);
Mike Reedede7bac2017-07-23 15:30:02 -0400173 partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500174 }
175
176 std::vector<SkCodec::FrameInfo> partialInfo;
177 size_t frameToCompare = 0;
178 for (; stream->getLength() <= file->size(); stream->addNewData(1)) {
179 partialInfo = partialCodec->getFrameInfo();
180 for (; frameToCompare < partialInfo.size(); frameToCompare++) {
181 REPORTER_ASSERT(r, partialInfo[frameToCompare].fRequiredFrame
182 == frameInfo[frameToCompare].fRequiredFrame);
183 }
184
185 if (frameToCompare == frameInfo.size()) {
186 break;
187 }
188 }
189}
190
scroggo19b91532016-10-24 09:03:26 -0700191DEF_TEST(Codec_partialAnim, r) {
192 auto path = "test640x479.gif";
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500193 sk_sp<SkData> file = GetResourceAsData(path);
scroggo19b91532016-10-24 09:03:26 -0700194 if (!file) {
195 return;
196 }
197
198 // This stream will be owned by fullCodec, but we hang on to the pointer
199 // to determine frame offsets.
Mike Reedede7bac2017-07-23 15:30:02 -0400200 std::unique_ptr<SkCodec> fullCodec(SkCodec::MakeFromStream(skstd::make_unique<SkMemoryStream>(file)));
scroggo19b91532016-10-24 09:03:26 -0700201 const auto info = standardize_info(fullCodec.get());
202
203 // frameByteCounts stores the number of bytes to decode a particular frame.
204 // - [0] is the number of bytes for the header
205 // - frames[i] requires frameByteCounts[i+1] bytes to decode
Leon Scroggins III1f6af6b2017-06-12 16:41:09 -0400206 const std::vector<size_t> frameByteCounts = { 455, 69350, 1344, 1346, 1327 };
scroggo19b91532016-10-24 09:03:26 -0700207 std::vector<SkBitmap> frames;
scroggo19b91532016-10-24 09:03:26 -0700208 for (size_t i = 0; true; i++) {
scroggo19b91532016-10-24 09:03:26 -0700209 SkBitmap frame;
210 frame.allocPixels(info);
211
212 SkCodec::Options opts;
213 opts.fFrameIndex = i;
214 const SkCodec::Result result = fullCodec->getPixels(info, frame.getPixels(),
Leon Scroggins571b30f2017-07-11 17:35:31 +0000215 frame.rowBytes(), &opts);
scroggo19b91532016-10-24 09:03:26 -0700216
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500217 if (result == SkCodec::kIncompleteInput || result == SkCodec::kInvalidInput) {
scroggo19b91532016-10-24 09:03:26 -0700218 // We need to distinguish between a partial frame and no more frames.
219 // getFrameInfo lets us do this, since it tells the number of frames
220 // not considering whether they are complete.
221 // FIXME: Should we use a different Result?
222 if (fullCodec->getFrameInfo().size() > i) {
223 // This is a partial frame.
224 frames.push_back(frame);
225 }
226 break;
227 }
228
229 if (result != SkCodec::kSuccess) {
230 ERRORF(r, "Failed to decode frame %i from %s", i, path);
231 return;
232 }
233
234 frames.push_back(frame);
235 }
236
237 // Now decode frames partially, then completely, and compare to the original.
238 HaltingStream* haltingStream = new HaltingStream(file, frameByteCounts[0]);
Mike Reedede7bac2017-07-23 15:30:02 -0400239 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
240 std::unique_ptr<SkStream>(haltingStream)));
scroggo19b91532016-10-24 09:03:26 -0700241 if (!partialCodec) {
242 ERRORF(r, "Failed to create a partial codec from %s with %i bytes out of %i",
243 path, frameByteCounts[0], file->size());
244 return;
245 }
246
247 SkASSERT(frameByteCounts.size() > frames.size());
248 for (size_t i = 0; i < frames.size(); i++) {
249 const size_t fullFrameBytes = frameByteCounts[i + 1];
250 const size_t firstHalf = fullFrameBytes / 2;
251 const size_t secondHalf = fullFrameBytes - firstHalf;
252
253 haltingStream->addNewData(firstHalf);
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500254 auto frameInfo = partialCodec->getFrameInfo();
255 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
256 REPORTER_ASSERT(r, !frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700257
258 SkBitmap frame;
259 frame.allocPixels(info);
260
261 SkCodec::Options opts;
262 opts.fFrameIndex = i;
263 SkCodec::Result result = partialCodec->startIncrementalDecode(info,
264 frame.getPixels(), frame.rowBytes(), &opts);
265 if (result != SkCodec::kSuccess) {
266 ERRORF(r, "Failed to start incremental decode for %s on frame %i",
267 path, i);
268 return;
269 }
270
271 result = partialCodec->incrementalDecode();
272 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
273
274 haltingStream->addNewData(secondHalf);
275 result = partialCodec->incrementalDecode();
276 REPORTER_ASSERT(r, SkCodec::kSuccess == result);
277
Leon Scroggins III3639faa2016-12-08 11:38:58 -0500278 frameInfo = partialCodec->getFrameInfo();
279 REPORTER_ASSERT(r, frameInfo.size() == i + 1);
280 REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
scroggo19b91532016-10-24 09:03:26 -0700281 compare_bitmaps(r, frames[i], frame);
282 }
scroggo8e6c7ad2016-09-16 08:20:38 -0700283}
284
285// Test that calling getPixels when an incremental decode has been
286// started (but not finished) makes the next call to incrementalDecode
287// require a call to startIncrementalDecode.
288static void test_interleaved(skiatest::Reporter* r, const char* name) {
Leon Scroggins IIIe4ba1052017-01-30 13:55:14 -0500289 sk_sp<SkData> file = GetResourceAsData(name);
scroggo19b91532016-10-24 09:03:26 -0700290 if (!file) {
291 return;
292 }
293 const size_t halfSize = file->size() / 2;
Mike Reedede7bac2017-07-23 15:30:02 -0400294 std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(
295 skstd::make_unique<HaltingStream>(std::move(file), halfSize)));
scroggo8e6c7ad2016-09-16 08:20:38 -0700296 if (!partialCodec) {
297 ERRORF(r, "Failed to create codec for %s", name);
298 return;
299 }
300
Ben Wagner145dbcd2016-11-03 14:40:50 -0400301 const SkImageInfo info = standardize_info(partialCodec.get());
scroggo8e6c7ad2016-09-16 08:20:38 -0700302 SkBitmap incremental;
303 incremental.allocPixels(info);
304
305 const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
306 incremental.getPixels(), incremental.rowBytes());
307 if (startResult != SkCodec::kSuccess) {
308 ERRORF(r, "Failed to start incremental decode\n");
309 return;
310 }
311
312 SkCodec::Result result = partialCodec->incrementalDecode();
313 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
314
315 SkBitmap full;
316 full.allocPixels(info);
317 result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
318 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
319
320 // Now incremental decode will fail
321 result = partialCodec->incrementalDecode();
322 REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
323}
324
325DEF_TEST(Codec_rewind, r) {
326 test_interleaved(r, "plane.png");
327 test_interleaved(r, "plane_interlaced.png");
scroggo19b91532016-10-24 09:03:26 -0700328 test_interleaved(r, "box.gif");
scroggo8e6c7ad2016-09-16 08:20:38 -0700329}
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500330
331// Modified version of the giflib logo, from
332// http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html
333// The global color map has been replaced with a local color map.
334static unsigned char gNoGlobalColorMap[] = {
335 // Header
336 0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
337
338 // Logical screen descriptor
339 0x0A, 0x00, 0x0A, 0x00, 0x11, 0x00, 0x00,
340
341 // Image descriptor
342 0x2C, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x81,
343
344 // Local color table
345 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
346
347 // Image data
348 0x02, 0x16, 0x8C, 0x2D, 0x99, 0x87, 0x2A, 0x1C, 0xDC, 0x33, 0xA0, 0x02, 0x75,
349 0xEC, 0x95, 0xFA, 0xA8, 0xDE, 0x60, 0x8C, 0x04, 0x91, 0x4C, 0x01, 0x00,
350
351 // Trailer
352 0x3B,
353};
354
355// Test that a gif file truncated before its local color map behaves as expected.
356DEF_TEST(Codec_GifPreMap, r) {
357 sk_sp<SkData> data = SkData::MakeWithoutCopy(gNoGlobalColorMap, sizeof(gNoGlobalColorMap));
Mike Reedede7bac2017-07-23 15:30:02 -0400358 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500359 if (!codec) {
360 ERRORF(r, "failed to create codec");
361 return;
362 }
363
364 SkBitmap truth;
365 auto info = standardize_info(codec.get());
366 truth.allocPixels(info);
367
368 auto result = codec->getPixels(info, truth.getPixels(), truth.rowBytes());
369 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
370
371 // Truncate to 23 bytes, just before the color map. This should fail to decode.
Mike Reedede7bac2017-07-23 15:30:02 -0400372 codec = SkCodec::MakeFromData(SkData::MakeWithoutCopy(gNoGlobalColorMap, 23));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500373 REPORTER_ASSERT(r, codec);
374 if (codec) {
375 SkBitmap bm;
376 bm.allocPixels(info);
377 result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
378 REPORTER_ASSERT(r, result == SkCodec::kInvalidInput);
379 }
380
381 // Again, truncate to 23 bytes, this time for an incremental decode. We
382 // cannot start an incremental decode until we have more data. If we did,
383 // we would be using the wrong color table.
384 HaltingStream* stream = new HaltingStream(data, 23);
Mike Reedede7bac2017-07-23 15:30:02 -0400385 codec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
Leon Scroggins III3fc97d72016-12-09 16:39:33 -0500386 REPORTER_ASSERT(r, codec);
387 if (codec) {
388 SkBitmap bm;
389 bm.allocPixels(info);
390 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
391 REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
392
393 stream->addNewData(data->size());
394 result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
395 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
396
397 result = codec->incrementalDecode();
398 REPORTER_ASSERT(r, result == SkCodec::kSuccess);
399 compare_bitmaps(r, truth, bm);
400 }
401}
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400402
403DEF_TEST(Codec_emptyIDAT, r) {
404 const char* name = "baby_tux.png";
405 sk_sp<SkData> file = GetResourceAsData(name);
406 if (!file) {
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400407 return;
408 }
409
410 // Truncate to the beginning of the IDAT, immediately after the IDAT tag.
411 file = SkData::MakeSubset(file.get(), 0, 80);
412
Mike Reedede7bac2017-07-23 15:30:02 -0400413 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(std::move(file)));
Leon Scroggins IIIb6446502017-04-24 09:32:50 -0400414 if (!codec) {
415 ERRORF(r, "Failed to create a codec for %s", name);
416 return;
417 }
418
419 SkBitmap bm;
420 const auto info = standardize_info(codec.get());
421 bm.allocPixels(info);
422
423 const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
424 REPORTER_ASSERT(r, SkCodec::kIncompleteInput == result);
425}
Leon Scroggins III588fb042017-07-14 16:32:31 -0400426
427DEF_TEST(Codec_incomplete, r) {
428 for (const char* name : { "baby_tux.png",
429 "baby_tux.webp",
430 "CMYK.jpg",
431 "color_wheel.gif",
432 "google_chrome.ico",
433 "rle.bmp",
434 "mandrill.wbmp",
435 }) {
436 sk_sp<SkData> file = GetResourceAsData(name);
437 if (!name) {
438 continue;
439 }
440
441 for (size_t len = 14; len <= file->size(); len += 5) {
442 SkCodec::Result result;
Mike Reedede7bac2017-07-23 15:30:02 -0400443 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(
444 skstd::make_unique<SkMemoryStream>(file->data(), len), &result));
Leon Scroggins III588fb042017-07-14 16:32:31 -0400445 if (codec) {
446 if (result != SkCodec::kSuccess) {
447 ERRORF(r, "Created an SkCodec for %s with %lu bytes, but "
448 "reported an error %i", name, len, result);
449 }
450 break;
451 }
452
453 if (SkCodec::kIncompleteInput != result) {
454 ERRORF(r, "Reported error %i for %s with %lu bytes",
455 result, name, len);
456 break;
457 }
458 }
459 }
460}